PageSourceSearch

https://pupilbot.com/app.js

js pupilbot.com collected 2026-10-02 01:40:22 UTC 217,974 bytes, 1,244 lines download raw bytes

1let accessToken=localStorage.getItem("pbot_access_token"),refreshToken=localStorage.getItem("pbot_refresh_token"),currentUser=null;const SUPPORTED_LOCALES=["en","es","fr","de","it","ro"],DEFAULT_LOCALE="en",LOCALE_META={en:{short:"EN",flag:"\u{1F1EC}\u{1F1E7}"},es:{short:"ES",flag:"\u{1F1EA}\u{1F1F8}"},fr:{short:"FR",flag:"\u{1F1EB}\u{1F1F7}"},de:{short:"DE",flag:"\u{1F1E9}\u{1F1EA}"},it:{short:"IT",flag:"\u{1F1EE}\u{1F1F9}"},ro:{short:"RO",flag:"\u{1F1F7}\u{1F1F4}"}},URL_LOCALE_RE=new RegExp(`^/(${SUPPORTED_LOCALES.join("|")})(?:/|$)`);function readLocaleCookie(){const e=document.cookie.match(/(?:^|;\s*)pbot_lang=([a-z]{2})/);return e&&SUPPORTED_LOCALES.includes(e[1])?e[1]:null}function writeLocaleCookie(e){if(!SUPPORTED_LOCALES.includes(e))return;const n=3600*24*365;document.cookie=`pbot_lang=${e}; Path=/; Max-Age=${n}; SameSite=Lax`}function pathWithoutLocale(e=location.pathname){const n=e.match(URL_LOCALE_RE);return n?e.slice(n[0].length-(e[n[0].length-1]==="/"?1:0))||"/":e||"/"}function localizePath(e,n){const s=e&&e.startsWith("/")?e:`/${e||""}`;return!n||n===DEFAULT_LOCALE?s:s==="/"?`/${n}`:`/${n}${s}`}function resolveClientLocale(){const e=location.pathname.match(URL_LOCALE_RE);if(e)return{locale:e[1],explicit:!0};const n=readLocaleCookie();if(n)return{locale:n,explicit:!0};const s=localStorage.getItem("pbot_lang");
1if(s&&SUPPORTED_LOCALES.includes(s))return{locale:s,explicit:!0};const r=(navigator.language||"").split("-")[0];return SUPPORTED_LOCALES.includes(r)?{locale:r,explicit:!1}:{locale:DEFAULT_LOCALE,explicit:!1}}const localeFromUrl=!!location.pathname.match(URL_LOCALE_RE),servedLocale=document.documentElement.lang||DEFAULT_LOCALE,_resolved=resolveClientLocale();let currentLocale=_resolved.explicit?_resolved.locale:servedLocale;_resolved.explicit&&writeLocaleCookie(currentLocale);let dictionary={},dictionaryLocale=null;const PLURAL_RE=/\{(\w+),\s*plural,\s*one\{([^{}]*)\}\s*other\{([^{}]*)\}\}/g;function applyPlurals(e,n){return e.replace(PLURAL_RE,(s,r,a,i)=>{const o=Number(n?.[r]);return(o===1?a:i).replace(/#/g,String(o))})}function applyVars(e,n){return n?e.replace(/\{(\w+)\}/g,(s,r)=>Object.prototype.hasOwnProperty.call(n,r)?String(n[r]):s):e}function t(e,n){const s=dictionary[e];return s===void 0?e:applyVars(applyPlurals(s,n),n)}function hasTranslation(e){return Object.prototype.hasOwnProperty.call(dictionary,e)}function tOr(e,n){return hasTranslation(e)?t(e):n}function canRepaintServedCopy(){return dictionaryLocale!==null&&dictionaryLocale===servedLocale}let defaultAcceptLanguage=currentLocale;async function loadDictionary(e){let n;try{const s=await fetch(`/api/i18n/${e}`);if(!s.ok)return e!==DEFAULT_LOCALE?loadDictionary(DEFAULT_LOCALE):void 0;n=await s.json()}catch{return}dictionary=n.data||{},dictionaryLocale=e,document.documentElement.lang=e,defaultAcceptLanguage=e}async function applyServerLanguage(e){const n=e?.language;return n?!SUPPORTED_LOCALES.includes(n)||n===currentLocale?!1:localeFromUrl?(api("/api/users/me",{method:"PATCH",body:{language:currentLocale}}).catch(()=>{}),currentUser&&(currentUser.language=currentLocale),!1):(localStorage.setItem("pbot_lang",n),writeLocaleCookie(n),currentLocale=n,await loadDictionary(n),!0):(api("/api/users/me",{method:"PATCH",body:{language:currentLocale}}).catch(()=>{}),currentUser&&(currentUser.language=currentLocale),!1)}async function api(e,{method:n="GET",body:s,auth:r=!0}={}){const a=_authGeneration,i={};s&&(i["Content-Type"]="application/json"),r&&accessToken&&(i.Authorization=`Bearer ${accessToken}`),defaultAcceptLanguage&&(i["Accept-Language"]=defaultAcceptLanguage);const o=await fetch(e,{method:n,headers:i,body:s?JSON.stringify(s):void 0});if(o.status===401&&r&&refreshToken){if(a!==_authGeneration)return null;const u=await tryRefresh();return a!==_authGeneration?null:u?api(e,{method:n,body:s,auth:r}):(logout(),null)}return o.json()}async function apiUpload(e,n){const s={};return accessToken&&(s.Authorization=`Bearer ${accessToken}`),defaultAcceptLanguage&&(s["Accept-Language"]=defaultAcceptLanguage),(await fetch(e,{method:"POST",headers:s,body:n})).json()}function postClientEvent(e,n){accessToken&&sendBeacon(e,n).catch(()=>{})}function beaconFetch(e,n){const s={"Content-Type":"application/json",Authorization:`Bearer ${accessToken}`};return defaultAcceptLanguage&&(s["Accept-Language"]=defaultAcceptLanguage),fetch("/api/events",{method:"POST",headers:s,body:JSON.stringify({event:e,metadata:n}),keepalive:!0})}async function sendBeacon(e,n){const s=_authGeneration;(await beaconFetch(e,n)).status!==401||!refreshToken||s===_authGeneration&&await tryRefresh()&&s===_authGeneration&&await beaconFetch(e,n)}let _lastScreenPosted=null;function postScreenView(e){e!==_lastScreenPosted&&(_lastScreenPosted=e,postClientEvent("screen_viewed",{screen:e}))}let _pendingPaywallSource=null;function escapeHtml(e){return String(e??"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;")}function renderRichMath(e,n){const s=globalThis.katex;if(s&&typeof s.renderToString=="function")try{return s.renderToString(e,{displayMode:n,throwOnError:!1,output:"htmlAndMathml",strict:"ignore"})}catch{}return escapeHtml(n?"$$"+e+"$$":"$"+e+"$")}function renderRichCodeBlock(e,n){return'<div class="rich-scroll"><pre class="rich-code"><code'+(/^[A-Za-z0-9_+-]+$/.test(e)?' class="language-'+escapeHtml(e)+'"':"")+">"+escapeHtml(n)+"</code></pre></div>"}function findInlineMathClose(e,n){const s=e[n+1];if(s===void 0||s==="$"||/\s/.test(s))return-1;for(let r=n+2;r<e.length;r+=1){const a=e[r];if(a===`
2`)return-1;if(a==="$"&&!/\s/.test(e[r-1])&&!/[0-9]/.test(e[r+1]||""))return r}return-1}function renderRichInline(e){let n="",s="",r=0;for(;r<e.length;){const a=e[r];if(a==="`"){const i=e.indexOf("`",r+1);if(i>r+1&&e.slice(r+1,i).indexOf(`
3`)===-1){n+=escapeHtml(s)+'<code class="rich-inline-code">'+escapeHtml(e.slice(r+1,i))+"</code>",s="",r=i+1;continue}}else if(a==="$"){const i=findInlineMathClose(e,r);if(i!==-1){n+=escapeHtml(s)+renderRichMath(e.slice(r+1,i),!1),s="",r=i+1;continue}}s+=a,r+=1}return n+escapeHtml(s)}function renderRichTableCells(e){let n=e.trim();return n.startsWith("|")&&(n=n.slice(1)),n.endsWith("|")&&(n=n.slice(0,-1)),n.split("|").map(s=>s.trim())}function renderRichTable(e){const n=renderRichTableCells(e[0]),s=e.slice(2).map(renderRichTableCells);let r='<div class="rich-scroll"><table class="rich-table"><thead><tr>';for(const a of n)r+="<th>"+renderRichInline(a)+"</th>";if(r+="</tr></thead>",s.length>0){r+="<tbody>";for(const a of s){r+="<tr>";for(let i=0;i<n.length;i+=1)r+="<td>"+renderRichInline(a[i]??"")+"</td>";r+="</tr>"}r+="</tbody>"}return r+"</table></div>"}function renderRich(e){const n=String(e??"").replace(/\r\n/g,`
4`);if(!n)return"";const s=[],r=d=>(s.push(d),"\uE000"+(s.length-1)+"\uE001");let a=n.replace(/```([A-Za-z0-9_+-]*)[ \t]*\n([\s\S]*?)\n?```/g,(d,m,g)=>r(renderRichCodeBlock(m,g)));a=a.replace(/\$\$([^$]+?)\$\$/g,(d,m)=>r('<div class="rich-math-display">'+renderRichMath(m.trim(),!0)+"</div>"));const i=d=>d.indexOf("|")!==-1&&/^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/.test(d),o=a.split(`
5`),u=[];let c=0;for(;c<o.length;){if(c+1<o.length&&o[c].indexOf("|")!==-1&&i(o[c+1])){let d=c+2;
5for(;d<o.length&&o[d].indexOf("|")!==-1&&o[d].trim()!=="";)d+=1;u.push({block:!0,html:renderRichTable(o.slice(c,d))}),c=d;continue}u.push({block:!1,html:renderRichInline(o[c])}),c+=1}let l="";for(let d=0;d<u.length;d+=1){if(u[d].block){l+=u[d].html;continue}d>0&&!u[d-1].block&&(l+=`
6`),l+=u[d].html}return l.replace(/\n?\uE000(\d+)\uE001\n?/g,(d,m)=>s[Number(m)])}const KATEX_URL="/assets/katex/katex.min.js";let _katexPromise=null;function loadKatex(){return globalThis.katex?Promise.resolve():(_katexPromise||(_katexPromise=new Promise(e=>{const n=document.createElement("script");n.src=KATEX_URL,n.async=!0,n.onload=e,n.onerror=()=>{_katexPromise=null,e()},document.head.appendChild(n)})),_katexPromise)}let _toastTimer=null;function showToast(e,n="error"){let s=document.getElementById("pbot-toast");s||(s=document.createElement("div"),s.id="pbot-toast",s.setAttribute("role","status"),s.setAttribute("aria-live","polite"),s.className="fixed left-1/2 -translate-x-1/2 bottom-6 z-50 max-w-sm px-4 py-3 rounded-[0.875rem] shelf-shadow text-sm font-medium pointer-events-none transition-opacity",document.body.appendChild(s));let r;n==="success"?r="bg-tertiary-container text-on-tertiary-container":n==="info"?r="bg-surface-container-highest text-on-surface":r="bg-error-container text-on-error-container",s.className=`fixed left-1/2 -translate-x-1/2 bottom-6 z-50 max-w-sm px-4 py-3 rounded-[0.875rem] shelf-shadow text-sm font-medium pointer-events-none transition-opacity ${r}`,s.textContent=e,s.style.opacity="1",_toastTimer&&clearTimeout(_toastTimer),_toastTimer=setTimeout(()=>{s.style.opacity="0",_toastTimer=setTimeout(()=>{s.remove(),_toastTimer=null},200)},3500)}let _refreshInFlight=null;async function tryRefresh(){if(_refreshInFlight)return _refreshInFlight;const e=refreshOnce();_refreshInFlight=e;try{return await e}finally{_refreshInFlight===e&&(_refreshInFlight=null)}}let _authGeneration=0;async function refreshOnce(){const e=_authGeneration,n=refreshToken;try{const s=await fetch("/auth/refresh",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({refreshToken:n})});if(!s.ok)return e===_authGeneration&&!!refreshToken&&refreshToken!==n;const r=await s.json();return e!==_authGeneration||refreshToken!==n?!1:(setTokens(r.accessToken,r.refreshToken),!0)}catch{return!1}}function setTokens(e,n){accessToken=e,refreshToken=n,localStorage.setItem("pbot_access_token",e),localStorage.setItem("pbot_refresh_token",n)}function jwtSub(e){try{return JSON.parse(atob(e.split(".")[1])).sub}catch{return null}}window.addEventListener("storage",e=>{if(e.key!=="pbot_access_token"&&e.key!=="pbot_refresh_token")return;const n=!!accessToken,s=jwtSub(accessToken);e.key==="pbot_access_token"&&(accessToken=e.newValue),e.key==="pbot_refresh_token"&&(refreshToken=e.newValue),!(!n||e.key!=="pbot_access_token")&&(e.newValue===null||s&&jwtSub(e.newValue)!==s)&&(_authGeneration+=1,location.reload())});function clearTokens(){accessToken=null,refreshToken=null,localStorage.removeItem("pbot_access_token"),localStorage.removeItem("pbot_refresh_token"),_authGeneration+=1,_refreshInFlight=null,_lastScreenPosted=null,_pasteDraft={title:"",body:"",studyLanguage:!1}}let isSignUp=!0,isForgot=!1,turnstileWidgetId=null;function isTurnstileConfigured(){return!!(window.__TURNSTILE_SITE_KEY__&&window.__TURNSTILE_SITE_KEY__.length>0)}let turnstileScriptRequested=!1;function ensureTurnstileScript(){if(turnstileScriptRequested||!isTurnstileConfigured())return;if(typeof window.turnstile<"u"){renderTurnstile();return}turnstileScriptRequested=!0;const e=document.createElement("script");e.src="https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit&onload=onTurnstileReady",e.async=!0,e.defer=!0,document.head.appendChild(e)}function renderTurnstile(){if(!isTurnstileConfigured())return;if(typeof window.turnstile>"u"){Array.isArray(window.__turnstileQueue)&&window.__turnstileQueue.push(renderTurnstile);return}const e=document.getElementById("auth-captcha");e&&turnstileWidgetId===null&&(turnstileWidgetId=window.turnstile.render(e,{sitekey:window.__TURNSTILE_SITE_KEY__,action:"auth",appearance:"interaction-only","error-callback":()=>{resetTurnstile()},"expired-callback":()=>{resetTurnstile()}}))}function resetTurnstile(){if(turnstileWidgetId!==null&&window.turnstile)try{window.turnstile.reset(turnstileWidgetId)}catch{}}function getTurnstileToken(){if(isTurnstileConfigured()&&!(turnstileWidgetId===null||!window.turnstile))try{return window.turnstile.getResponse(turnstileWidgetId)||void 0}catch{return}}function authPageMode(){const e=pathWithoutLocale().replace(/\/+$/,"")||"/";return e==="/login"?"login":e==="/signup"?"signup":null}async function completeAuthNavigation(){if(authPageMode()){location.assign(localizePath("/",currentLocale)+location.search);return}await consumeStripeReturnParam(),showDashboard(),consumeMaterialReturnParam()}function setAuthMode(e){if(!document.getElementById("auth-form"))return;
6isSignUp=e==="signup",isForgot=e==="forgot";const n=isForgot?"ui.auth.forgot_title":isSignUp?"ui.auth.signup_title":"ui.auth.login_title",s=isForgot?"ui.auth.forgot_subtitle":isSignUp?"ui.auth.signup_subtitle":"ui.auth.login_subtitle",r=isForgot?"ui.auth.forgot_submit":isSignUp?"ui.auth.signup_submit":"ui.auth.login_submit",a=canRepaintServedCopy(),i=(u,c)=>{const l=document.getElementById(u);l&&a&&hasTranslation(c)&&(l.textContent=t(c))};i("auth-title",n),i("auth-subtitle",s),i("auth-submit",r);const o=document.getElementById("auth-toggle-text");isForgot?(o&&(o.textContent=""),i("auth-toggle","ui.auth.back_to_login")):(i("auth-toggle-text",isSignUp?"ui.auth.have_account":"ui.auth.no_account"),i("auth-toggle",isSignUp?"ui.auth.toggle_to_login":"ui.auth.toggle_to_signup")),document.getElementById("name-field").style.display=isSignUp?"block":"none",document.getElementById("password-field").style.display=isForgot?"none":"block",document.getElementById("auth-password").required=!isForgot,document.getElementById("auth-forgot").classList.toggle("hidden",isSignUp||isForgot),document.getElementById("auth-divider").style.display=isForgot?"none":"flex",document.getElementById("google-signin-btn").style.display=isForgot?"none":"flex",document.getElementById("apple-signin-btn").style.display=isForgot?"none":"flex",document.getElementById("auth-error").classList.add("hidden"),document.getElementById("auth-success").classList.add("hidden")}function switchAuthMode(e){canRepaintServedCopy()&&hasTranslation("ui.auth."+e+"_submit")&&setAuthMode(e)}function initAuth(){const e=document.getElementById("auth-form"),n=document.getElementById("auth-toggle");if(!e)return;const s=()=>{ensureTurnstileScript(),renderTurnstile()};e.addEventListener("focusin",s,{once:!0});const r=document.getElementById("login");r&&"IntersectionObserver"in window?new IntersectionObserver((o,u)=>{o.some(c=>c.isIntersecting)&&(s(),u.disconnect())},{rootMargin:"300px"}).observe(r):s(),n&&n.addEventListener("click",()=>{if(!isForgot&&authPageMode()){location.assign(localizePath(isSignUp?"/login":"/signup",currentLocale)+location.search);return}switchAuthMode(isForgot||isSignUp?"login":"signup")});const a=document.getElementById("auth-forgot");a&&a.addEventListener("click",()=>switchAuthMode("forgot")),e.addEventListener("submit",async i=>{i.preventDefault();const o=document.getElementById("auth-error"),u=document.getElementById("auth-success");o.classList.add("hidden"),u.classList.add("hidden");const c=document.getElementById("auth-email").value,l=getTurnstileToken();if(isForgot){const m=document.getElementById("auth-submit");m.disabled=!0;try{const g=await api("/auth/forgot-password",{method:"POST",body:{email:c,turnstileToken:l},auth:!1});if(g&&g.error){o.textContent=g.error,o.classList.remove("hidden"),resetTurnstile();return}u.textContent=t("ui.auth.forgot_success"),u.classList.remove("hidden"),resetTurnstile()}catch{o.textContent=t("ui.auth.generic_error"),o.classList.remove("hidden")}finally{m.disabled=!1}return}const d=document.getElementById("auth-password").value;try{let m;if(isSignUp){const g=document.getElementById("auth-name").value;m=await api("/auth/register/request-pin",{method:"POST",body:{email:c,password:d,name:g,turnstileToken:l},auth:!1})}else m=await api("/auth/login",{method:"POST",body:{email:c,password:d,turnstileToken:l},auth:!1});if(m.error){o.textContent=m.error,o.classList.remove("hidden"),resetTurnstile();return}if(isSignUp){resetTurnstile(),showPinStep({email:c,password:d,name:document.getElementById("auth-name").value,registrationId:m.registrationId});return}setTokens(m.accessToken,m.refreshToken),currentUser=m.user,await applyServerLanguage(m.user),await completeAuthNavigation()}catch{o.textContent=t("ui.auth.generic_error"),o.classList.remove("hidden")}}),initPinStep(),initAppleSignIn(),initGoogleSignIn()}let pendingPin=null,pinSavedCopy=null;const PIN_HIDDEN_WHILE_VERIFYING=["auth-form","auth-divider","google-signin-btn","apple-signin-btn","auth-toggle-row"];let pinSavedDisplay=null;function showPinStep(e){const n=document.getElementById("pin-section");if(!n)return;pendingPin=e;const s=document.getElementById("auth-title"),r=document.getElementById("auth-subtitle");pinSavedCopy||(pinSavedCopy={title:s.textContent,subtitle:r.textContent}
6),s.textContent=n.dataset.title,r.textContent=n.dataset.subtitle.replace("{email}",e.email),pinSavedDisplay||(pinSavedDisplay={},PIN_HIDDEN_WHILE_VERIFYING.forEach(i=>{const o=document.getElementById(i);o&&(pinSavedDisplay[i]=o.style.display,o.style.display="none")})),document.getElementById("pin-error").classList.add("hidden"),document.getElementById("pin-success").classList.add("hidden");const a=document.getElementById("pin-input");a.value="",n.classList.remove("hidden"),a.focus()}function hidePinStep(){const e=document.getElementById("pin-section");e&&(pendingPin=null,e.classList.add("hidden"),pinSavedDisplay&&(Object.entries(pinSavedDisplay).forEach(([n,s])=>{const r=document.getElementById(n);r&&(r.style.display=s)}),pinSavedDisplay=null),pinSavedCopy&&(document.getElementById("auth-title").textContent=pinSavedCopy.title,document.getElementById("auth-subtitle").textContent=pinSavedCopy.subtitle))}function initPinStep(){const e=document.getElementById("pin-form");if(!e)return;const n=document.getElementById("pin-input");n.addEventListener("input",()=>{n.value=String(n.value||"").replace(/\D/g,"").slice(0,4)}),document.getElementById("pin-back").addEventListener("click",hidePinStep),document.getElementById("pin-resend").addEventListener("click",resendPin),e.addEventListener("submit",onPinSubmit)}async function onPinSubmit(e){e.preventDefault();const n=document.getElementById("pin-error"),s=document.getElementById("pin-success");n.classList.add("hidden"),s.classList.add("hidden");const r=pendingPin?.registrationId,a=(document.getElementById("pin-input").value||"").trim();if(!r){hidePinStep();return}const i=document.getElementById("pin-submit");i.disabled=!0;try{const o=await api("/auth/register/verify-pin",{method:"POST",body:{registrationId:r,pin:a},auth:!1});if(!o||o.error){if(o?.reason==="email_already_registered"){hidePinStep();const u=document.getElementById("auth-error");u.textContent=o.error,u.classList.remove("hidden");return}n.textContent=o?.error||t("ui.auth.pin_error_generic"),n.classList.remove("hidden");return}pendingPin=null,setTokens(o.accessToken,o.refreshToken),currentUser=o.user,await applyServerLanguage(o.user),await completeAuthNavigation()}catch{n.textContent=t("ui.auth.pin_error_generic"),n.classList.remove("hidden")}finally{i.disabled=!1}}async function resendPin(){if(!pendingPin){hidePinStep();return}const e=document.getElementById("pin-error"),n=document.getElementById("pin-success");e.classList.add("hidden"),n.classList.add("hidden");const s=document.getElementById("pin-resend");s.disabled=!0;const r=pendingPin;try{const{email:a,password:i,name:o}=r,u=getTurnstileToken(),c=await api("/auth/register/request-pin",{method:"POST",body:{email:a,password:i,name:o,turnstileToken:u},auth:!1});if(pendingPin!==r)return;if(!c||c.error){e.textContent=c?.error||t("ui.auth.generic_error"),e.classList.remove("hidden");return}c.registrationId&&(r.registrationId=c.registrationId),n.textContent=t("ui.auth.pin_sent"),n.classList.remove("hidden")}catch{if(pendingPin!==r)return;e.textContent=t("ui.auth.generic_error"),e.classList.remove("hidden")}finally{resetTurnstile(),s.disabled=!1}}function initAppleSignIn(){const e=document.getElementById("apple-signin-btn");if(!(!e||typeof window.AppleID>"u")){try{window.AppleID.auth.init({clientId:"com.pupilbot.web",scope:"name email",redirectURI:window.location.origin+"/",usePopup:!0})}catch{}e.addEventListener("click",async()=>{const n=document.getElementById("auth-error");n.classList.add("hidden"),e.disabled=!0;try{const s=await window.AppleID.auth.signIn(),r=s?.authorization?.id_token;if(!r)throw new Error("missing_id_token");const a=await api("/auth/apple",{method:"POST",body:{identityToken:r,user:s.user},auth:!1});if(a.error){n.textContent=a.error,n.classList.remove("hidden");return}setTokens(a.accessToken,a.refreshToken),currentUser=a.user,await applyServerLanguage(a.user),await completeAuthNavigation()}catch(s){const r=s?.error;if(r==="popup_closed_by_user"||r==="user_cancelled_authorize")return;n.textContent=t("ui.auth.generic_error"),n.classList.remove("hidden")}finally{e.disabled=!1}})}}function initGoogleSignIn(){const e=document.getElementById("google-signin-btn");if(!e||!window.__GOOGLE_CLIENT_ID__)return;if(!window.google||!window.google.accounts||!window.google.accounts.id){window.onGoogleLibraryLoad=initGoogleSignIn;return}window.google.accounts.id.initialize({client_id:window.__GOOGLE_CLIENT_ID__,callback:handleGoogleCredential,auto_select:!1,ux_mode:"popup",itp_supp
6ort:!0});const n=Math.round(e.getBoundingClientRect().width)||320;window.google.accounts.id.renderButton(e,{type:"standard",theme:"outline",size:"large",text:"continue_with",shape:"pill",logo_alignment:"center",width:Math.max(200,Math.min(400,n)),locale:document.documentElement.lang||"en"})}async function handleGoogleCredential(e){const n=document.getElementById("auth-error"),s=e&&e.credential;if(s){n&&n.classList.add("hidden");try{const r=await api("/auth/google",{method:"POST",body:{credential:s},auth:!1});if(r.error){n&&(n.textContent=r.error,n.classList.remove("hidden"));return}setTokens(r.accessToken,r.refreshToken),currentUser=r.user,await applyServerLanguage(r.user),await completeAuthNavigation()}catch{n&&(n.textContent=t("ui.auth.generic_error"),n.classList.remove("hidden"))}}}function logout(){refreshToken&&api("/auth/logout",{method:"POST",body:{refreshToken}}).catch(()=>{}),clearTokens(),currentUser=null,showLanding()}let _selectedPlan="annual",_stripePrices=null;async function loadStripePrices(){if(_stripePrices)return _stripePrices;const e=await api("/api/subscriptions/stripe/prices",{auth:!1});return e?.data&&(_stripePrices=e.data),_stripePrices}function formatPrice(e){if(!e)return"";try{return new Intl.NumberFormat(currentLocale,{style:"currency",currency:e.currency}).format(e.amount)}catch{return`${e.amount} ${e.currency}`}}async function initLandingPricing(){const e=document.getElementById("landing-billing-toggle");if(!e)return;let n="annual";const s=document.getElementById("landing-premium-price"),r=document.getElementById("landing-premium-period"),a=document.getElementById("landing-premium-billing"),i=document.getElementById("landing-price-saving"),o=()=>{e.querySelectorAll("[data-billing]").forEach(d=>{d.setAttribute("aria-pressed",String(d.dataset.billing===n))});const l=_stripePrices?.[n];if(!l){s.textContent="\u2014",r.textContent="",a.textContent=t("ui.pricing.unavailable");return}s.textContent=formatPrice({...l,amount:n==="annual"?l.amount/12:l.amount}),r.textContent=t("ui.studies.per_month_short"),a.textContent=n==="annual"?t("ui.pricing.billed_year",{price:formatPrice(l)}):t("ui.pricing.billed_month")};e.querySelectorAll("[data-billing]").forEach(l=>{l.addEventListener("click",()=>{n=l.dataset.billing,o()})}),await loadStripePrices().catch(()=>{});const u=_stripePrices?.monthly,c=_stripePrices?.annual;if(u&&(document.getElementById("landing-free-price").textContent=formatPrice({...u,amount:0})),u?.amount>0&&c&&u.currency===c.currency){const l=Math.floor((1-c.amount/(u.amount*12))*100);i.textContent=l>0?t("ui.studies.save_pct",{pct:l}):""}o()}async function startStripeCheckout(){postClientEvent("checkout_started",{plan:_selectedPlan,provider:"stripe"}),showToast(t("ui.studies.upgrade_starting"),"success");const e=await api("/api/subscriptions/stripe/checkout",{method:"POST",body:{plan:_selectedPlan}});if(e?.url){window.location.href=e.url;return}showToast(e?.error||t("ui.studies.upgrade_failed"),"error")}function renderPlanToggle(){const e=_stripePrices?.monthly,n=_stripePrices?.annual;let s=null;e&&n&&e.amount>0&&n.amount<e.amount*12&&(s=Math.floor((1-n.amount/(e.amount*12))*100));const r=(a,i,o,u,c,l)=>{const d=_selectedPlan===a,m=d?"var(--color-secondary-bright)":"rgba(232, 189, 134, 0.22)",g=d?"rgba(244, 236, 224, 0.08)":"rgba(244, 236, 224, 0.03)",h=o?formatPrice(o):"\u2014";return`
7      <button type="button" data-plan="${a}"
8        class="plan-toggle-btn relative block w-full text-left p-4 rounded-[0.875rem] transition"
9        style="border: 1px solid ${m}; background: ${g};">
10        ${l?`
11          <span class="absolute -top-2 right-3 px-2 py-0.5 rounded-full font-mono text-[0.55rem] uppercase tracking-[0.15em]"
12                style="background: var(--color-secondary-bright); color: var(--color-on-secondary);">
13            ${t("ui.studies.paywall_popular")}
14          </span>
15        `:""}
16        <div class="flex items-center justify-between gap-3">
17          <div class="min-w-0">
18            <div class="font-display text-base font-semibold tracking-tight" style="color: var(--color-on-surface);">${i}</div>
19            ${c?`<div class="font-mono text-[0.65rem] uppercase tracking-[0.18em] mt-1" style="color: var(--color-secondary-bright);">${c}</div>`:""}
20          </div>
21          <div class="text-right shrink-0">
22            <div class="font-display text-lg font-semibold tracking-tight" style="color: var(--color-on-surface);">${h}</div>
23            <div class="text-xs mt-0.5" style="color: rgba(244, 236, 224, 0.6);">${t(u)}</div>
24          </div>
25        </div>
26      </button>
27    `};return`
28    <div class="plan-toggle-wrapper space-y-3 mb-5 pt-2">
29      ${r("annual",t("ui.studies.plan_annual"),n,"ui.studies.per_year_short",s?t("ui.studies.save_pct",{pct:s}):null,!0)}
30      ${r("monthly",t("ui.studies.plan_monthly"),e,"ui.studies.per_month_short",null,!1)}
31    </div>
32  `}function bindPlanToggleHandlers(e){for(const n of e.querySelectorAll(".plan-toggle-btn"))n.addEventListener("click",()=>{const s=n.dataset.plan;if(!s||s===_selectedPlan)return;_selectedPlan=s;const r=n.closest(".plan-toggle-wrapper");
32r&&(r.outerHTML=renderPlanToggle(),bindPlanToggleHandlers(e))})}async function openStripePortal(){const e=await api("/api/subscriptions/stripe/portal",{method:"POST"});if(e?.url){window.location.href=e.url;return}showToast(e?.error||t("ui.studies.upgrade_failed"),"error")}function launchConfetti(){if(window.matchMedia?.("(prefers-reduced-motion: reduce)").matches)return;const e=document.createElement("canvas");e.className="fixed inset-0 pointer-events-none",e.style.zIndex="60";const n=window.devicePixelRatio||1;e.width=window.innerWidth*n,e.height=window.innerHeight*n,e.style.width=window.innerWidth+"px",e.style.height=window.innerHeight+"px",document.body.appendChild(e);const s=e.getContext("2d");s.scale(n,n);const r=window.innerWidth,a=window.innerHeight,i=["#E8BD86","#D4A574","#B8845F","#9CB28F","#F4ECE0"],o=[];for(let d=0;d<120;d++){const m=d<60,g=(m?-Math.PI/4:-3*Math.PI/4)+(Math.random()-.5)*.9,h=9+Math.random()*7;o.push({x:m?0:r,y:a*.75,vx:Math.cos(g)*h,vy:Math.sin(g)*h,size:5+Math.random()*7,angle:Math.random()*Math.PI*2,spin:(Math.random()-.5)*.35,color:i[Math.floor(Math.random()*i.length)]})}const u=performance.now(),c=3500;function l(d){const m=d-u,g=Math.max(0,1-Math.max(0,m-c*.6)/(c*.4));s.clearRect(0,0,r,a);for(const h of o)h.x+=h.vx,h.y+=h.vy,h.vy+=.22,h.vx*=.992,h.angle+=h.spin,s.save(),s.globalAlpha=g,s.translate(h.x,h.y),s.rotate(h.angle),s.fillStyle=h.color,s.fillRect(-h.size/2,-h.size/2,h.size,h.size*.55),s.restore();m<c?requestAnimationFrame(l):e.remove()}requestAnimationFrame(l)}async function consumeStripeReturnParam(){const e=new URLSearchParams(window.location.search),n=e.get("stripe");if(!n)return;e.delete("stripe");const s=window.location.pathname+(e.toString()?`?${e}`:"");if(window.history.replaceState({},"",s),n==="cancel"){showToast(t("ui.studies.upgrade_cancelled"),"info");return}if(n==="update_payment"){accessToken&&(postClientEvent("email_click",{src:"payment_failed"}),openStripePortal());return}if(!(n!=="success"||!accessToken)){for(let r=0;r<4;r++){const a=await api("/api/subscriptions/stripe/refresh",{method:"POST"});if(a?.accessToken&&(setTokens(a.accessToken,a.refreshToken),a.tier==="premium")){launchConfetti(),showToast(t("ui.studies.upgrade_success_body"),"success");return}await new Promise(i=>setTimeout(i,750))}launchConfetti(),showToast(t("ui.studies.upgrade_success_body"),"success")}}async function consumeMaterialReturnParam(){const e=new URLSearchParams(window.location.search),n=e.get("material");if(!n)return;e.delete("material");const s=window.location.pathname+(e.toString()?`?${e}`:"");window.history.replaceState({},"",s),accessToken&&(postClientEvent("email_click",{src:"material_ready"}),setActiveTab("studies"),await showStudyDetail(n,"studies"))}function initNav(){const e=document.getElementById("mobile-nav-toggle"),n=document.getElementById("mobile-menu");if(e&&n){const s=r=>{n.classList.toggle("hidden",!r),e.setAttribute("aria-expanded",r?"true":"false"),e.setAttribute("aria-label",t(r?"ui.nav.close_menu":"ui.nav.open_menu"))};e.addEventListener("click",()=>s(n.classList.contains("hidden"))),n.querySelectorAll("a").forEach(r=>{r.addEventListener("click",()=>s(!1))}),document.addEventListener("keydown",r=>{r.key==="Escape"&&!n.classList.contains("hidden")&&(s(!1),e.focus())})}}function initFadeIn(){const e=new IntersectionObserver(n=>{n.forEach(s=>{s.isIntersecting&&s.target.classList.add("visible")})},{threshold:.1});document.querySelectorAll(".fade-in").forEach(n=>e.observe(n))}function initMascotTypewriter(){const e=document.getElementById("mascot-bubble-text");if(!e)return;const n=[];for(let o=1;o<=8;o++){const u=`ui.mascot.bubble.${o}`,c=t(u);c&&c!==u&&n.push(c)}if(n.length===0)return;if(window.matchMedia&&window.matchMedia("(prefers-reduced-motion: reduce)").matches){e.textContent=n[0];return}e.textContent="";let s=0,r=0,a=!1;const i=()=>{const o=n[s];if(a){if(r--,e.textContent=o.slice(0,r),r<=0)return a=!1,s=(s+1)%n.length,setTimeout(i,380);setTimeout(i,26)}else{if(r++,e.textContent=o.slice(0,r),r>=o.length)return a=!0,setTimeout(i,1800);setTimeout(i,55+Math.random()*35)}};setTimeout(i,500)}function initMascotGreeter(){const e=document.querySelector(".mascot-greeter");if(!e)return;const n=e.querySelector(".pb-mascot");
32n&&(n.addEventListener("click",s=>{s.stopPropagation(),e.classList.toggle("is-active")}),document.addEventListener("click",s=>{e.contains(s.target)||e.classList.remove("is-active")}))}function isSafari(){const e=navigator.userAgent;return/Safari/.test(e)&&!/Chrome|Chromium|CriOS|FxiOS|EdgiOS|Edg\/|OPR\/|OPiOS/.test(e)}function isResetPasswordRoute(){return window.location.pathname.replace(/\/+$/,"")==="/reset-password"}function showResetPassword(e){document.body.innerHTML=`
33    <main class="min-h-screen flex items-center justify-center px-6 py-16">
34      <div class="max-w-md w-full">
35        <a href="/" class="flex items-center justify-center gap-2 mb-8 font-display text-2xl font-semibold text-on-surface tracking-tight">
36          <img src="/assets/icons/icon-192.png" alt="" class="w-6 h-6" />
37          PupilBot
38        </a>
39        <div class="bg-surface-container rounded-[1.25rem] p-8 shelf-shadow">
40          <h2 class="font-display text-3xl font-semibold text-center mb-2 tracking-tight">${t("ui.auth.reset_title")}</h2>
41          <p class="text-center text-on-surface-muted mb-8">${t("ui.auth.reset_subtitle")}</p>
42          <form id="reset-form" class="space-y-5">
43            <div>
44              <label class="block text-sm font-medium mb-1.5 text-on-surface" for="reset-password">${t("ui.auth.reset_password_label")}</label>
45              <input type="password" id="reset-password" required minlength="8" placeholder="${t("ui.auth.password_placeholder")}"
46                class="w-full bg-surface-container-high rounded-[0.75rem] px-4 py-3 text-on-surface placeholder:text-on-surface-muted/60 outline-none border-b-2 border-transparent focus:border-primary transition">
47            </div>
48            <div>
49              <label class="block text-sm font-medium mb-1.5 text-on-surface" for="reset-confirm">${t("ui.auth.reset_confirm_label")}</label>
50              <input type="password" id="reset-confirm" required minlength="8" placeholder="${t("ui.auth.reset_confirm_placeholder")}"
51                class="w-full bg-surface-container-high rounded-[0.75rem] px-4 py-3 text-on-surface placeholder:text-on-surface-muted/60 outline-none border-b-2 border-transparent focus:border-primary transition">
52            </div>
53            <div id="reset-error" class="hidden text-sm text-error bg-error-container/40 rounded-[0.75rem] p-3"></div>
54            <button type="submit" id="reset-submit"
55              class="w-full bg-primary text-on-primary py-3.5 rounded-[0.875rem] font-display font-semibold text-base hover:opacity-90 transition">${t("ui.auth.reset_submit")}</button>
56          </form>
57          <div id="reset-done" class="hidden">
58            <div id="reset-done-msg" class="text-sm rounded-[0.75rem] p-3 mb-5"></div>
59            <a href="${localizePath("/login",currentLocale)}" class="block w-full text-center bg-primary text-on-primary py-3.5 rounded-[0.875rem] font-display font-semibold text-base hover:opacity-90 transition">${t("ui.auth.go_to_login")}</a>
60          </div>
61        </div>
62      </div>
63    </main>
64  `;function n(a,i){const o=document.getElementById("reset-done-msg");o.textContent=a,o.className=`text-sm rounded-[0.75rem] p-3 mb-5 ${i?"text-error bg-error-container/40":"text-primary bg-primary/10"}`,document.getElementById("reset-form").classList.add("hidden"),document.getElementById("reset-done").classList.remove("hidden")}if(!e){n(t("ui.auth.reset_invalid_link"),!0);return}const s=document.getElementById("reset-form"),r=document.getElementById("reset-error");s.addEventListener("submit",async a=>{a.preventDefault(),r.classList.add("hidden");const i=document.getElementById("reset-password").value,o=document.getElementById("reset-confirm").value;if(i!==o){r.textContent=t("ui.auth.reset_mismatch"),r.classList.remove("hidden");return}const u=document.getElementById("reset-submit");u.disabled=!0;try{const c=await api("/auth/reset-password",{method:"POST",body:{token:e,password:i},auth:!1});if(!c||c.error){r.textContent=c&&c.error||t("ui.auth.generic_error"),r.classList.remove("hidden"),u.disabled=!1;return}clearTokens(),currentUser=null,n(t("ui.auth.reset_success"),!1)}catch{r.textContent=t("ui.auth.generic_error"),r.classList.remove("hidden"),u.disabled=!1}})}let currentTab="studies";function showDashboard(){document.body.classList.add("study-paper"),document.body.innerHTML=`
65    <nav id="app-nav" class="bg-surface-container-low" style="border-bottom: 1px solid var(--color-outline-variant);">
66      <div class="max-w-3xl mx-auto px-4 sm:px-6 py-4 flex items-center justify-between gap-3">
67        <div class="flex items-center gap-2 sm:gap-3 shrink-0">
68          <a href="#" class="flex items-center gap-2 font-display text-lg sm:text-2xl font-semibold text-on-surface tracking-tight" id="brand-link">
69            <img src="/assets/icons/icon-192.png" alt="" class="w-5 h-5 sm:w-6 sm:h-6" />
70            PupilBot
71          </a>
72        </div>
73        <div class="hidden sm:flex items-center gap-3 sm:gap-4 text-xs sm:text-sm">
74          <button id="tab-home" class="cursor-pointer text-on-surface font-medium transition">${t("ui.dashboard.home")}</button>
75          <button id="tab-studies" class="cursor-pointer text-on-surface-muted hover:text-on-surface font-medium transition">${t("ui.dashboard.studies")}</button>
76          <button id="tab-connect" class="cursor-pointer text-on-surface-muted hover:text-on-surface font-medium transition">${t("ui.dashboard.connect")}</button>
77          <button id="tab-settings" class="cursor-pointer text-on-surface-muted hover:text-on-surface font-medium transition">${t("ui.dashboard.settings")}</button>
78          <button id="logout-btn" class="cursor-pointer text-error/80 hover:text-error font-medium transition">${t("ui.dashboard.logout")}</button>
79        </div>
80        <button id="dashboard-menu-toggle" class="sm:hidden p-2 -mr-2 text-on-surface" aria-label="${t("ui.nav.open_menu")}" aria-expanded="false" aria-controls="dashboard-menu">
81          <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
82            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16"/>
83          </svg>
84        </button>
85      </div>
86      <div id="dashboard-menu" class="hidden sm:hidden px-3 pt-2 pb-3 flex flex-col gap-1" style="border-top: 1px solid var(--color-outline-variant);">
87        <button id="m-tab-home" class="block w-full text-left px-4 py-3.5 rounded-lg text-base text-on-surface font-medium active:bg-white/5">${t("ui.dashboard.home")}</button>
88        <button id="m-tab-studies" class="block w-full text-left px-4 py-3.5 rounded-lg text-base text-on-surface-muted font-medium active:bg-white/5">${t("ui.dashboard.studies")}</button>
89        <button id="m-tab-connect" class="block w-full text-left px-4 py-3.5 rounded-lg text-base text-on-surface-muted font-medium active:bg-white/5">${t("ui.dashboard.connect")}</button>
90        <button id="m-tab-settings" class="block w-full text-left px-4 py-3.5 rounded-lg text-base text-on-surface-muted font-medium active:bg-white/5">${t("ui.dashboard.settings")}</button>
91        <button id="m-logout-btn" class="block w-full text-left px-4 py-3.5 rounded-lg text-base text-error/80 font-medium active:bg-error/10">${t("ui.dashboard.logout")}</button>
92      </div>
93    </nav>
94    <main id="dashboard-content" class="max-w-3xl mx-auto px-6 py-8"></main>
95    <footer id="app-footer" class="max-w-3xl mx-auto px-6 py-8 flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-xs text-on-surface-muted">
96      <!-- Privacy + Terms live in the landing footer only; the signed-in footer keeps Support centered. -->
97      <a href="/support" class="hover:text-on-surface transition">${t("ui.footer.support")}</a>
98      ${isSafari()?`<a href="https://apps.apple.com/app/pupilbot-scan-notes-to-quiz/id6768584919" target="_blank" rel="noopener" class="text-secondary hover:text-on-surface transition">${t("ui.footer.ios_coming_soon")}</a>`:""}
99    </footer>
100  `,document.getElementById("brand-link").addEventListener("click",a=>{a.preventDefault(),showHome()}),document.getElementById("tab-home").addEventListener("click",showHome),document.getElementById("tab-studies").addEventListener("click",showStudies),document.getElementById("tab-connect").addEventListener("click",showConnect),document.getElementById("tab-settings").addEventListener("click",showSettings),document.getElementById("logout-btn").addEventListener("click",logout);const e=document.getElementById("dashboard-menu"),n=document.getElementById("dashboard-menu-toggle"),s=()=>{e?.classList.add("hidden"),n?.setAttribute("aria-expanded","false"),n?.setAttribute("aria-label",t("ui.nav.open_menu"))};n?.addEventListener("click",a=>{a.stopPropagation();const i=e?.classList.toggle("hidden")===!1;n.setAttribute("aria-expanded",i?"true":"false"),n.setAttribute("aria-label",t(i?"ui.nav.close_menu":"ui.nav.open_menu"))}),document.getElementById("app-nav")?.addEventListener("keydown",a=>{a.key==="Escape"&&e&&!e.classList.contains("hidden")&&(s(),n?.focu
100s())}),e?.addEventListener("click",a=>a.stopPropagation());const r=(a,i)=>{document.getElementById(a)?.addEventListener("click",()=>{s(),i()})};r("m-tab-home",showHome),r("m-tab-studies",showStudies),r("m-tab-connect",showConnect),r("m-tab-settings",showSettings),r("m-logout-btn",logout),showHome()}function setShellSurface(e){document.body.classList.toggle("reading",e==="reading")}function setActiveTab(e){postScreenView(e),currentTab=e,setShellSurface("paper");for(const n of["tab-home","tab-studies","tab-connect","tab-settings"]){const s=document.getElementById(n);if(!s)continue;const r=n===`tab-${e}`;s.className=r?"text-on-surface font-medium transition":"text-on-surface-muted hover:text-on-surface font-medium transition"}for(const n of["m-tab-home","m-tab-studies","m-tab-connect","m-tab-settings"]){const s=document.getElementById(n);if(!s)continue;const r=n===`m-tab-${e}`;s.className=r?"block w-full text-left px-4 py-3.5 rounded-lg text-base text-on-surface font-medium bg-white/5 active:bg-white/10":"block w-full text-left px-4 py-3.5 rounded-lg text-base text-on-surface-muted font-medium active:bg-white/5"}}function freeSlotUsed(e){return!e||e.isPremium?!1:(e.lifetimeStudiesUploaded??0)>=1}function renderManageSubscriptionLink(e,n="text-xs text-on-surface-muted hover:text-on-surface underline transition mb-6 inline-block"){if(!e?.isPremium)return"";const s=renderRenewalLine(e);if(e.subscriptionProvider==="stripe"){const r=t("ui.studies.manage_subscription");return`<button id="open-stripe-portal" class="${n}">${r}</button>${s}`}return`<p class="text-sm text-on-surface-muted">${t("ui.settings.premium_apple_note")}</p>${s}`}function renderRenewalLine(e){if(!e?.subscriptionExpiresAt)return"";const n=new Date(e.subscriptionExpiresAt);if(Number.isNaN(n.getTime()))return"";const s=new Intl.DateTimeFormat(currentLocale,{month:"short",day:"numeric",year:"numeric"}).format(n),r=e.subscriptionAutoRenew?"ui.settings.renews_on":"ui.settings.ends_on";return`<p class="text-sm text-on-surface-muted mt-2">${t(r,{date:s})}</p>`}let _studiesMaterials=[],_studiesMastery=new Map,_studiesFilter="all",_openStudyMenu=null,_connectHelpOpen=!1,_pendingLinkCode=null,_supportUnread=0;function applySupportBadge(){const e=document.getElementById("home-support-badge");e&&(e.textContent=_supportUnread>99?"99+":String(_supportUnread),e.classList.toggle("hidden",_supportUnread===0),e.classList.toggle("inline-flex",_supportUnread>0))}async function refreshSupportUnread(){const e=await api("/api/support/unread").catch(()=>null);e&&!e.error&&typeof e.data?.count=="number"&&(_supportUnread=e.data.count),applySupportBadge()}const _rowSigs=new Map;function processingFraction(e,n){const o=n||{};switch(e){case"refining":return .45;case"topics":return .65;case"questions":return .75+countFraction(o.topics_done,o.topics_total)*(.95-.75);case"ready":return 1;case"failed":return .45;default:return countFraction(o.pages_done,o.pages_total)*.45}}function countFraction(e,n){return!n||n<=0?0:Math.min(Math.max(0,e||0)/n,1)}function processingDetail(e,n){const s=n||{};switch(e){case"refining":return t("ui.studies.progress_refining");case"topics":return t("ui.studies.progress_topics");case"questions":return s.topics_total>0?t("ui.studies.progress_questions",{done:s.topics_done||0,total:s.topics_total}):t("ui.studies.progress_questions_starting");case"ready":return t("ui.studies.progress_almost_done");case"failed":return"";default:return s.pages_total>0?t("ui.studies.progress_pages",{done:s.pages_done||0,total:s.pages_total}):t("ui.studies.progress_pages_starting")}}function studyRowSignature(e){return[e.status,e.processing_stage||"",JSON.stringify(e.processing_progress||{}),e.title,e.is_focused?1:0,e.page_count==null?"?":e.page_count,_openStudyMenu===e.id?1:0].join("|")}async function showStudies(){document.body.classList.remove("capture-mode"),document.getElementById("app-nav")?.classList.remove("hidden"),_studiesFilter="all",setActiveTab("studies");const e=document.getElementById("dashboard-content");e?.classList.remove("py-4"),e?.classList.add("py-8"),e.innerHTML=`<p class="text-on-surface-muted">${t("ui.studies.title")}\u2026</p>`;const n=await api("/api/users/me").catch(()=>null);n&&!n.error&&(currentUser=n);const[s,r]=await Promise.all([api("/api/materials"),api("/api/mastery").catch(()=>null)]);_studiesMaterials=s?.data||[],_studiesMastery=new Map;for(const o of r?.data||[])_studiesMastery.set(o.material_id,o);const a=freeSlotUsed(currentUser),i=_pendingPaywallSource||"studies_tab";
100_pendingPaywallSource=null,a&&!_stripePrices&&await loadStripePrices().catch(()=>{}),a&&_stripePrices&&postClientEvent("paywall_viewed",{source:i}),e.innerHTML=`
101    <div class="flex items-end justify-between mb-6">
102      <div>
103        <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.studies.eyebrow")}</span>
104        <h1 class="font-display text-3xl font-semibold tracking-tight mt-1">${t("ui.studies.title")}</h1>
105      </div>
106      ${a?"":`
107        <button id="new-study-btn" class="bg-primary text-on-primary px-5 py-2.5 rounded-[0.875rem] font-medium hover:opacity-90 transition">
108          ${t("ui.studies.new")}
109        </button>
110      `}
111    </div>
112
113    ${a?"":renderStudyPlanNotice()}
114
115    ${a||_studiesMaterials.length===0?"":`
116      <p class="text-on-surface-muted text-sm mb-6">${t("ui.studies.tap_to_open")}</p>
117    `}
118
119    ${_studiesMaterials.length===0?`
120        <div class="flex flex-col items-center justify-center text-center py-12 px-4">
121          <p class="text-on-surface-muted text-base max-w-xs leading-relaxed">${t("ui.studies.empty")}</p>
122        </div>
123      `:`<div id="studies-list" class="space-y-3">${visibleStudies().map(o=>renderStudyRow(o)).join("")}</div>`}
124
125    ${a?`
126      <div class="paywall-hero hero-leather rounded-[1.25rem] p-6 mt-6 shelf-shadow"
127           style="border: 1px solid rgba(232, 189, 134, 0.35);">
128        <div class="flex items-center gap-2 mb-3">
129          <svg class="w-4 h-4" style="color: var(--color-secondary-bright);" fill="currentColor" viewBox="0 0 20 20">
130            <path d="M10 1.5l1.6 4.9 5.1.1-4.1 3 1.5 4.9-4.1-3-4.1 3 1.5-4.9-4.1-3 5.1-.1z"/>
131          </svg>
132          <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em]" style="color: var(--color-secondary-bright);">${t("ui.studies.paywall_eyebrow")}</span>
133        </div>
134        <h3 class="font-display text-2xl font-semibold tracking-tight mb-2" style="color: var(--color-on-surface);">${t("ui.studies.paywall_headline")}</h3>
135        <p class="text-sm leading-relaxed mb-5" style="color: rgba(244, 236, 224, 0.78);">${t("ui.studies.paywall_subhead")}</p>
136        <ul class="space-y-2.5 mb-5">
137          <li class="flex items-start gap-2.5">
138            <svg class="w-4 h-4 mt-0.5 shrink-0" style="color: var(--color-secondary-bright);" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M4 10.5l4 4 8-9"/></svg>
139            <span class="text-sm leading-relaxed" style="color: var(--color-on-surface);">${t("ui.studies.paywall_benefit_capture")}</span>
140          </li>
141          <li class="text-sm leading-relaxed">${t("ui.plan.pages_premium")}</li>
142        </ul>
143        <p class="text-xs text-on-surface-muted mb-4">${t("ui.plan.reset")}</p>
144        ${renderPlanToggle()}
145        <button id="open-stripe-checkout"
146                class="w-full px-5 py-3.5 rounded-[0.875rem] font-medium hover:opacity-90 transition inline-flex items-center justify-center gap-2"
147                style="background: var(--color-secondary-bright); color: var(--color-on-secondary);">
148          ${t("ui.studies.upgrade")}
149          <svg class="w-4 h-4" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="2.5"><path stroke-linecap="round" stroke-linejoin="round" d="M5 10h10m0 0l-4-4m4 4l-4 4"/></svg>
150        </button>
151        <div class="mt-3 flex flex-wrap items-center justify-center gap-x-3 gap-y-1 text-xs" style="color: rgba(244, 236, 224, 0.65);">
152          <span class="inline-flex items-center gap-1.5">
153            <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M6 9V6.5a4 4 0 118 0V9M5 9h10a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6a1 1 0 011-1z"/></svg>
154            ${t("ui.studies.secure_checkout")}
155          </span>
156          <span aria-hidden="true" style="color: rgba(244, 236, 224, 0.35);">\xB7</span>
157          <span class="inline-flex items-center gap-1.5">
158            <svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="1.8"><path stroke-linecap="round" stroke-linejoin="round" d="M4 10a6 6 0 0110.5-4M16 10a6 6 0 01-10.5 4M16 4v3.5h-3.5M4 16v-3.5h3.5"/></svg>
159            ${t("ui.studies.cancel_anytime")}
160          </span>
161        </div>
162        <p class="mt-4 pt-4 text-xs leading-relaxed" style="color: rgba(244, 236, 224, 0.55); border-top: 1px solid rgba(232, 189, 134, 0.15);">${t("ui.studies.paywall_footnote")}</p>
163      </div>
164    `:""}
165  `,document.getElementById("new-study-btn")?.addEventListener("click",()=>startCapture("studies")),document.getElementById("open-connect")?.addEventListener("click",
165showConnect),document.getElementById("open-stripe-checkout")?.addEventListener("click",startStripeCheckout),bindPlanToggleHandlers(e),bindStudyRowHandlers(e),refreshRowSignatures(),_studiesMaterials.some(o=>o.status==="processing"||o.status==="uploaded")?startStudiesPolling():stopStudiesPolling()}function renderStudyRow(e){const n=e.page_count==null?t("ui.studies.pages_unknown"):t("ui.studies.pages",{count:e.page_count}),s={ready:t("ui.studies.ready"),processing:t("ui.studies.processing"),uploaded:t("ui.studies.queued"),failed:t("ui.studies.failed")}[e.status]||escapeHtml(e.status),r={ready:"text-tertiary-fixed",processing:"text-secondary",uploaded:"text-on-surface-muted",failed:"text-error"}[e.status]||"text-on-surface-muted",a=!!e.is_focused,i=_studiesMastery.get(e.id),o=i&&i.total_attempts>0,u=o?Math.round(i.mastery_score*100):null,c=e.status==="processing"||e.status==="uploaded",l=e.status==="ready",d=l&&!o,m=_openStudyMenu===e.id,g=failedPagesOf(e),h=unreadablePagesOf(e),C=c?Math.round(processingFraction(e.processing_stage,e.processing_progress)*100):0,I=c?processingDetail(e.processing_stage,e.processing_progress):"",L=l,y=t("ui.studies.open_study"),_=L?'role="button" tabindex="0" data-card-action="open"':"",w=L?"cursor-pointer hover:bg-surface-container-high transition-colors":"";return`
166    <div class="relative">
167      <div data-row="${e.id}" ${_}
168           aria-label="${L?escapeHtml(y):""}"
169           class="${a?"bg-surface-container-high":"bg-surface-container"} ${w} rounded-[1rem] p-5 pl-7 relative overflow-hidden">
170        <div class="study-spine ${a?"study-spine-current":""}" aria-hidden="true"></div>
171        <div class="flex items-start justify-between gap-3">
172          <div class="min-w-0 flex-1" data-title-host="${e.id}">
173            <h3 data-title="${e.id}" class="font-display text-lg font-semibold tracking-tight truncate">${escapeHtml(e.title)}</h3>
174            <div class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-1">
175              ${a?`<span class="inline-flex items-center gap-1 text-secondary"><svg class="w-3 h-3" fill="currentColor" viewBox="0 0 20 20"><path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-3-5 3V4z"/></svg>${t("ui.studies.current")}</span><span class="text-on-surface-muted/40">\xB7</span>`:""}
176              <span>${n}</span>
177              ${c?"":`
178                <span class="text-on-surface-muted/40">\xB7</span>
179                <span class="${r}">${s}</span>
180              `}
181            </div>
182          </div>
183          <button data-menu="${e.id}" aria-label="${t("ui.studies.more_actions")}" aria-haspopup="true" aria-expanded="${m?"true":"false"}"
184                  class="text-on-surface hover:text-on-surface transition shrink-0 p-2 rounded-full bg-surface-container-highest hover:bg-surface-container-high"
185                  style="border: 1px solid var(--color-outline-variant);">
186            <svg class="w-5 h-5" fill="currentColor" viewBox="0 0 24 24"><circle cx="12" cy="5" r="1.75"/><circle cx="12" cy="12" r="1.75"/><circle cx="12" cy="19" r="1.75"/></svg>
187          </button>
188        </div>
189
190        ${c?`
191          <div class="mt-4 -mx-2 px-3 py-2.5 rounded-[0.625rem] bg-surface-container-highest/60" style="border: 1px solid var(--color-outline-variant);">
192            <div class="flex items-center justify-between text-xs font-medium mb-2 gap-3">
193              <span class="inline-flex items-center gap-2 text-secondary min-w-0">
194                <svg class="w-3.5 h-3.5 animate-spin shrink-0" fill="none" viewBox="0 0 24 24" aria-hidden="true">
195                  <circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.5" stroke-opacity="0.25"/>
196                  <path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/>
197                </svg>
198                <span class="truncate">${escapeHtml(I)}</span>
199              </span>
200              <span class="text-on-surface tabular-nums shrink-0 font-mono">${C}%</span>
201            </div>
202            <div class="h-2 rounded-full bg-surface-container overflow-hidden">
203              <div class="h-full bg-secondary rounded-full transition-all" style="width: ${C}%"></div>
204            </div>
205          </div>
206        `:o?`
207          <div class="mt-4">
208            <div class="flex items-center justify-between font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mb-1.5">
209              <span>${t("ui.studies.mastery")}</span>
210              <span class="text-on-surface tabular-nums">${u}%</span>
211            </div>
212            <div class="h-1 rounded-full bg-surface-container-highest overflow-hidden">
213              <div class="h-full bg-primary rounded-full transition-all" style="width: ${u}%"></div>
214            </div>
215          </div>
216        `:d?`
217          <p class="mt-3 font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted/70">${t("ui.studies.not_started")}</p>
218        `:""}
219        ${l&&g.length>0?`
220          <p class="mt-2 font-mono text-[0.65rem] uppercase tracking-wider text-error/80">${t("ui.studies.pages_unread",{count:g.length})}</p>
221        `:""}
222        ${l&&h.length>0?`
223          <p class="mt-2 font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted">${t("ui.studies.pages_left_out",{count:h.length})}</p>
224        `:""}
225        ${e.status==="failed"?`
226          <div class="mt-4 space-y-3">
227            <p class="text-sm text-on-surface leading-relaxed">${escapeHtml(failureMessage(e))}</p>
228            ${suggestsClearerPages(e)?`
229              <p class="text-sm text-on-surface-muted leading-relaxed">${escapeHtml(t("ui.studies.clearer_pages_hint"))}</p>
230              <button data-action="upload-clearer" data-id="${e.id}" class="bg-primary text-on-primary px-4 py-3 rounded-[0.75rem] text-sm font-medium hover:opacity-90 transition">${t("ui.studies.upload_clearer")}</button>
231              <p class="text-xs text-on-surface-muted">${escapeHtml(t("ui.studies.clearer_new_study"))}</p>
232            `:""}
233            <p class="text-xs text-on-surface-muted leading-relaxed">${escapeHtml(t("ui.studies.retry_saved_hint"))}</p>
234            <button data-action="retry" data-id="${e.id}" ${_retryingMaterials.has(e.id)?'disabled aria-busy="true"':""} class="bg-surface-container-high text-on-surface px-4 py-3 rounded-[0.75rem] text-sm font-medium hover:opacity-90 disabled:opacity-50 transition">${t(_retryingMaterials.has(e.id)?"ui.studies.processing":"ui.studies.retry_saved")}</button>
235          </div>
236        `:""}
237
238      </div>
239      ${m?renderStudyMenu(e,{isReady:l,isCurrent:a,isProcessing:c}):""}
240    </div>
241  `}const _retryingMaterials=new Set;function suggestsClearerPages(e){const n=e?.processing_progress?.failure_reason;return n==="no_text"||n==="no_topics"}function failureMessage(e){const n=e?.processing_progress?.failure_reason;return t(n==="no_text"?"ui.studies.failed_no_text":n==="no_topics"?"ui.studies.failed_no_topics":n==="no_questions"?"ui.studies.failed_no_questions":n==="too_few_questions"?"ui.studies.failed_too_few_questions":"ui.studies.failed_generic")}
241function renderStudyMenu(e,{isReady:n,isCurrent:s,isProcessing:r}){const a=!n,i=a?"w-full text-left px-4 py-2.5 text-sm text-on-surface-muted opacity-50 cursor-not-allowed flex items-center gap-2":"w-full text-left px-4 py-2.5 text-sm text-on-surface hover:bg-surface-container-high transition flex items-center gap-2",o=!!r,u=o?"w-full text-left px-4 py-2.5 text-sm text-on-surface-muted opacity-50 cursor-not-allowed flex items-center gap-2":"w-full text-left px-4 py-2.5 text-sm text-on-surface hover:bg-surface-container-high transition flex items-center gap-2";return`
242    <div data-menu-panel="${e.id}" role="menu"
243         class="absolute right-3 top-12 z-10 min-w-[12rem] bg-surface-container-highest rounded-[0.75rem] py-1 shelf-shadow"
244         style="border: 1px solid var(--color-outline-variant);">
245      <button ${a?'disabled aria-disabled="true"':`data-action="toggle-current" data-id="${e.id}" data-focused="${s?"false":"true"}"`} role="menuitem"
246              class="${i}">
247        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"/></svg>
248        ${t(s?"ui.studies.remove_from_current":"ui.studies.add_to_current")}
249      </button>
250      <button ${o?'disabled aria-disabled="true"':`data-action="rename" data-id="${e.id}"`} role="menuitem"
251              class="${u}">
252        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z"/></svg>
253        ${t("ui.studies.rename")}
254      </button>
255      <button data-action="delete" data-id="${e.id}" role="menuitem"
256              class="w-full text-left px-4 py-2.5 text-sm text-error hover:bg-error-container/40 transition flex items-center gap-2">
257        <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6M1 7h22M9 7V4a1 1 0 011-1h4a1 1 0 011 1v3"/></svg>
258        ${t("ui.studies.delete")}
259      </button>
260    </div>
261  `}function bindStudyRowHandlers(e){e.querySelectorAll("[data-menu]").forEach(r=>{r.addEventListener("click",a=>{a.stopPropagation(),toggleStudyMenu(r.getAttribute("data-menu"))})}),e.querySelectorAll('[data-action="toggle-current"]').forEach(r=>{r.addEventListener("click",a=>{a.stopPropagation(),closeStudyMenu(),toggleStudyCurrent(r.getAttribute("data-id"),r.getAttribute("data-focused")==="true")})}),e.querySelectorAll('[data-action="rename"]').forEach(r=>{r.addEventListener("click",a=>{a.stopPropagation(),closeStudyMenu(),beginRenameStudy(r.getAttribute("data-id"))})}),e.querySelectorAll('[data-action="delete"]').forEach(r=>{r.addEventListener("click",a=>{a.stopPropagation(),closeStudyMenu(),deleteStudy(r.getAttribute("data-id"))})}),e.querySelectorAll('[data-action="upload-clearer"]').forEach(r=>{r.addEventListener("click",a=>{a.stopPropagation(),startCapture(currentTab)})}),e.querySelectorAll('[data-action="retry"]').forEach(r=>{r.addEventListener("click",a=>{a.stopPropagation(),r.disabled=!0,retryMaterialProcessing(r.getAttribute("data-id"))})});const n='[data-card-action="open"]';(e.matches?.(n)?[e]:e.querySelectorAll(n)).forEach(r=>{const a=()=>{_openStudyMenu===null&&handleStudyCardClick(r.getAttribute("data-row"))};r.addEventListener("click",a),r.addEventListener("keydown",i=>{(i.key==="Enter"||i.key===" ")&&(i.preventDefault(),a())})})}function handleStudyCardClick(e){_studiesMaterials.find(s=>s.id===e)&&showStudyDetail(e,currentTab)}function toggleStudyMenu(e){_openStudyMenu=_openStudyMenu===e?null:e,repaintStudiesList()}function closeStudyMenu(){_openStudyMenu!==null&&(_openStudyMenu=null,repaintStudiesList())}document.addEventListener("click",()=>{_openStudyMenu!==null&&closeStudyMenu()}),document.addEventListener("keydown",e=>{e.key==="Escape"&&_openStudyMenu!==null&&closeStudyMenu()});function closeDashboardMobileMenu(){const e=document.getElementById("dashboard-menu"),n=document.getElementById("dashboard-menu-toggle");!e||e.classList.contains("hidden")||(e.classList.add("hidden"),n?.setAttribute("aria-expanded","fal
261se"))}document.addEventListener("click",closeDashboardMobileMenu),document.addEventListener("keydown",e=>{e.key==="Escape"&&closeDashboardMobileMenu()});function visibleStudies(){return _studiesFilter==="focused"?_studiesMaterials.filter(e=>e.is_focused):_studiesMaterials}function repaintStudiesList(){if(currentTab!=="studies"&&currentTab!=="home")return;const e=document.getElementById("studies-list");e&&(e.innerHTML=visibleStudies().map(n=>renderStudyRow(n)).join(""),bindStudyRowHandlers(e),refreshRowSignatures())}function refreshRowSignatures(){_rowSigs.clear();for(const e of _studiesMaterials)_rowSigs.set(e.id,studyRowSignature(e))}function updateStudyRow(e){const n=studyRowSignature(e);if(_rowSigs.get(e.id)===n)return;const s=document.querySelector(`[data-row="${e.id}"]`);if(!s)return;const r=document.createElement("div");r.innerHTML=renderStudyRow(e);const a=r.querySelector(`[data-row="${e.id}"]`);a&&(s.replaceWith(a),bindStudyRowHandlers(a),_rowSigs.set(e.id,n))}async function toggleStudyCurrent(e,n){const s=_studiesMaterials.map(i=>i.is_focused),r=_studiesMaterials.find(i=>i.id===e);r&&(r.is_focused=n),repaintStudiesList();const a=await api(`/api/materials/${e}/focus`,{method:"PATCH",body:{focused:n,exclusive:!1}}).catch(()=>null);return!a||a.error?(_studiesMaterials.forEach((i,o)=>{i.is_focused=s[o]}),repaintStudiesList(),showToast(t("ui.studies.set_current_failed")),!1):!0}function beginRenameStudy(e){const n=document.querySelector(`[data-title="${e}"]`),s=document.querySelector(`[data-title-host="${e}"]`);if(!n||!s)return;const r=_studiesMaterials.find(c=>c.id===e)?.title||n.textContent,a=document.createElement("div");a.className="flex items-center gap-2",a.innerHTML=`
262    <input type="text" value="${escapeHtml(r)}" maxlength="200"
263           class="flex-1 bg-surface-container-high rounded-[0.5rem] px-3 py-1.5 text-on-surface font-display text-lg font-semibold tracking-tight outline-none border-b-2 border-transparent focus:border-primary transition" />
264    <button data-rename-save class="text-sm px-3 py-1.5 rounded-[0.5rem] bg-primary text-on-primary font-medium hover:opacity-90 transition">${t("ui.studies.rename_save")}</button>
265    <button data-rename-cancel class="text-sm px-3 py-1.5 rounded-[0.5rem] bg-surface-container-high text-on-surface hover:bg-surface-container-highest transition">${t("ui.studies.rename_cancel")}</button>
266  `,n.replaceWith(a);const i=a.querySelector("input");i.focus(),i.select();const o=()=>repaintStudiesList(),u=async()=>{const c=(i.value||"").trim();if(!c||c===r){o();return}a.querySelectorAll("button, input").forEach(g=>{g.disabled=!0});const l=await api(`/api/materials/${e}`,{method:"PATCH",body:{title:c}}).catch(()=>null);if(!l||l.error){a.querySelectorAll("button, input").forEach(g=>{g.disabled=!1}),showToast(t("ui.studies.rename_failed"));return}const d=l.data?.title||c,m=_studiesMaterials.find(g=>g.id===e);m&&(m.title=d),repaintStudiesList()};a.querySelector("[data-rename-save]").addEventListener("click",u),a.querySelector("[data-rename-cancel]").addEventListener("click",o),i.addEventListener("keydown",c=>{c.key==="Enter"?(c.preventDefault(),u()):c.key==="Escape"&&(c.preventDefault(),o())}),a.addEventListener("click",c=>c.stopPropagation())}async function deleteStudy(e){let n=t("ui.studies.delete_confirm");if(!currentUser?.isPremium&&_studiesMaterials.length===1&&(n+=`
267
268`+t("ui.studies.delete_confirm_free_last")),!confirm(n))return;const s=await api(`/api/materials/${e}`,{method:"DELETE"}).catch(()=>null);if(!s||s.error){showToast(t("ui.studies.delete_failed"));return}currentTab==="home"?showHome():showStudies()}let _studiesPoll=null;function startStudiesPolling(){stopStudiesPolling(),_studiesPoll=setInterval(async()=>{if(currentTab!=="studies"&&currentTab!=="home")return;const n=(await api("/api/materials").catch(()=>null))?.data||[];if(currentTab!=="studies"&&currentTab!=="home"||_openStudyMenu!==null||document.activeElement?.tagName==="INPUT"&&document.activeElement.closest("[data-row]"))return;const s=_studiesMaterials.map(a=>a.id).join(","),r=n.map(a=>a.id).join(",");if(_studiesMaterials=n,s!==r)repaintStudiesList();else for(const a of n)updateStudyRow(a);n.some(a=>a.status==="processing"||a.status==="uploaded")||stopStudiesPolling()},5e3)}function stopStudiesPolling(){_studiesPoll&&(clearInterval(_studiesPoll),_studiesPoll=null)}function spinnerSvg(e){return`<svg class="${e} animate-spin" fill="none" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="2.5" stroke-opacity="0.25"/><path d="M21 12a9 9 0 0 0-9-9" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"/></svg>`}function chevronRightSvg(e){return`<svg class="${e}" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"/></svg>`}function flameSvg(e){return`<svg class="${e}" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M13.5.67s.74 2.65.74 4.8c0 2.06-1.35 3.73-3.41 3.73-2.07 0-3.63-1.67-3.63-3.73l.03-.36C5.21 7.51 4 10.62 4 14c0 4.42 3.58 8 8 8s8-3.58 8-8C20 8.61 17.41 3.8 13.5.67zM11.71 19c-1.78 0-3.22-1.4-3.22-3.14 0-1.62 1.05-2.76 2.81-3.12 1.77-.36 3.6-1.21 4.62-2.58.39 1.29.59 2.65.59 4.04 0 2.65-2.15 4.8-4.8 4.8z"/></svg>`}function micSvg(e){return`<svg class="${e}" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 14a3 3 0 003-3V5a3 3 0 00-6 0v6a3 3 0 003 3z"/><path d="M17 11a5 5 0 01-10 0H5a7 7 0 006 6.92V21h2v-3.08A7 7 0 0019 11h-2z"/></svg>`}function stopSvg(e){return`<svg class="${e}" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><rect x="6" y="6" width="12" height="12" rx="2"/></svg>`}function sparklesSvg(e){return`<svg class="${e}" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2l1.7 4.8L18.5 8.5l-4.8 1.7L12 15l-1.7-4.8L5.5 8.5l4.8-1.7L12 2zM19 13l.9 2.6L22.5 16.5l-2.6.9L19 20l-.9-2.6-2.6-.9 2.6-.9L19 
26813zM6 15l.8 2.2L9 18l-2.2.8L6 21l-.8-2.2L3 18l2.2-.8L6 15z"/></svg>`}function flipSvg(e){return`<svg class="${e}" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h5M20 20v-5h-5M4 9a8 8 0 0114-3M20 15a8 8 0 01-14 3"/></svg>`}function flagSvg(e,n){return n?`<svg class="${e}" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 3v18h2v-7h11l-2.5-4L18 6H7V3H5z"/></svg>`:`<svg class="${e}" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 21v-7m0 0V4.5A1.5 1.5 0 017.5 3H18l-2.5 4L18 11H7.5A1.5 1.5 0 016 12.5V14z"/></svg>`}function bookmarkSvg(e){return`<svg class="${e}" fill="currentColor" viewBox="0 0 20 20" aria-hidden="true"><path d="M5 4a2 2 0 012-2h6a2 2 0 012 2v14l-5-3-5 3V4z"/></svg>`}function masteryBandColor(e){return e>=.8?"var(--color-tertiary)":e>=.4?"var(--color-primary)":"var(--color-secondary)"}async function showHome(){document.body.classList.remove("capture-mode"),document.getElementById("app-nav")?.classList.remove("hidden"),_studiesFilter="focused",setActiveTab("home");const e=document.getElementById("dashboard-content");e.innerHTML=`<p class="text-on-surface-muted">${t("ui.dashboard.home")}\u2026</p>`;const[n,s,r,a]=await Promise.all([api("/api/users/me").catch(()=>null),api("/api/materials"),api("/api/mastery").catch(()=>null),api("/api/users/me/streak").catch(()=>null)]);if(n&&!n.error&&(currentUser=n),currentTab!=="home")return;_studiesMaterials=s?.data||[],_studiesMastery=new Map;for(const m of r?.data||[])_studiesMastery.set(m.material_id,m);const i=Number(a?.data?.currentStreak)||0,o=(currentUser?.name||"").trim()||t("ui.home.there"),u=_studiesMaterials.some(m=>m.status==="ready"),c=r?.data||[],l=c.reduce((m,g)=>m+(Number(g.total_attempts)||0),0),d=visibleStudies();e.innerHTML=`
269    <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.home.eyebrow")}</span>
270    <div class="flex items-start justify-between gap-3 mt-1">
271      <h1 class="font-display text-3xl sm:text-4xl font-semibold tracking-tight leading-tight min-w-0">
272        <span class="block text-on-surface-muted text-lg font-normal">${t("ui.home.welcome")}</span>
273        <span class="block truncate">${escapeHtml(o)}</span>
274      </h1>
275      ${i>0?`
276        <span class="inline-flex items-center gap-1.5 shrink-0 px-3 py-1.5 rounded-full bg-secondary-container mt-2">
277          ${flameSvg("w-4 h-4 lamp-glow")}
278          <span class="font-mono text-[0.7rem] uppercase tracking-wider" style="color: var(--color-secondary-bright);">${t("ui.home.streak",{count:i})}</span>
279        </span>
280      `:""}
281    </div>
282    <p class="text-on-surface-muted text-sm mt-2 mb-8">${t(u?"ui.home.subtitle_ready":"ui.home.subtitle_empty")}</p>
283
284    <button id="home-new-study" class="hero-leather w-full text-left rounded-[1.25rem] p-6 shelf-shadow flex items-center gap-4 hover:opacity-95 transition mb-6">
285      <span class="w-12 h-12 rounded-full bg-black/25 flex items-center justify-center shrink-0" style="color: var(--color-on-surface);">
286        <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
287          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
288          <circle cx="12" cy="13" r="3.5" stroke-width="2"/>
289        </svg>
290      </span>
291      <span class="flex-1 min-w-0">
292        <span class="block font-display text-xl font-semibold tracking-tight" style="color: var(--color-on-surface);">${t("ui.home.new_study_title")}</span>
293        <span class="block text-sm mt-0.5" style="color: rgba(244, 236, 224, 0.75);">${t("ui.home.new_study_sub")}</span>
294      </span>
295      ${chevronRightSvg("w-5 h-5 shrink-0 opacity-70")}
296    </button>
297
298    ${l>0?`
299      <div class="grid grid-cols-2 gap-3 mb-8">
300        <div class="bg-surface-container rounded-[1rem] p-4">
301          <div class="font-display text-2xl font-semibold tabular-nums">${c.length}</div>
302          <div class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mt-1">${t("ui.home.stat_studies")}</div>
303        </div>
304        <div class="bg-surface-container rounded-[1rem] p-4">
305          <div class="font-display text-2xl font-semibold tabular-nums">${l}</div>
306          <div class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mt-1">${t("ui.home.stat_answered")}</div>
307        </div>
308      </div>
309    `:""}
310
311    <div class="flex items-end justify-between mb-3">
312      <h2 class="font-display text-xl font-semibold tracking-tight">${t("ui.home.current_title")}</h2>
313      <button id="home-all-studies" class="text-sm text-primary font-medium hover:underline">${t("ui.home.all_studies")} \u2192</button>
314    </div>
315    ${d.length===0?`<p class="text-on-surface-muted text-sm py-6 text-center">${t("ui.home.no_current")}</p>`:`<div id="studies-list" class="space-y-3">${d.map(m=>renderStudyRow(m)).join("")}</div>`}
316
317    <button id="home-support" class="w-full text-left bg-surface-container rounded-[1rem] p-4 mt-8 flex items-center gap-4 hover:bg-surface-container-high transition">
318      <span class="relative w-10 h-10 rounded-full bg-surface-container-high flex items-center justify-center shrink-0 text-primary">
319        <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
320          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M21 12c0 4.418-4.03 8-9 8a9.86 9.86 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"/>
321        </svg>
322        <span id="home-support-badge" class="hidden absolute -top-1 -right-1 min-w-[1.1rem] h-[1.1rem] px-1 rounded-full bg-primary text-on-primary text-[0.65rem] font-semibold items-center justify-center tabular-nums">0</span>
323      </span>
324      <span class="flex-1 min-w-0">
325        <span class="block font-display text-lg font-semibold tracking-tight">${t("ui.support.card_title")}</span>
326        <span class="block text-sm text-on-surface-muted mt-0.5">${t("ui.support.card_sub")}</span>
327      </span>
328      ${chevronRightSvg("w-5 h-5 shrink-0 opacity-70")}
329    </button>
330  `,document.getElementById("home-new-study")?.addEventListener("click",()=>{freeSlotUsed(currentUser)?(_pendingPaywallSource="home_new_study",showStudies()):startCapture("home")}),document.getElementById("home-all-studies")?.addEventListener("click",showStudies),bindStudyRowHandlers(e),refreshRowSignatures(),document.getElementById("home-support")?.addEventListener("click",showSupportModal),refreshSupportUnread(),_studiesMaterials.some(m=>m.status==="processing"||m.status==="uploaded")?startStudiesPolling():stopStudiesPolling()}const SUPPORT_SUGGEST_KEYS=["ui.support.suggest_problem","ui.support.suggest_idea","ui.support.suggest_billing"],SUPPORT_MAX_CHARS=2e3,SUPPORT_COUNT_VISIBLE_AT=1600;function supportPasteOverflow(e,n,s,r){const i=e-n+s-r;return i>0?i:0}function showSupportModal(){if(document.getElementById("support-modal"))return;postClientEvent("support_opened");let e=[],n=0,s=!1,r=!1,a=null;const i=document.createElement("div");i.id="support-modal",i.className="fixed inset-0 z-50 bg-black/70 backdrop-blur-sm flex items-stretch justify-center sm:items-center sm:p-4",i.innerHTML=`
331    <div data-panel class="bg-surface-container w-full h-full sm:h-[85vh] sm:max-w-lg sm:rounded-[1.25rem] flex flex-col overflow-hidden">
332      <div class="flex items-center justify-between gap-3 px-4 py-3 border-b border-surface-container-high">
333        <button data-new-chat class="text-sm text-on-surface-muted hover:text-on-surface transition disabled:opacity-40" disabled>${t("ui.support.new_chat")}</button>
334        <div class="text-center min-w-0">
335          <div class="font-display font-semibold tracking-tight truncate">${t("ui.support.title")}</div>
336          <div class="text-xs text-on-surface-muted truncate">${t("ui.support.subtitle")}</div>
337        </div>
338        <button data-done class="text-sm text-primary font-medium">${t("ui.support.done")}</button>
339      </div>
340      <div data-scroll class="flex-1 overflow-y-auto px-4 py-4">
341        <div data-list class="space-y-2.5"></div>
342      </div>
343      <div data-chips class="flex gap-2 overflow-x-auto px-4 pb-2" style="display:none"></div>
344      <div data-count class="hidden px-4 pt-1 text-right text-[0.7rem] text-on-surface-muted tabular-nums"></div>
345      <div class="flex items-end gap-2 px-4 pb-4 pt-1">
346        <textarea data-input rows="1" maxlength="2000" placeholder="${t("ui.support.input_placeholder")}"
347          class="flex-1 resize-none bg-surface-container-high rounded-[0.875rem] px-4 py-3 text-sm text-on-surface placeholder:text-on-surface-muted focus:outline-none"></textarea>
348        <button data-send class="w-11 h-11 rounded-full bg-primary text-on-primary flex items-center justify-center shrink-0 disabled:opacity-40 transition" disabled>
349          <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12l7-7 7 7M12 5v14"/></svg>
350        </button>
351      </div>
352    </div>`,document.body.appendChild(i);const o=i.querySelector("[data-panel]"),u=i.querySelector("[data-scroll]"),c=i.querySelector("[data-list]"),l=i.querySelector("[data-chips]"),d=i.querySelector("[data-input]"),m=i.querySelector("[data-count]"),g=i.querySelector("[data-send]"),h=i.querySelector("[data-new-chat]"),C=`<p class="text-sm text-on-surface-muted text-center py-10">${t("ui.support.empty")}</p>`;function I(b){const S=b.direction==="outbound";return`<div class="flex ${S?"justify-end":"justify-start"}"><div class="max-w-[82%] px-3.5 py-2.5 text-sm leading-relaxed whitespace-pre-wrap break-words ${S?"bg-primary text-on-primary rounded-[1.125rem] rounded-br-[0.25rem]":"bg-surface-container-high text-on-surface rounded-[1.125rem] rounded-bl-[0.25rem]"}">${escapeHtml(b.body)}</div></div>`}function L(){requestAnimationFrame(()=>{u.scrollTop=u.scrollHeight})}function y(){l.innerHTML=SUPPORT_SUGGEST_KEYS.map(b=>`<button class="whitespace-nowrap text-sm font-medium px-3.5 py-2 rounded-full bg-surface-container-high text-on-surface hover:bg-surface-container transition">${t(b)}</button>`).join(""),l.style.display="flex";for(const b of l.querySelectorAll("button"))b.addEventListener("click",()=>q(b.textContent))}function _(){c.innerHTML=e.length?e.map(I).join(""):C,h.disabled=e.length===0,e.length===0?y():l.style.display="none",L()}function w(){g.disabled=s||d.value.trim().length===0}function B(){d.style.height="auto",d.style.height=Math.min(d.scrollHeight,128)+"px"}function A(){const b=d.value.length,S=b>
352=SUPPORT_COUNT_VISIBLE_AT;m.classList.toggle("hidden",!S),S&&(m.textContent=t("ui.support.chars",{count:b,max:SUPPORT_MAX_CHARS}))}function $(b){b>0&&api("/api/support/read",{method:"POST",body:{upTo:b}}).catch(()=>{})}async function j(){const b=await api("/api/support/messages").catch(()=>null);if(!r){if(!b||b.error){_();return}e=b.data?.messages||[],n=e.reduce((S,M)=>Math.max(S,M.id||0),0),_(),$(n)}}async function D(){if(r||s)return;const b=await api("/api/support/messages?after="+n).catch(()=>null);if(r||!b||b.error)return;const S=(b.data?.messages||[]).filter(M=>!e.some(P=>P.id===M.id));S.length&&(e.push(...S),n=e.reduce((M,P)=>Math.max(M,P.id||0),0),_(),$(n))}async function q(b){const S=(b||"").trim();if(!S||s)return;s=!0,d.value="",B(),A();const M=-Date.now();e.push({id:M,direction:"outbound",body:S}),_(),w();const P=await api("/api/support/messages",{method:"POST",body:{body:S}}).catch(()=>null);if(s=!1,r)return;if(!P||P.error||!P.data?.id){e=e.filter(z=>z.id!==M),d.value=S,B(),A(),_(),w(),showToast(P?.error||t("ui.support.send_failed"));return}const R=e.findIndex(z=>z.id===M);R!==-1&&(e[R]=P.data),n=Math.max(n,P.data.id),_(),w()}async function U(){if(e.length===0||!confirm(t("ui.support.new_chat_confirm")))return;const b=await api("/api/support/archive",{method:"POST",body:{}}).catch(()=>null);if(!r){if(!b||b.error){showToast(b?.error||t("ui.support.send_failed"));return}e=[],n=0,_(),d.focus()}}function H(){r||(r=!0,a&&clearInterval(a),document.removeEventListener("keydown",N),i.remove(),refreshSupportUnread())}function N(b){b.key==="Escape"&&H()}g.addEventListener("click",()=>q(d.value)),d.addEventListener("input",()=>{B(),w(),A()}),d.addEventListener("paste",b=>{const S=b.clipboardData?.getData("text")||"";if(!S)return;const M=Math.abs((d.selectionEnd??0)-(d.selectionStart??0));supportPasteOverflow(d.value.length,M,S.length,SUPPORT_MAX_CHARS)>0&&showToast(t("ui.support.paste_trimmed",{max:SUPPORT_MAX_CHARS}),"info")}),d.addEventListener("keydown",b=>{b.key==="Enter"&&!b.shiftKey&&(b.preventDefault(),q(d.value))}),h.addEventListener("click",U),i.querySelector("[data-done]").addEventListener("click",H),i.addEventListener("click",b=>{o.contains(b.target)||H()}),document.addEventListener("keydown",N),_(),j(),a=setInterval(D,2e3),d.focus()}let _detail=null;async function showStudyDetail(e,n="studies"){postScreenView("study_detail"),stopStudiesPolling(),setShellSurface("reading"),_detail={id:e,fromTab:n==="home"?"home":"studies",material:null,topics:[],panel:null,content:null};const s=document.getElementById("dashboard-content");s.innerHTML=`<p class="text-on-surface-muted">${t("ui.material.loading")}</p>`;const[r,a]=await Promise.all([api(`/api/materials/${e}`).catch(()=>null),api(`/api/mastery/${e}`).catch(()=>null)]);if(!(!_detail||_detail.id!==e)){if(!r||r.error||!r.data){showToast(r?.error||t("ui.study_detail.load_failed")),exitStudyDetail();return}_detail.material=r.data,_detail.topics=a?.data||[],postClientEvent("study_opened",{materialId:e,status:_detail.material?.status??null}),paintStudyDetail()}}function exitStudyDetail(){const e=_detail?.fromTab;_detail=null,e==="home"?showHome():showStudies()}function paintStudyDetail(){const e=document.getElementById("dashboard-content");if(!e||!_detail?.material)return;const n=_detail.material,s=_detail.topics,r=n.status==="ready",a=!!n.public_url,i=!!n.is_focused,o=s.some(y=>y.attempted&&y.total_attempts>0);let u=0,c=0;for(const y of s){const _=Number(y.topic_weight)||1;u+=_,c+=(Number(y.mastery_score)||0)*_}const l=u>0?c/u:0,d=Math.round(l*100),m=n.page_count==null?t("ui.studies.pages_unknown"):t("ui.studies.pages",{count:n.page_count}),g=r&&i,h=failedPagesOf(n),C=unreadablePagesOf(n),I=(n.resources||[]).some(y=>y.url),L=(y,_,w,B)=>`
353    <button id="detail-${y}-btn" ${w?"disabled":""}
354            class="inline-flex items-center gap-2 px-4 py-2.5 rounded-[0.75rem] text-sm font-medium transition ${_?"bg-surface-container-high text-on-surface":"bg-surface-container text-on-surface-muted hover:text-on-surface"} disabled:opacity-40 disabled:cursor-not-allowed">
355      ${B}${t(`ui.study_detail.${y}`)}
356    </button>
357  `;e.innerHTML=`
358    <button id="detail-back" class="text-sm text-on-surface-muted hover:text-on-surface mb-6 block transition">${t("ui.material.back")}</button>
359
360    <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.study_detail.eyebrow")}</span>
361    <h1 class="font-display text-3xl font-semibold tracking-tight mt-1 mb-2">${escapeHtml(n.title)}</h1>
362    <div class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted flex flex-wrap items-center gap-x-2 gap-y-1 mb-6">
363      ${i?`<span class="inline-flex items-center gap-1 text-secondary">${bookmarkSvg("w-3 h-3")}${t("ui.studies.current")}</span><span class="text-on-surface-muted/40">\xB7</span>`:""}
364      <span>${m}</span>
365    </div>
366
367    ${r&&h.length>0?`
368      <div class="bg-error-container/40 rounded-[1rem] p-4 mb-6 flex flex-col sm:flex-row sm:items-center gap-3">
369        <p class="flex-1 text-sm text-on-surface leading-relaxed">${escapeHtml(failedPagesMessage(h))}</p>
370        <button id="detail-retry-pages" class="shrink-0 bg-surface-container-high text-on-surface px-4 py-2 rounded-[0.75rem] text-sm font-medium hover:opacity-90 transition disabled:opacity-40">${t("ui.study_detail.retry_pages")}</button>
371      </div>
372    `:""}
373    ${r&&C.length>0?`
374      <div class="bg-surface-container rounded-[1rem] p-4 mb-6">
375        <p class="text-sm text-on-surface-muted leading-relaxed">${escapeHtml(unreadablePagesMessage(C))}</p>
376      </div>
377    `:""}
378
379    ${o?`
380      <div class="mb-6">
381        <div class="flex items-center justify-between font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mb-1.5">
382          <span>${t("ui.studies.mastery")}</span>
383          <span class="text-on-surface tabular-nums">${d}%</span>
384        </div>
385        <div class="h-1.5 rounded-full bg-surface-container-high overflow-hidden">
386          <div class="h-full rounded-full mastery-bar" style="width: ${d}%; background: ${masteryBandColor(l)};"></div>
387        </div>
388      </div>
389    `:""}
390
391    ${!i&&r?`
392      <div class="bg-surface-container rounded-[1rem] p-5 mb-6 flex items-start gap-3">
393        ${bookmarkSvg("w-5 h-5 shrink-0 text-secondary mt-0.5")}
394        <div class="flex-1 min-w-0">
395          <h3 class="font-display text-base font-semibold tracking-tight">${t("ui.study_detail.not_current_title")}</h3>
396          <p class="text-sm text-on-surface-muted mt-1 leading-relaxed">${t("ui.study_detail.not_current_body")}</p>
397        </div>
398        <button id="detail-add-current" class="shrink-0 bg-primary text-on-primary px-4 py-2 rounded-[0.75rem] text-sm font-medium hover:opacity-90 transition">${t("ui.studies.add_to_current")}</button>
399      </div>
400    `:""}
401
402    ${r?`
403      <button id="detail-start" ${g?"":"disabled"}
404              class="hero-leather w-full text-left rounded-[1.25rem] p-6 shelf-shadow flex items-center gap-4 transition mb-6 ${g?"hover:opacity-95":"opacity-55 saturate-50 cursor-not-allowed"}">
405        <span class="w-12 h-12 rounded-full bg-black/25 flex items-center justify-center shrink-0" style="color: var(--color-on-surface);">
406          ${g?'<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M18 2H8a2 2 0 00-2 2v14.5A2.5 2.5 0 018.5 16H20V4a2 2 0 00-2-2zM8.5 18a1 1 0 000 2H20v-2H8.5zM6 20.5V21a1 1 0 001 1h.05A2.5 2.5 0 016 20.5z"/><path d="M20 22H7a1 1 0 01-1-1v-.5A2.5 2.5 0 018.5 18H20v4z"/></svg>':'<svg class="w-6 h-6" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 2a5 5 0 00-5 5v3H6a2 2 0 00-2 2v8a2 2 0 002 2h12a2 2 0 002-2v-8a2 2 0 00-2-2h-1V7a5 5 0 00-5-5zm-3 8V7a3 3 0 016 0v3H9z"/></svg>'}
407        </span>
408        <span class="flex-1 min-w-0">
409          <span class="block font-display text-xl font-semibold tracking-tight" style="color: var(--color-on-surface);">${t(o?"ui.study_detail.continue_practice":"ui.study_detail.start_practice")}</span>
410          <span class="block text-sm mt-0.5" style="color: rgba(244, 236, 224, 0.75);">${t("ui.study_detail.practice_sub")}</span>
411          <span class="block font-mono text-[0.6rem] uppercase tracking-[0.15em] mt-2" style="color: rgba(244, 236, 224, 0.55);">${t("ui.study_detail.practice_note")}</span>
412        </span>
413        ${chevronRightSvg("w-5 h-5 shrink-0 opacity-70")}
414      </button>
415    `:""}
416
417    ${r&&!n.is_demo?`
418      <div class="bg-surface-container rounded-[1rem] p-5 mb-6">
419        <div class="flex items-start justify-between gap-3">
420          <div class="flex-1 min-w-0">
421            <h3 class="font-display text-base font-semibold tracking-tight">${t("ui.study_detail.share_title")}</h3>
422            <p class="text-sm text-on-surface-muted mt-1 leading-relaxed">${t("ui.study_detail.share_body")}</p>
423          </div>
424          <button id="detail-share-toggle" type="button" role="switch" aria-checked="${a}"
425                  class="shrink-0 px-4 py-2 rounded-[0.75rem] text-sm font-medium transition ${a?"bg-primary text-on-primary":"bg-surface-container-high text-on-surface-muted hover:text-on-surface"}">
426            ${t(a?"ui.study_detail.share_on":"ui.study_detail.share_off")}
427          </button>
428        </div>
429        ${a?`
430          <div class="mt-4 flex items-center gap-2">
431            <input id="detail-share-url" readonly value="${escapeHtml(n.public_url)}" class="flex-1 min-w-0 bg-surface rounded-[0.75rem] px-3 py-2 text-sm text-on-surface font-mono" />
432            <a href="${escapeHtml(n.public_url)}" target="_blank" rel="noopener" class="shrink-0 text-sm text-primary font-medium hover:underline">${t("ui.study_detail.share_open")}</a>
433            <button id="detail-share-copy" type="button" class="shrink-0 bg-surface-container-high px-3 py-2 rounded-[0.75rem] text-sm font-medium hover:text-on-surface transition">${t("ui.study_detail.share_copy")}</button>
434          </div>
435        `:""}
436      </div>
437    `:""}
438
439    <div class="flex gap-2 mb-4">
440      ${L("text",_detail.panel==="text",!1,'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/></svg>')}
441      ${L("photos",_detail.panel==="photos",!I,'<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2zm2-11a1 1 0 100-2 1 1 0 000 2z"/></svg>')}
442    </div>
443    <div id="detail-panel" class="${_detail.panel?"mb-6":""}"></div>
444
445    ${s.length>0?`
446      <h2 class="font-display text-xl font-semibold tracking-tight mb-3">${t("ui.study_detail.topics")}</h2>
447      <div class="space-y-3">
448        ${s.map(y=>renderTopicRow(y)).join("")}
449      </div>
450    `:""}
451  `,document.getElementById("detail-back")?.addEventListener("click",exitStudyDetail),document.getElementById("detail-retry-pages")?.addEventListener("click",y=>{y.currentTarget.disabled=!0,retryMaterialProcessing(n.id)}),document.getElementById("detail-add-current")?.addEventListener("click",addDetailToCurrent),document.getElementById("detail-start")?.addEventListener("click",()=>{g&&startPracticeSession(n)}),document.getElementById("detail-text-btn")?.addEventListener("click",()=>toggleDetailPanel("text")),document.getElementById("detail-photos-btn")?.addEventListener("click",()=>toggleDetailPanel("photos")),document.getElementById("detail-share-toggle")?.addEventListener("click",()=>toggleDetailShare(!a)),document.getElementById("detail-share-copy")?.addEventListener("click",async()=>{const y=_detail?.material?.public_url;if(y)try{await navigator.clipboard.writeText(y),showToast(t("ui.study_detail.share_copied"),"success")}catch{document.getElementById("detail-share-url")?.select()}}),paintDetailPanel()}
451async function toggleDetailShare(e){const n=_detail?.id;if(!n)return;const s=document.getElementById("detail-share-toggle");s&&(s.disabled=!0);const r=await api(`/api/materials/${n}/share`,{method:"PATCH",body:{public:e}}).catch(()=>null);if(!(!_detail||_detail.id!==n)){if(!r||r.error||!r.data){s&&(s.disabled=!1),showToast(r?.error||t("ui.study_detail.share_failed"));return}_detail.material.public_url=r.data.public_url,_detail.material.public_slug=r.data.public_slug,_detail.material.published_at=r.data.public?new Date().toISOString():null,postClientEvent("study_shared",{materialId:n,public:!!r.data.public}),paintStudyDetail()}}function failedPagesOf(e){const n=e?.processing_progress?.pages_failed;return Array.isArray(n)?n.filter(s=>Number.isFinite(Number(s))).map(Number):[]}function failedPagesMessage(e){const n=e.join(", ");return e.length===1?t("ui.study_detail.pages_failed_one",{pages:n}):t("ui.study_detail.pages_failed_many",{pages:n})}function unreadablePagesOf(e){const n=e?.processing_progress?.pages_unreadable;return Array.isArray(n)?n.filter(s=>Number.isFinite(Number(s))).map(Number):[]}function unreadablePagesMessage(e){const n=e.join(", ");return e.length===1?t("ui.study_detail.pages_left_out_one",{pages:n}):t("ui.study_detail.pages_left_out_many",{pages:n})}async function retryMaterialProcessing(e){if(_retryingMaterials.has(e))return;_retryingMaterials.add(e);const n=await api(`/api/materials/${e}/reprocess`,{method:"POST"}).catch(()=>null);if(_retryingMaterials.delete(e),!n||n.error||!n.data){showToast(n?.error||t("ui.study_detail.retry_failed")),_detail?.id===e?paintStudyDetail():currentTab==="studies"?showStudies():currentTab==="home"&&showHome();return}showToast(t("ui.study_detail.retry_started"),"success");const s=_studiesMaterials.find(r=>r.id===e);if(s&&(s.status="processing",s.processing_stage="ocr",s.processing_progress={}),_detail?.id===e){_detail=null,showStudies();return}currentTab==="studies"?showStudies():currentTab==="home"&&showHome()}function renderTopicRow(e){const n=Number(e.mastery_score)||0,s=Math.round(n*100),r=Number(e.total_attempts)||0,a=Number(e.correct_attempts)||0,i=Number(e.current_streak)||0,o=e.attempted&&r>0;return`
452    <div class="bg-surface-container rounded-[1rem] p-4">
453      <div class="flex items-baseline justify-between gap-3 mb-2">
454        <h3 class="font-display text-base font-semibold tracking-tight min-w-0 truncate">${escapeHtml(e.topic_name)}</h3>
455        <span class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted shrink-0">${t("ui.studies.mastery")} <span class="text-on-surface tabular-nums">${s}%</span></span>
456      </div>
457      <div class="h-1 rounded-full bg-surface-container-high overflow-hidden mb-2">
458        <div class="h-full rounded-full mastery-bar" style="width: ${s}%; background: ${masteryBandColor(n)};"></div>
459      </div>
460      <div class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted flex flex-wrap items-center gap-x-2 gap-y-1">
461        <span>${o?t("ui.study_detail.topic_correct",{correct:a,total:r}):t("ui.studies.not_started")}</span>
462        ${i>0?`<span class="text-on-surface-muted/40">\xB7</span><span class="inline-flex items-center gap-1 text-secondary">${flameSvg("w-3 h-3")}${t("ui.study_detail.topic_streak",{count:i})}</span>`:""}
463      </div>
464    </div>
465  `}async function addDetailToCurrent(){const e=_detail?.material;if(!e)return;const n=await api(`/api/materials/${e.id}/focus`,{method:"PATCH",body:{focused:!0,exclusive:!1}}).catch(()=>null);if(!n||n.error){showToast(t("ui.studies.set_current_failed"));return}if(_detail?.material!==e)return;e.is_focused=!0;const s=_studiesMaterials.find(r=>r.id===e.id);s&&(s.is_focused=!0),paintStudyDetail()}function toggleDetailPanel(e){_detail&&(_detail.panel=_detail.panel===e?null:e,paintStudyDetail())}async function paintDetailPanel(){const e=document.getElementById("detail-panel");if(!(!e||!_detail)){if(!_detail.panel){e.innerHTML="";return}if(_detail.panel==="photos"){const n=(_detail.material.resources||[]).filter(s=>s.url);e.innerHTML=n.length===0?`<p class="text-sm text-on-surface-muted">${t("ui.study_detail.no_photos")}</p>`:`
466        <div class="grid grid-cols-3 sm:grid-cols-4 gap-2">
467          ${n.map(s=>`<img src="${escapeHtml(s.url)}" alt="" loading="lazy" class="rounded-[0.625rem] aspect-square object-cover w-full bg-surface-container-high">`).join("")}
468        </div>
469      `;return}if(_detail.content==null){e.innerHTML=`<p class="text-sm text-on-surface-muted">${t("ui.material.loading")}</p>`;const n=await api(`/api/materials/${_detail.id}
469/content`).catch(()=>null);if(!_detail||_detail.panel!=="text")return;_detail.content=n?.data&&(n.data.refinedText||n.data.extractedText)||""}e.innerHTML=_detail.content?`
470      <div class="bg-surface-container rounded-[1rem] px-5 py-6 sm:px-8 max-h-[70vh] overflow-y-auto">
471        <p class="reading-text whitespace-pre-wrap">${escapeHtml(_detail.content)}</p>
472      </div>
473    `:`<p class="text-sm text-on-surface-muted">${t("ui.study_detail.no_text")}</p>`}}let _sess=null;const LLM_JUDGED_TYPES=["free_form","cloze","numeric"],SESSION_BUDGET=10;function enterSessionChrome(){document.getElementById("app-nav")?.classList.add("hidden"),document.getElementById("app-footer")?.classList.add("hidden")}function exitSessionChrome(){document.getElementById("app-nav")?.classList.remove("hidden"),document.getElementById("app-footer")?.classList.remove("hidden")}async function startPracticeSession(e){_sess={materialId:e.id,materialTitle:e.title,fromTab:_detail?.fromTab||"studies",sessionId:null,phase:"starting",question:null,answered:0,correct:0,reviews:0,fresh:0,checks:0,topics:new Set,next:"end",nextQuestion:null,extended:!1,summary:void 0,dict:null},postScreenView("practice"),setShellSurface("reading"),enterSessionChrome(),renderSession();const n=loadKatex(),s=await api(`/api/materials/${e.id}/study`,{method:"POST",body:{source:"web"}}).catch(()=>null);if(await n,!(!_sess||_sess.materialId!==e.id)){if(!s||s.error){showToast(s?.error||t("ui.study.no_questions")),_sess.phase="error",endPracticeSession();return}_sess.sessionId=s.data?.session?.id||null,s.data?.question?setSessionQuestion(s.data.question):(_sess.phase="empty",postPracticeEnded("empty"),renderSession())}}function postPracticeEnded(e){!_sess||_sess.endPosted||(_sess.endPosted=!0,postClientEvent("practice_ended",{materialId:_sess.materialId,answered:_sess.answered,correct:_sess.correct,reason:e}))}function setSessionQuestion(e){abortDictation(),Object.assign(_sess,{phase:"question",question:e,attemptNonce:crypto.randomUUID(),shownAt:Date.now(),selected:new Set,pairs:new Map,selectedSubmitted:null,answerTextSubmitted:"",submitting:!1,result:null,nextQuestion:null,next:"end",flagged:!1,flagOpen:!1,gradeCheck:null,flipped:!1,flipPainted:!1,cardPainted:!1,tutor:{open:!1,messages:[],pending:!1,remaining:5}}),renderSession()}function endPracticeSession(){abortDictation();const e=_sess;e&&postPracticeEnded(e.phase==="error"?"error":"user_exit"),_sess=null,exitSessionChrome(),e?.sessionId&&api(`/api/sessions/${e.sessionId}/end`,{method:"POST"}).catch(()=>{}),e?showStudyDetail(e.materialId,e.fromTab):showStudies()}function requestEndSession(){if(!_sess)return;if((_sess.phase==="question"||_sess.phase==="review"||_sess.phase==="loading_next"||_sess.phase==="next_failed")&&_sess.answered>0){showSessionSummary();return}endPracticeSession()}
473function renderSession(){if(!_sess)return;const e=document.getElementById("dashboard-content");if(!e)return;const n=_sess.extended?t("ui.study.answered_count",{count:_sess.answered}):t("ui.study.session_progress",{answered:_sess.answered,budget:SESSION_BUDGET});e.innerHTML=`
474    <div class="flex items-center justify-between gap-3 mb-6">
475      ${_sess.phase==="summary"?'<span class="shrink-0 w-16"></span>':`<button id="sess-end" class="text-sm text-on-surface-muted hover:text-on-surface transition shrink-0">${t("ui.study.end_session")}</button>`}
476      <h1 class="font-display text-base font-semibold tracking-tight truncate">${escapeHtml(_sess.materialTitle)}</h1>
477      <span class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted shrink-0 tabular-nums">
478        ${n} \xB7 <span class="text-tertiary-fixed">${t("ui.study.correct_count",{count:_sess.correct})}</span>
479      </span>
480    </div>
481    <div class="max-w-2xl mx-auto" id="sess-body">${renderSessionBody()}</div>
482  `,document.getElementById("sess-end")?.addEventListener("click",requestEndSession),bindSessionBodyHandlers()}
482function renderSessionBody(){switch(_sess.phase){case"starting":return`
483        <div class="flex flex-col items-center justify-center py-24 text-on-surface-muted">
484          ${spinnerSvg("w-8 h-8 text-secondary")}
485          <p class="text-sm mt-4">${t("ui.study.starting")}</p>
486        </div>
487      `;case"empty":return`
488        <div class="text-center py-16">
489          <svg class="w-10 h-10 mx-auto text-on-surface-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M20 13V6a2 2 0 00-2-2H6a2 2 0 00-2 2v7m16 0v5a2 2 0 01-2 2H6a2 2 0 01-2-2v-5m16 0h-5.5a2.5 2.5 0 01-5 0H4"/></svg>
490          <h2 class="font-display text-2xl font-semibold mt-4 mb-2 tracking-tight">${t("ui.study.no_questions_title")}</h2>
491          <p class="text-on-surface-muted text-sm max-w-sm mx-auto leading-relaxed mb-8">${t("ui.study.no_questions_body")}</p>
492          <button id="sess-back-study" class="bg-primary text-on-primary px-6 py-3 rounded-[0.875rem] font-medium hover:opacity-90 transition">${t("ui.study.back_to_study")}</button>
493        </div>
494      `;case"loading_next":return`
495        <div class="flex flex-col items-center justify-center py-24 text-on-surface-muted">
496          ${spinnerSvg("w-8 h-8 text-secondary")}
497          <p class="text-sm mt-4">${t("ui.study.next_preparing")}</p>
498        </div>
499      `;case"next_failed":return`
500        <div class="text-center py-16">
501          <h2 class="font-display text-2xl font-semibold mb-2 tracking-tight">${t("ui.study.next_failed_title")}</h2>
502          <p class="text-on-surface-muted text-sm max-w-sm mx-auto leading-relaxed mb-8">${t("ui.study.next_failed_body")}</p>
503          <div class="flex flex-col sm:flex-row items-center justify-center gap-3">
504            <button id="sess-next-retry" class="bg-primary text-on-primary px-6 py-3 rounded-[0.875rem] font-medium hover:opacity-90 transition">${t("ui.study.retry")}</button>
505            <button id="sess-summary" class="px-6 py-3 rounded-[0.875rem] text-sm text-on-surface-muted hover:text-on-surface transition font-medium">${t("ui.study.finish_session")}</button>
506          </div>
507        </div>
508      `;case"summary":return renderSessionSummary();default:return renderQuestionCard()}}
508function renderSessionSummary(){const e=!_sess.resumeQuestion&&_sess.next==="end"&&!_sess.nextQuestion,n=[t("ui.study.summary_reviews",{count:_sess.reviews}),t("ui.study.summary_new",{count:_sess.fresh}),..._sess.checks>0?[t("ui.study.summary_checks",{count:_sess.checks})]:[]].join(" \xB7 "),s=[..._sess.topics],r=_sess.summary,a=[];if(r&&(Number(r.dueNow)>0?a.push(t("ui.study.summary_due_now",{count:Number(r.dueNow)})):r.nextDueAt&&a.push(t("ui.study.summary_next_due",{when:formatSessionDate(r.nextDueAt)})),Number(r.totalTopics)>0)){const i=Number(r.untestedTopics)||0;a.push(i>0?t("ui.study.summary_untested",{untested:i,total:Number(r.totalTopics)}):t("ui.study.summary_all_tested"))}return`
509    <div class="text-center py-12">
510      <span class="font-mono text-[0.7rem] uppercase tracking-[0.2em] text-secondary">${t("ui.study.closing_book")}</span>
511      <h2 class="font-display text-3xl font-semibold mt-3 mb-3 tracking-tight">${t(e?"ui.study.session_complete":"ui.study.summary_title")}</h2>
512      <p class="text-on-surface font-medium mb-1">${t("ui.study.session_summary",{answered:_sess.answered,correct:_sess.correct})}</p>
513      <p class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mb-6">${escapeHtml(n)}</p>
514      ${s.length>0?`
515        <div class="bg-surface-container rounded-[1rem] p-4 mb-4 text-left max-w-md mx-auto">
516          <div class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mb-1.5">${t("ui.study.summary_topics")}</div>
517          <p class="text-sm text-on-surface leading-relaxed">${s.map(i=>escapeHtml(i)).join(", ")}</p>
518        </div>
519      `:""}
520      ${a.length>0?`
521        <div class="bg-surface-container rounded-[1rem] p-4 mb-4 text-left max-w-md mx-auto space-y-1">
522          ${a.map(i=>`<p class="text-sm text-on-surface leading-relaxed">${escapeHtml(i)}</p>`).join("")}
523        </div>
524      `:""}
525      <p class="text-on-surface-muted text-sm mb-8 max-w-sm mx-auto leading-relaxed">${t(e?"ui.study.summary_bank_done":"ui.study.session_complete_body")}</p>
526      <div class="flex flex-col sm:flex-row items-center justify-center gap-3">
527        ${e?"":`<button id="sess-keep-going" class="bg-primary text-on-primary px-6 py-3 rounded-[0.875rem] font-medium hover:opacity-90 transition">${t("ui.study.keep_going")}</button>`}
528        <button id="sess-back-study" class="${e?"bg-primary text-on-primary":"bg-surface-container-high text-on-surface"} px-6 py-3 rounded-[0.875rem] font-medium hover:opacity-90 transition">${t("ui.study.back_to_study")}</button>
529      </div>
530    </div>
531  `}function formatSessionDate(e){const n=new Date(e);if(Number.isNaN(n.getTime()))return"";try{return n.toLocaleString(currentLocale||void 0,{dateStyle:"medium",timeStyle:"short"})}catch{return n.toLocaleString()}}function renderQuestionCard(){const e=_sess.question;return e.type==="free_form"?renderFlashcard():`
532    ${renderQuestionPrompt(e)}
533    ${_sess.phase==="review"?renderReviewStack():renderQuestionInput(e)}
534  `}function renderQuestionPrompt(e){return`
535    <h2 class="font-display text-2xl font-semibold mb-6 leading-snug tracking-tight rich">${renderRich(e.questionText)}</h2>
536  `}function renderQuestionInput(e){if(e.type==="scq"||e.type==="mcq"){const n=e.type==="mcq";return`
537      <p class="text-xs text-on-surface-muted mb-3">${t(n?"ui.study.choice_hint_multi":"ui.study.choice_hint_single")}</p>
538      <div class="space-y-2.5 mb-6" id="sess-options">
539        ${(e.options||[]).map((s,r)=>renderOptionRow(s.text,r,n,_sess.selected.has(r))).join("")}
540      </div>
541      ${renderSubmitRow({disabled:_sess.selected.size===0})}
542    `}if(e.type==="matching"){const n=e.options?.left||[],s=e.options?.right||[];return`
543      <p class="text-xs text-on-surface-muted mb-3">${t("ui.study.matching_hint")}</p>
544      <div class="space-y-2.5 mb-6" id="sess-matching">
545        ${n.map((r,a)=>`
546          <label class="flex flex-col sm:flex-row sm:items-center gap-2 bg-surface-container-low rounded-[0.75rem] p-3">
547            <span class="flex-1 min-w-0 text-on-surface leading-relaxed rich">${renderRich(r.text)}</span>
548            <select data-match-left="${a}" ${_sess.submitting?"disabled":""}
549                    class="sm:w-1/2 bg-surface-container-high rounded-[0.625rem] px-3 py-2.5 text-sm text-on-surface outline-none border-b-2 border-transparent focus:border-primary transition disabled:opacity-60">
550              <option value="">${escapeHtml(t("ui.study.matching_pick"))}</option>
551              ${s.map((i,o)=>`<option value="${o}" ${_sess.pairs.get(a)===o?"selected":""}>${escapeHtml(i.text)}</option>`).join("")}
552            </select>
553          </label>
554        `).join("")}
555      </div>
556      ${renderSubmitRow({disabled:_sess.pairs.size<n.length})}
557    `}if(e.type==="cloze"){const n=(e.questionText.match(/___/g)||[]).length;return n>1?`
558        <div class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mb-2">${t("ui.study.blanks_title")}</div>
559        <div class="space-y-2.5 mb-6">
560          ${Array.from({length:n},(s,r)=>`
561            <label class="block">
562              <span class="block text-xs text-on-surface-muted mb-1">${t("ui.study.blank_label",{n:r+1})}</span>
563              <input type="text" class="sess-blank w-full bg-surface-container-high rounded-[0.75rem] px-4 py-3 text-on-surface placeholder:text-on-surface-muted/60 outline-none border-b-2 border-transparent focus:border-primary transition" placeholder="${escapeHtml(t("ui.study.cloze_placeholder"))}">
564            </label>
565          `).join("")}
566        </div>
567        ${renderSubmitRow({disabled:!0})}
568      `:`
569      <input type="text" id="sess-answer" placeholder="${escapeHtml(t("ui.study.cloze_placeholder"))}"
570             class="w-full bg-surface-container-high rounded-[0.75rem] px-4 py-3 text-on-surface placeholder:text-on-surface-muted/60 outline-none border-b-2 border-transparent focus:border-primary transition mb-6">
571      ${renderSubmitRow({disabled:!0})}
572    `}return`
573    <input type="text" id="sess-answer" inputmode="decimal" placeholder="${escapeHtml(t("ui.study.numeric_placeholder"))}"
574           class="w-full bg-surface-container-high rounded-[0.75rem] px-4 py-3 text-on-surface placeholder:text-on-surface-muted/60 outline-none border-b-2 border-transparent focus:border-primary transition mb-6">
575    ${renderSubmitRow({disabled:!0})}
576  `}function renderOptionRow(e,n,s,r){return`
577    <button type="button" data-opt="${n}" aria-pressed="${r}"
578            class="w-full text-left rounded-[0.75rem] p-4 flex items-center gap-3 transition disabled:opacity-60 ${r?"bg-surface-container-high":"bg-surface-container-low hover:bg-surface-container"}">
579      ${s?`<span class="w-5 h-5 rounded-[0.3rem] shrink-0 inline-flex items-center justify-center text-[0.7rem] font-bold transition" style="border: 2px solid ${r?"var(--color-primary)":"var(--color-on-surface-muted)"}; background: ${r?"var(--color-primary)":"transparent"}; color: ${r?"var(--color-on-primary)":"transparent"};">\u2713</span>`:`<span class="w-5 h-5 rounded-full shrink-0 inline-flex items-center justify-center transition" style="border: 2px solid ${r?"var(--color-primary)":"var(--color-on-surface-muted)"};">${r?'<span class="w-2.5 h-2.5 rounded-full" style="background: var(--color-primary);"></span>':""}</span>`}
580      <span class="flex-1 min-w-0 text-on-surface leading-relaxed rich">${renderRich(e)}</span>
581    </button>
582  `}
582function renderSubmitRow({disabled:e=!1,skipOnly:n=!1}={}){return`
583    ${n?"":`
584      <button id="sess-submit" ${e||_sess.submitting?"disabled":""}
585              class="w-full bg-primary text-on-primary py-3.5 rounded-[0.875rem] font-display font-semibold text-base hover:opacity-90 transition disabled:opacity-40 disabled:cursor-not-allowed inline-flex items-center justify-center gap-2">
586        ${t("ui.study.submit")}
587      </button>
588    `}
589    <button id="sess-skip" ${_sess.submitting?"disabled":""}
590            class="w-full mt-2 py-3 text-sm text-on-surface-muted hover:text-on-surface transition font-medium disabled:opacity-40">
591      ${t("ui.study.skip")}
592    </button>
593  `}function renderFlashcard(){const e=_sess.question,n=_sess.phase==="review",s=(o,u)=>`
594    <button id="${o}" class="fc-flip-chip">${flipSvg("w-3.5 h-3.5")}${u}</button>
595  `,r=n?_sess.answered:_sess.answered+1,a=o=>`
596    <div class="fc-head">
597      <span>${escapeHtml(e.topicName||_sess.materialTitle)}</span>
598      ${o?`<span>\u2116 ${r}</span>`:""}
599    </div>
600  `,i=n?'data-flip role="button" tabindex="0" style="cursor: pointer;"':"";return`
601    <div class="fc-scene ${_sess.cardPainted?"":"fc-enter"}">
602      <div id="flip-card" class="flip-card ${n&&_sess.flipped&&_sess.flipPainted?"flipped":""}">
603        <div class="flip-face">
604          <div class="fc-card" ${i}>
605            ${a(!0)}
606            <div class="fc-body">
607              <p class="fc-question rich">${renderRich(e.questionText)}</p>
608            </div>
609          </div>
610        </div>
611        <div class="flip-face flip-back">
612          ${n?renderFlashcardBack(a(!1),i):""}
613        </div>
614      </div>
615    </div>
616    ${n?`
617      <div class="mt-4 mb-4 text-center">${s("flip-chip",_sess.flipped?t("ui.study.flip_to_question"):t("ui.study.flip_to_answer"))}</div>
618      ${renderFlashcardDesk()}
619    `:`
620      <div class="fc-sheet mt-6">
621        <label for="sess-answer" class="block text-sm font-medium mb-2">${t("ui.study.your_answer")}</label>
622        <textarea id="sess-answer" rows="3" placeholder="${escapeHtml(t("ui.study.answer_placeholder"))}"
623          class="w-full bg-surface-container-high rounded-[0.875rem] px-4 py-3 text-on-surface placeholder:text-on-surface-muted/60 outline-none border-b-2 border-transparent focus:border-primary transition"></textarea>
624        ${renderDictationRow()}
625      </div>
626      <div class="mt-4">${renderSubmitRow({disabled:!0})}</div>
627    `}
628  `}function renderFlashcardBack(e,n){const s=_sess.question,r=reviewState(),a={correct:"correct",incorrect:"not_quite",skipped:"skipped",pending:"pending"}[r],i=_sess.result?.wasSkipped?"":formatSubmittedAnswer(s),o=formatCorrectAnswer(s);return`
629    <div class="fc-card" ${n}>
630      <span class="fc-stamp fc-stamp-${r} ${_sess.flipPainted?"fc-stamp-settled":""}">${t(`ui.study.${a}`)}</span>
631      ${e}
632      <div class="fc-body">
633        ${o?`
634          <div class="fc-label">${t("ui.study.correct_answer")}</div>
635          <p class="fc-answer rich">${renderRich(o)}</p>
636        `:""}
637        <div class="fc-label">${t("ui.study.your_answer")}</div>
638        <p class="fc-yours ${i?"":"fc-yours-empty"}">${i?`<span class="rich">${renderRich(i)}</span>`:escapeHtml(t("ui.study.skipped"))}</p>
639      </div>
640    </div>
641  `}function renderFlashcardDesk(){const e=_sess.question,n=_sess.result||{},s=n.feedback&&n.feedback!==e.explanation?n.feedback:"";return`
642    <div class="flex items-center justify-end mb-3">${renderFlagButton()}</div>
643    ${renderFlagPanel()}
644    <div class="space-y-3">
645      ${s?renderReviewCard(t("ui.study.feedback"),s,"bg-tertiary-container/30"):""}
646      ${e.explanation?renderReviewCard(t("ui.study.explanation"),e.explanation,"bg-surface-container"):""}
647      ${renderSourceCard()}
648      ${renderGradeCheck()}
649    </div>
650    ${renderTutorPanel()}
651    ${renderNextButton()}
652  `}function toggleFlashcardFlip(){if(!_sess)return;_sess.flipped=!_sess.flipped,document.getElementById("flip-card")?.classList.toggle("flipped",_sess.flipped);const e=document.getElementById("flip-chip");e&&(e.innerHTML=`${flipSvg("w-3.5 h-3.5")}${_sess.flipped?t("ui.study.flip_to_question"):t("ui.study.flip_to_answer")}`)}function renderReviewStack(e=!1){const n=_sess.question,s=_sess.result||{},r=reviewState(),a={correct:{cls:"text-tertiary-fixed bg-tertiary-container/50",icon:'<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M5 13l4 4L19 7"/></svg>',label:t("ui.study.correct")},incorrect:{cls:"text-error bg-error-container/50",icon:'<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="3" d="M6 6l12 12M18 6L6 18"/></svg>',label:t("ui.study.not_quite")},skipped:{cls:"text-on-surface-muted bg-surface-container-highest",icon:'<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M5 5v14l8-7-8-7zm9 0v14l8-7-8-7z"/></svg>',label:t("ui.study.skipped")},pending:{cls:"text-secondary bg-secondary-container/50",icon:'<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 2h12M6 22h12M8 2v4l4 4 4-4V2M8 22v-4l4-4 4 4v4"/></svg>',label:t("ui.study.pending")}}[r],i=s.wasSkipped?"":formatSubmittedAnswer(n),o=formatCorrectAnswer(n),u=!!o&&(r==="incorrect"||r==="skipped"||e),c=s.feedback&&s.feedback!==n.explanation?s.feedback:"";return`
653    <div class="flex items-center justify-between gap-3 mb-4">
654      <span class="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-full font-mono text-[0.7rem] uppercase tracking-[0.12em] ${a.cls}">${a.icon}${a.label}</span>
655      ${renderFlagButton()}
656    </div>
657    ${renderFlagPanel()}
658    <div class="space-y-3">
659      ${i?renderReviewCard(t("ui.study.your_answer"),i,"bg-surface-container"):""}
660      ${c?renderReviewCard(t("ui.study.feedback"),c,"bg-tertiary-container/30"):""}
661      ${u?renderReviewCard(t("ui.study.correct_answer"),o,"bg-surface-container",!0):""}
662      ${n.explanation?renderReviewCard(t("ui.study.explanation"),n.explanation,"bg-surface-container"):""}
663      ${renderSourceCard()}
664      ${renderGradeCheck()}
665    </div>
666    ${renderTutorPanel()}
667    ${renderNextButton()}
668  `}function reviewState(){const e=_sess.result||{};return e.wasSkipped?"skipped":e.isCorrect===!0?"correct":e.isCorrect===!1?"incorrect":"pending"}function renderSourceCard(){const e=_sess.result?.sourceQuote;return e?renderReviewCard(t("ui.study.source"),e,"bg-surface-container-low",!0):""}function renderGradeCheck(){if((_sess.result||{}).wasSkipped)return"";const n=_sess.gradeCheck;if(n&&typeof n=="object"){const s={upheld:"grade_upheld",answer_correct:"grade_answer_correct",key_wrong:"grade_key_wrong"}[n.verdict]||"grade_upheld",r=`${t(`ui.study.${s}`)}${n.explanation?`
669${n.explanation}`:""}`;return renderReviewCard(t("ui.study.grade_check_title"),r,n.verdict==="upheld"?"bg-surface-container":"bg-tertiary-container/30")}return`
670    <button id="sess-check-grade" ${n==="pending"?"disabled":""}
671            class="w-full text-left bg-surface-container-low hover:bg-surface-container rounded-[1rem] p-4 text-sm text-on-surface transition disabled:opacity-60">
672      ${n==="pending"?`${spinnerSvg("w-4 h-4 inline-block mr-2")}${t("ui.study.check_grade_pending")}`:t("ui.study.check_grade")}
673    </button>
674  `}async function checkCurrentGrade(){if(!_sess||_sess.phase!=="review"||_sess.gradeCheck)return;const e=_sess.question;_sess.gradeCheck="pending",renderSession();const n=await api(`/api/questions/${e.id}/check-grade`,{method:"POST"}).catch(()=>null);if(!(!_sess||_sess.question!==e)){if(!n||n.error||!n.data){_sess.gradeCheck=null,showToast(n?.error||t("ui.study.check_grade_failed")),renderSession();return}_sess.gradeCheck={verdict:n.data.verdict,explanation:n.data.explanation||""},n.data.verdict==="key_wrong"&&(_sess.flagged=!0),renderSession()}}function renderFlagButton(){return _sess.flagged?`<span class="inline-flex items-center gap-1.5 text-[0.7rem] font-mono uppercase tracking-wider text-on-surface-muted">${flagSvg("w-3.5 h-3.5",!0)}${t("ui.study.reported")}</span>`:`<button id="sess-flag" class="inline-flex items-center gap-1.5 text-[0.7rem] font-mono uppercase tracking-wider text-on-surface-muted hover:text-error transition">${flagSvg("w-3.5 h-3.5",!1)}${t("ui.study.report")}</button>`}function renderFlagPanel(){return _sess.flagOpen?`
675    <div class="bg-surface-container-highest rounded-[0.875rem] p-4 mb-4">
676      <p class="text-sm font-medium mb-3">${t("ui.study.report_title")}</p>
677      <div class="flex flex-wrap gap-2">
678        ${[["incorrect","report_incorrect"],["unclear","report_unclear"],["off_topic","report_off_topic"],["other","report_other"]].map(([e,n])=>`<button data-flag-reason="${e}" class="text-xs font-medium px-3 py-2 rounded-[0.5rem] bg-surface-container-high text-on-surface hover:bg-error-container/60 transition">${t(`ui.study.${n}`)}</button>`).join("")}
679        <button id="sess-flag-cancel" class="text-xs font-medium px-3 py-2 rounded-[0.5rem] text-on-surface-muted hover:text-on-surface transition">${t("ui.review.cancel")}</button>
680      </div>
681    </div>
682  `:""}function renderNextButton(){const e=_sess.answered>=SESSION_BUDGET&&!_sess.extended,n=_sess.nextQuestion||_sess.next!=="end";return`
683    <button id="sess-next" class="w-full mt-6 bg-primary text-on-primary py-3.5 rounded-[0.875rem] font-display font-semibold text-base hover:opacity-90 transition">
684      ${t(e?"ui.study.finish_session":n?"ui.study.next_question":"ui.study.finish")}
685    </button>
686  `}function renderReviewCard(e,n,s,r=!1){return`
687    <div class="${s} rounded-[1rem] p-4">
688      <div class="font-mono text-[0.65rem] uppercase tracking-wider text-on-surface-muted mb-1.5 flex items-center gap-1.5">
689        ${r?'<span class="w-1.5 h-1.5 rounded-full shrink-0" style="background: var(--color-tertiary);"></span>':""}
690        ${e}
691      </div>
692      <p class="text-sm text-on-surface leading-relaxed rich">${renderRich(n)}</p>
693    </div>
694  `}function formatMatchingPairs(e,n){const s=new Map((e.options?.left||[]).map(a=>[a.id,a.text])),r=new Map((e.options?.right||[]).map(a=>[a.id,a.text]));return(n||[]).map(a=>`${s.get(a?.left)??a?.left??""} \u2192 ${r.get(a?.right)??a?.right??""}`).join(`
695`)}function formatSubmittedAnswer(e){if(e.type==="scq"||e.type==="mcq"){const n=e.options||[];return(_sess.selectedSubmitted||[]).map(s=>
695n[s]?.text??"").filter(Boolean).join(", ")}return e.type==="matching"?formatMatchingPairs(e,_sess.selectedSubmitted):_sess.answerTextSubmitted||""}function formatCorrectAnswer(e){const n=e.answerKey;if(!n)return"";if(Array.isArray(n.correctIndices)){const s=e.options||[];return n.correctIndices.map(r=>s[r]?.text??"").filter(Boolean).join(", ")}return Array.isArray(n.pairs)?formatMatchingPairs(e,n.pairs):n.correctAnswer||""}function renderTutorPanel(){const e=_sess.tutor;if(!e.open)return`
696      <button id="tutor-open" class="w-full mt-4 text-left bg-tertiary-container/30 hover:bg-tertiary-container/45 rounded-[1rem] p-4 flex items-center gap-3 transition">
697        ${sparklesSvg("w-5 h-5 shrink-0 text-tertiary")}
698        <span class="flex-1 min-w-0">
699          <span class="block text-sm font-medium text-on-surface">${t("ui.study.tutor_open")}</span>
700          <span class="block text-xs text-on-surface-muted mt-0.5">${t("ui.study.tutor_sub")}</span>
701        </span>
702        ${chevronRightSvg("w-4 h-4 shrink-0 text-on-surface-muted")}
703      </button>
704    `;const n=e.remaining<=0;return`
705    <div class="mt-4 bg-surface-container rounded-[1rem] p-4">
706      <div class="flex items-center gap-2 mb-3">
707        ${sparklesSvg("w-4 h-4 text-tertiary")}
708        <span class="font-display text-base font-semibold tracking-tight">${t("ui.study.tutor_open")}</span>
709      </div>
710      <div id="tutor-messages" class="space-y-2.5 max-h-72 overflow-y-auto mb-3">
711        ${e.messages.length===0?`
712          <p class="text-sm text-on-surface-muted">${t("ui.study.tutor_empty")}</p>
713          <p class="text-xs text-on-surface-muted/70">${t("ui.study.tutor_cap")}</p>
714        `:e.messages.map(s=>s.role==="user"?`<div class="flex justify-end"><div class="max-w-[85%] bg-primary text-on-primary rounded-[0.875rem] rounded-br-[0.25rem] px-3.5 py-2.5 text-sm leading-relaxed whitespace-pre-wrap">${escapeHtml(s.content)}</div></div>`:`<div class="flex justify-start"><div class="max-w-[85%] min-w-0 bg-surface-container-high text-on-surface rounded-[0.875rem] rounded-bl-[0.25rem] px-3.5 py-2.5 text-sm leading-relaxed rich">${renderRich(s.content)}</div></div>`).join("")}
715        ${e.pending?`<p class="text-xs text-on-surface-muted italic">${t("ui.study.tutor_thinking")}</p>`:""}
716      </div>
717      ${n?`<p class="text-xs text-on-surface-muted">${t("ui.study.tutor_limit")}</p>`:`
718          <div class="flex items-center gap-2">
719            <input id="tutor-input" type="text" maxlength="2000" placeholder="${escapeHtml(t("ui.study.tutor_placeholder"))}" ${e.pending?"disabled":""}
720                   class="flex-1 min-w-0 bg-surface-container-high rounded-[0.75rem] px-3.5 py-2.5 text-sm text-on-surface placeholder:text-on-surface-muted/60 outline-none border-b-2 border-transparent focus:border-primary transition">
721            <button id="tutor-send" ${e.pending?"disabled":""} aria-label="${escapeHtml(t("ui.study.tutor_send"))}"
722                    class="shrink-0 w-10 h-10 rounded-full bg-primary text-on-primary flex items-center justify-center hover:opacity-90 transition disabled:opacity-40">
723              <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M12 19V5m0 0l-6 6m6-6l6 6"/></svg>
724            </button>
725          </div>
726        `}
727    </div>
728  `}async function sendTutorMessage(){const e=_sess?.tutor,n=document.getElementById("tutor-input");if(!e||e.pending||!n)return;const s=n.value.trim();if(!s)return;const r=_sess.question;e.messages.push({role:"user",content:s}),e.pending=!0,renderSession();const a=await api(`/api/questions/${r.id}/tutor`,{method:"POST",body:{messages:e.messages}}).catch(()=>null);if(!(!_sess||_sess.question!==r)){if(e.pending=!1,!a||a.error||!a.data){e.messages.pop(),showToast(a?.error||t("ui.study.tutor_failed")),renderSession();return}e.messages.push({role:"assistant",content:a.data.reply||""}),e.remaining=a.data.userMessagesRemaining??Math.max(0,e.remaining-1),renderSession(),document.getElementById("tutor-input")?.focus()}}function bindSessionBodyHandlers(){if(!_sess)return;const e=document.getElementById("sess-body");if(!e)return;e.querySelectorAll("[data-opt]").forEach(i=>
728{i.addEventListener("click",()=>toggleSessionOption(parseInt(i.dataset.opt,10)))}),document.getElementById("sess-submit")?.addEventListener("click",submitCurrentAnswer),document.getElementById("sess-skip")?.addEventListener("click",skipCurrentQuestion),document.getElementById("sess-next")?.addEventListener("click",advanceSession),document.getElementById("sess-back-study")?.addEventListener("click",endPracticeSession),document.getElementById("sess-next-retry")?.addEventListener("click",fetchNextQuestion),document.getElementById("sess-summary")?.addEventListener("click",showSessionSummary),document.getElementById("sess-keep-going")?.addEventListener("click",keepGoing);const n=e.querySelectorAll("[data-match-left]");n.forEach(i=>{i.addEventListener("change",()=>{if(!_sess||_sess.phase!=="question")return;const o=Number(i.getAttribute("data-match-left"));i.value!==""?_sess.pairs.set(o,Number(i.value)):_sess.pairs.delete(o);const u=document.getElementById("sess-submit");u&&!_sess.submitting&&(u.disabled=_sess.pairs.size<n.length)})}),document.getElementById("sess-flag")?.addEventListener("click",()=>{_sess.flagOpen=!_sess.flagOpen,renderSession()}),document.getElementById("sess-check-grade")?.addEventListener("click",checkCurrentGrade),document.getElementById("sess-flag-cancel")?.addEventListener("click",()=>{_sess.flagOpen=!1,renderSession()}),e.querySelectorAll("[data-flag-reason]").forEach(i=>{i.addEventListener("click",()=>submitQuestionFlag(i.dataset.flagReason))}),document.getElementById("tutor-open")?.addEventListener("click",()=>{postClientEvent("tutor_opened",{questionType:_sess.question?.type??null}),_sess.tutor.open=!0,renderSession(),document.getElementById("tutor-input")?.focus()}),document.getElementById("tutor-send")?.addEventListener("click",sendTutorMessage),document.getElementById("tutor-input")?.addEventListener("keydown",i=>{i.key==="Enter"&&(i.preventDefault(),sendTutorMessage())}),document.getElementById("flip-chip")?.addEventListener("click",toggleFlashcardFlip),e.querySelectorAll("[data-flip]").forEach(i=>{i.addEventListener("click",toggleFlashcardFlip),i.addEventListener("keydown",o=>{(o.key==="Enter"||o.key===" ")&&(o.preventDefault(),toggleFlashcardFlip())})}),_sess.cardPainted=!0,document.getElementById("dict-btn")?.addEventListener("click",toggleDictation);const s=[document.getElementById("sess-answer"),...e.querySelectorAll(".sess-blank")].filter(Boolean);if(s.length>0&&_sess.phase==="question"){const i=()=>{const o=document.getElementById("sess-submit");o&&!_sess.submitting&&(o.disabled=!s.some(u=>u.value.trim().length>0))};s.forEach(o=>o.addEventListener("input",i)),i()}const r=document.getElementById("tutor-messages");r&&(r.scrollTop=r.scrollHeight);const a=document.getElementById("flip-card");a&&_sess.phase==="review"&&_sess.flipped&&!_sess.flipPainted&&(_sess.flipPainted=!0,requestAnimationFrame(()=>requestAnimationFrame(()=>a.classList.add("flipped"))))}function toggleSessionOption(e){if(!_sess||_sess.phase!=="question"||_sess.submitting)return;_sess.question.type==="scq"?(_sess.selected.clear(),_sess.selected.add(e)):_sess.selected.has(e)?_sess.selected.delete(e):_sess.selected.add(e),renderSession()}function collectAnswerText(){const e=[...document.querySelectorAll(".sess-blank")];if(e.length>0)return e.map(s=>s.value.trim()).join(" | ");const n=document.getElementById("sess-answer");return n?n.value.trim():""}function setSessionBusy(e){const n=document.getElementById("sess-submit");n&&(n.disabled=e,n.innerHTML=e&&LLM_JUDGED_TYPES.includes(_sess.question.type)?`${spinnerSvg("w-4 h-4")} ${t("ui.study.evaluating")}`:t("ui.study.submit"));const s=document.getElementById("sess-skip");s&&(s.disabled=e),document.querySelectorAll("#sess-options [data-opt]").forEach(a=>{a.disabled=e}),document.querySelectorAll("[data-match-left]").forEach(a=>{a.disabled=e});const r=document.getElementById("sess-answer");r&&(r.disabled=e||!!(_sess.dict&&_sess.dict.state!=="error")),document.querySelectorAll(".sess-blank").forEach(a=>{a.disabled=e})}async function submitCurrentAnswer(){if(!_sess||_sess.phase!=="question"||_sess.submitting)return;const e=_sess.question;if(_sess.dict&&_sess.dict.state!=="error"){_sess.dict.pendingSubmit=!0,stopDictation();return}const n={source:"web",sessionId:_sess.sessionId,selectionSource:e.selectionSource??null,attemptNonce:_sess.attemptNonce,timeSpentMs:Date.now()-_sess.shownAt};if(e.type==="scq"||e.type==="mcq"){if(_sess.selected.size===0)return;n.selectedOptions=[..._sess.selected].sort((s,r)=>s-r),_sess.selectedSubmitted=n.selectedOptions}else if(e.type==="matching"){const s=e.options?.left||[],r=e.options?.right||[];if(_sess.pairs.size<s.length)return;n.selectedOptions=s.map((a,i)=>({left:a.id,right:r[_sess.pairs.get(i)]?.id})),_sess.selectedSubmitted=n.selectedOptions}else{const s=collectAnswerText();if(!s)return;n.answerText=s,_sess.answerTextSubmitted=s}await postAnswer(n)}async function skipCurrentQuestion(){!_sess||_sess.phase!=="question"||_sess.submitting||(abortDictation(),renderDictationUi(),await postAnswer({source:"web",sessionId:_sess.sessionId,selectionSource:_sess.question.selectionSource??null,attemptNonce:_sess.attemptNonce,timeSpentMs:Date.now()-_sess.shownAt,skipped:!0}))}async function postAnswer(e){const n=_sess.question;_sess.submitting=!0,setSessionBusy(!0);const s=await api(`/api/questions/${n.id}/answer`,{method:"POST",body:{...e,awaitNext:!1}}).catch(()=>null);if(!(!_sess||_sess.question!==n)){if(_sess.submitting=!1,s?.reason==="question_retired"){showToast(t("ui.study.question_retired")),s.nextQuestion?setSessionQuestion(s.nextQuestion):fetchNextQuestion();return}if(!s||s.error||!s.data){const r=s?.reason==="grading_unavailable";showToast(r?t("ui.study.grading_unavailable"):s?.error||t("ui.study.submit_failed")),setSessionBusy(!1);return}_sess.result=s.data.answer||{},_sess.nextQuestion=s.data.nextQuestion??null,_sess.next=s.data.next?.state||(_sess.nextQuestion?"available":"end"),_sess.answered+=1,_sess.result.isCorrect===!0&&(_sess.correct+=1),n.kind==="review"?_sess.reviews+=1:n.kind==="check"?_sess.checks+=1:_sess.fresh+=1,n.topicName&&_sess.topics.add(n.topicName),_sess.phase="review",n.type==="free_form"&&(_sess.flipped=!0),renderSession()}}function advanceSession(){if(_sess){if(_sess.answered>=SESSION_BUDGET&&!_sess.extended){showSessionSummary();return}continueSession()}}function continueSession(){if(_sess){if(_sess.nextQuestion){setSessionQuestion(_sess.nextQuestion);return}if(_sess.next==="end"){showSessionSummary();return}fetchNextQuestion()}}async function fetchNextQuestion(){if(!_sess)return;abortDictation();const e={};_sess.nextToken=e,_sess.phase="loading_next",renderSession();const n=await api(`/api/materials/${_sess.materialId}/next-question?source=web`).catch(()=>null);if(!(!_sess||_sess.nextToken!==e||_sess.phase!=="loading_next")){if(!n||n.error||!n.data){_sess.phase="next_failed",render
728Session();return}if(n.data.question){setSessionQuestion(n.data.question);return}if(_sess.next=n.data.next?.state||"end",_sess.next==="end"){showSessionSummary();return}_sess.phase="next_failed",renderSession()}}function collectDraftInputs(){const e=document.getElementById("sess-answer");return{answer:e?e.value:null,blanks:[...document.querySelectorAll(".sess-blank")].map(n=>n.value)}}function restoreDraftInputs(e){if(!e)return;const n=document.getElementById("sess-answer");n&&e.answer!=null&&(n.value=e.answer),document.querySelectorAll(".sess-blank").forEach((a,i)=>{e.blanks[i]!=null&&(a.value=e.blanks[i])});const s=[n,...document.querySelectorAll(".sess-blank")].filter(Boolean),r=document.getElementById("sess-submit");r&&s.length>0&&!_sess.submitting&&(r.disabled=!s.some(a=>a.value.trim().length>0))}function showSessionSummary(){if(_sess&&(abortDictation(),_sess.nextToken=null,_sess.resumeQuestion=_sess.phase==="question"?_sess.question:null,_sess.resumeDraft=_sess.resumeQuestion?collectDraftInputs():null,_sess.phase="summary",renderSession(),_sess.summary===void 0)){const e=_sess;e.summary=null,api(`/api/materials/${e.materialId}/session-summary`).then(n=>{_sess!==e||!n?.data||(e.summary=n.data,e.phase==="summary"&&renderSession())}).catch(()=>{})}}function keepGoing(){if(!(!_sess||_sess.phase!=="summary")){if(_sess.extended=!0,_sess.summary=void 0,_sess.resumeQuestion){const e=_sess.resumeDraft;_sess.resumeQuestion=null,_sess.resumeDraft=null,_sess.phase="question",renderSession(),restoreDraftInputs(e);return}continueSession()}}async function submitQuestionFlag(e){if(!_sess||_sess.flagged)return;const n=_sess.question,s=await api(`/api/questions/${n.id}/flag`,{method:"POST",body:{reason:e,source:"web"}}).catch(()=>null);if(!(!_sess||_sess.question!==n)){if(!s||s.error){showToast(s?.error||t("ui.study.report_failed"));return}_sess.flagged=!0,_sess.flagOpen=!1,renderSession()}}const DICT_SAMPLE_RATE=16e3,DICT_FINAL_GRACE_MS=1500;let _dictWorkletUrl=null;function dictationWorkletUrl(){if(!_dictWorkletUrl){const e=`
729      registerProcessor('pbot-pcm-capture', class extends AudioWorkletProcessor {
730        process(inputs) {
731          const ch = inputs[0] && inputs[0][0];
732          if (ch && ch.length) this.port.postMessage(ch.slice(0));
733          return true;
734        }
735      });
736    `;_dictWorkletUrl=URL.createObjectURL(new Blob([e],{type:"text/javascript"}))}return _dictWorkletUrl}function dictationSupported(){return!!(navigator.mediaDevices?.getUserMedia&&window.WebSocket&&(window.AudioContext||window.webkitAudioContext)&&window.AudioWorkletNode)}function currentDict(){return _sess?.dict||null}function renderDictationRow(){const e=_sess.dict,n=e?.state||"idle",s=n==="connecting"||n==="finishing",r=n==="listening",a=t(r?"ui.dictation.stop":n==="finishing"?"ui.dictation.finishing":n==="connecting"?"ui.dictation.connecting":"ui.dictation.start"),i=n==="error"?e?.error||t("ui.dictation.failed"):s?a:t(r?"ui.dictation.listening":"ui.dictation.helper");return`<div id="dict-row" class="fc-dict">
737    <button id="dict-btn" type="button" ${s||_sess.submitting?"disabled":""}
738      aria-pressed="${r}" aria-describedby="dict-status"
739      class="fc-mic ${r?"dict-pulse bg-error text-white":"bg-surface-container-highest text-primary"}">
740      ${s?spinnerSvg("w-4 h-4"):r?stopSvg("w-4 h-4"):micSvg("w-4 h-4")}
741      <span>${escapeHtml(a)}</span>
742    </button>
743    <p id="dict-status" class="fc-dict-status ${n==="error"?"text-error":"text-on-surface-muted"}" role="status" aria-live="polite">${escapeHtml(i)}</p>
744  </div>`}function renderDictationUi(){if(!_sess)return;const e=document.getElementById("dict-row");if(e){const s=document.createElement("div");s.innerHTML=renderDictationRow();const r=e.querySelector("#dict-btn"),a=e.querySelector("#dict-status"),i=s.querySelector("#dict-btn"),o=s.querySelector("#dict-status");r.innerHTML=i.innerHTML,r.className=i.className,r.disabled=i.disabled,r.setAttribute("aria-pressed",i.getAttribute("aria-pressed")),a.className=o.className,a.textContent=o.textContent}const n=document.getElementById("sess-answer");n&&(n.disabled=!!(_sess.dict&&_sess.dict.state!=="error")||!!_sess.submitting)}async function toggleDictation(){if(_sess){if(_sess.dict&&_sess.dict.state!=="error"){stopDictation();return}await startDictation()}}async function startDictation(){if(!_sess||_sess.phase!=="question"||_sess.submitting)return;if(!dictationSupported()){postClientEvent("dictation_failed",{reason:"unsupported"}),_sess.dict={state:"error",error:t("ui.dictation.unavailable"),question:_sess.question},renderDictationUi();return}const e=document.getElementById("sess-answer"),n={state:"connecting",question:_sess.question,base:(e?.value||"").trim(),committed:"",interim:"",pendingSubmit:!1,ws:null,stream
744:null,ctx:null,buffer:[],buffered:0,graceTimer:null,error:null};_sess.dict=n,postClientEvent("dictation_started"),renderDictationUi();try{n.stream=await navigator.mediaDevices.getUserMedia({audio:{channelCount:1,echoCancellation:!0,noiseSuppression:!0}})}catch(s){const r=s?.name||"",a=r==="NotAllowedError"||r==="SecurityError";failDictation(n,t(a?"ui.dictation.denied":"ui.dictation.unavailable"),a?"permission":"unsupported");return}if(n!==currentDict()){teardownDictation(n);return}connectDictationSocket(n,!1)}function connectDictationSocket(e,n){const s=location.protocol==="https:"?"wss":"ws";let r;try{r=new WebSocket(`${s}://${location.host}/api/voice/stt?lang=${currentLocale}`,["bearer.pbot",accessToken])}catch{failDictation(e,t("ui.dictation.failed"),"socket");return}e.ws=r,r.binaryType="arraybuffer";let a=!1;r.onmessage=i=>{if(e!==currentDict())return;let o;try{o=JSON.parse(i.data)}catch{return}o.type==="ready"?(a=!0,startDictationAudio(e)):o.type==="transcript.partial"?(o.is_final?(e.committed=joinDictation(e.committed,o.text||""),e.interim=""):e.interim=o.text||"",applyDictationText(e)):o.type==="transcript.done"?(typeof o.text=="string"&&o.text.trim()&&(e.committed=o.text.trim(),e.interim=""),finalizeDictation(e)):o.type==="error"&&failDictation(e,t("ui.dictation.failed"))},r.onclose=i=>{if(!(e!==currentDict()||e.ws!==r)){if(!a&&i.code===1008&&!n){tryRefresh().then(o=>{e===currentDict()&&(o?connectDictationSocket(e,!0):failDictation(e,t("ui.dictation.failed"),"socket"))});return}e.state==="connecting"?failDictation(e,t("ui.dictation.failed"),"socket"):finalizeDictation(e)}}}async function startDictationAudio(e){try{const n=window.AudioContext||window.webkitAudioContext;let s;try{s=new n({sampleRate:DICT_SAMPLE_RATE})}catch{s=new n}if(e.ctx=s,await s.audioWorklet.addModule(dictationWorkletUrl()),e!==currentDict()||!e.stream){try{s.close()}catch{}return}const r=s.createMediaStreamSource(e.stream),a=new AudioWorkletNode(s,"pbot-pcm-capture",{numberOfOutputs:1,outputChannelCount:[1]});a.port.onmessage=i=>pushDictationSamples(e,i.data,s.sampleRate),r.connect(a),a.connect(s.destination),e.state="listening",renderDictationUi()}catch{failDictation(e,t("ui.dictation.failed"))}}function pushDictationSamples(e,n,s){if(e!==currentDict()||e.state!=="listening"||!e.ws||e.ws.readyState!==WebSocket.OPEN||(e.buffer.push(n),e.buffered+=n.length,e.buffered<Math.round(s/10)))return;const r=new Float32Array(e.buffered);let a=0;for(const i of e.buffer)r.set(i,a),a+=i.length;e.buffer=[],e.buffered=0;try{e.ws.send(encodePcm16(r,s))}catch{}}function encodePcm16(e,n){let s=e;if(n!==DICT_SAMPLE_RATE){const a=n/DICT_SAMPLE_RATE,i=Math.floor(e.length/a);s=new Float32Array(i);for(let o=0;o<i;o++){const u=o*a,c=Math.floor(u),l=Math.min(c+1,e.length-1);s[o]=e[c]+(e[l]-e[c])*(u-c)}}const r=new Int16Array(s.length);for(let a=0;a<s.length;a++){const i=Math.max(-1,Math.min(1,s[a]));r[a]=i<0?i*32768:i*32767}return r.buffer}function joinDictation(e,n){return[e,n].map(s=>(s||"").trim()).filter(Boolean).join(" ")}function applyDictationText(e){if(_sess?.question!==e.question)return;const n=document.getElementById("sess-answer");if(!n)return;n.value=joinDictation(e.base,joinDictation(e.committed,e.interim));const s=document.getElementById("sess-submit");s&&!_sess.submitting&&(s.disabled=n.value.trim().length===0)}function stopDictation(){const e=currentDict();if(e){if(e.state==="error"){_sess.dict=null,renderDictationUi();return}if(e.state="finishing",stopDictationCapture(e),e.ws&&e.ws.readyState===WebSocket.OPEN){try{e.ws.send(JSON.stringify({type:"audio.done"}))}catch{}e.graceTimer=setTimeout(()=>finalizeDictation(e),DICT_FINAL_GRACE_MS),renderDictationUi()}else finalizeDictation(e)}}function stopDictationCapture(e){if(e.stream){for(const n of e.stream.getTracks())try{n.stop()}catch{}e.stream=null}if(e.ctx){try{e.ctx.close()}catch{}e.ctx=null}e.buffer=[],e.buffered=0}function teardownDictation(e){if(e.graceTimer&&(clearTimeout(e.graceTimer),e.graceTimer=null),stopDictationCapture(e),e.ws){e.ws.onmessage=null,e.ws.onclose=null,e.ws.onerror=null;try{e.ws.close(1e3)}catch{}e.ws=null}}function finalizeDictation(e){if(e!==currentDict())return;teardownDictation(e),applyDictationText(e);const n=e.pendingSubmit;_sess.dict=null,renderDictationUi(),n&&submitCurrentAnswer()}function failDictation(e,n,s="error"){postClientEvent("dictation_failed",{reason:s}),teardownDictation(e),e===currentDict()&&(applyDictationText(e),e.state="error",e.error=n,e.pendingSubmit=!1,renderDictationUi())}function abortDictation(){const e=currentDict();e&&(teardownDictation(e),_sess.dict=null)}
744function captureMaxPages(){return currentUser?.studyLimits?.maxPagesPerStudy??(currentUser?.isPremium?50:3)}function renderStudyPlanNotice(){return`<div class="rounded-[0.875rem] bg-surface-container-high p-4 mb-5 max-w-xl text-sm leading-relaxed">
745    <p class="font-medium text-on-surface">${t(currentUser?.isPremium?"ui.plan.premium":"ui.plan.free",{max:captureMaxPages()})}</p>
746    <p class="text-on-surface-muted mt-1">${t(currentUser?.isPremium?"ui.plan.reset":"ui.plan.demo")}</p>
747    ${currentUser?.isPremium?"":`<p class="text-primary mt-2">${t("ui.plan.premium_summary")}</p>`}
748  </div>`}const CAPTURE_JPEG_QUALITY=.85;let capture=null;async function startCapture(e="studies"){if(freeSlotUsed(currentUser)){_pendingPaywallSource="capture_blocked",showStudies();return}postClientEvent("capture_started",{from:e}),capture={stream:null,pages:[],meta:[],facing:"environment",from:e},renderSourcePicker()}const PASTE_MAX_CHARS=1e5;let _pasteDraft={title:"",body:"",studyLanguage:!1};function validatePastedText(e,n){const s=(e||"").trim();return s.length===0?{ok:!1,code:"empty",chars:0}:s.length>n?{ok:!1,code:"too_long",chars:s.length}:{ok:!0,code:null,chars:s.length}}function clipSaveOutcome(e){return e?e.reason==="upload_limit_reached"?"capped":e.error?"failed":"saved":"failed"}
748function renderSourcePicker(){postScreenView("capture");const e=document.getElementById("dashboard-content");e.innerHTML=`
749    <button id="picker-back" class="text-sm text-on-surface-muted hover:text-on-surface inline-flex items-center gap-1 transition mb-6">${t("ui.review.cancel")}</button>
750    <div>
751      <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.home.new_study_title")}</span>
752      <h1 class="font-display text-3xl font-semibold mb-3 mt-1 tracking-tight">${t("ui.capture.picker_title")}</h1>
753      <p class="text-on-surface-muted text-base leading-normal mb-8 max-w-prose">${t("ui.capture.picker_subtitle")}</p>
754    </div>
755    ${renderStudyPlanNotice()}
756    <div id="picker-error" class="hidden text-sm text-error bg-error-container/40 rounded-[0.75rem] p-3 mb-4"></div>
757    <div class="space-y-3 max-w-xl">
758      <button id="picker-camera" class="w-full text-left rounded-[1.25rem] p-6 bg-surface-container hover:bg-surface-container-high transition flex items-center gap-4">
759        <span class="w-12 h-12 rounded-full bg-surface-container-highest text-primary flex items-center justify-center shrink-0">
760          <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
761            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
762            <circle cx="12" cy="13" r="3.5" stroke-width="2"/>
763          </svg>
764        </span>
765        <span class="flex-1 min-w-0">
766          <span class="block font-display text-xl font-semibold tracking-tight">${t("ui.capture.picker_camera_title")}</span>
767          <span class="block text-sm mt-0.5 text-on-surface-muted">${t("ui.capture.picker_camera_sub")}</span>
768        </span>
769        ${chevronRightSvg("w-5 h-5 shrink-0 opacity-70")}
770      </button>
771      <button id="picker-pdf" class="w-full text-left rounded-[1.25rem] p-6 bg-surface-container hover:bg-surface-container-high transition flex items-center gap-4">
772        <span class="w-12 h-12 rounded-full bg-surface-container-highest text-primary flex items-center justify-center shrink-0">
773          <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
774            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"/>
775          </svg>
776        </span>
777        <span class="flex-1 min-w-0">
778          <span class="block font-display text-xl font-semibold tracking-tight">${t("ui.capture.picker_pdf_title")}</span>
779          <span class="block text-sm mt-0.5 text-on-surface-muted">${t("ui.capture.picker_pdf_sub")}</span>
780        </span>
781        ${chevronRightSvg("w-5 h-5 shrink-0 opacity-70")}
782      </button>
783      <button id="picker-text" class="w-full text-left rounded-[1.25rem] p-6 bg-surface-container hover:bg-surface-container-high transition flex items-center gap-4">
784        <span class="w-12 h-12 rounded-full bg-surface-container-highest text-primary flex items-center justify-center shrink-0">
785          <svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
786            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h10M4 18h7"/>
787          </svg>
788        </span>
789        <span class="flex-1 min-w-0">
790          <span class="block font-display text-xl font-semibold tracking-tight">${t("ui.capture.picker_text_title")}</span>
791          <span class="block text-sm mt-0.5 text-on-surface-muted">${t("ui.capture.picker_text_sub")}</span>
792        </span>
793        ${chevronRightSvg("w-5 h-5 shrink-0 opacity-70")}
794      </button>
795    </div>
796    <input type="file" id="picker-pdf-input" accept="application/pdf,.pdf" class="hidden">
797  `,document.getElementById("picker-back")?.addEventListener("click",()=>{if(capture?.pages.length&&!confirm(t("ui.review.discard_confirm")))return;postClientEvent("capture_cancelled",{pages:capture?.pages.length??0,at:"picker"});const s=capture?.from;stopStream(),capture=null,(s==="home"?showHome:showStudies)()}),document.getElementById("picker-camera")?.addEventListener("click",()=>{renderCaptureView()}),document.getElementById("picker-text")?.addEventListener("click",()=>{if(capture?.pages.length){if(!confirm(t("ui.review.discard_confirm")))return;postClientEvent("capture_cancelled",{pages:capture.pages.length,at:"picker"}),capture.pages=[],capture.meta=[]}renderPasteText()});const n=document.getElementById("picker-pdf-input");document.getElementById("picker-pdf")?.addEventListener("click",()=>n?.click()),n?.addEventListener("change",()=>{const s=n.files?.[0];
797n.value="",s&&handlePdfFile(s)})}function renderPasteText(){postScreenView("capture");const e=document.getElementById("dashboard-content");e.innerHTML=`
798    <div class="flex items-center justify-between mb-4 gap-3">
799      <button id="paste-back" class="text-sm text-on-surface-muted hover:text-on-surface inline-flex items-center gap-1 transition">${t("ui.review.cancel")}</button>
800      <button id="paste-save" class="bg-primary text-on-primary px-6 py-3 rounded-[0.875rem] font-display font-semibold hover:opacity-90 transition">${t("ui.review.save")}</button>
801    </div>
802    <h1 class="font-display text-3xl font-semibold mb-2 tracking-tight">${t("ui.paste.title")}</h1>
803    <p class="text-on-surface-muted text-sm mb-6">${t("ui.paste.subtitle")}</p>
804
805    <div id="paste-error" class="hidden text-sm text-error bg-error-container/40 rounded-[0.75rem] p-3 mb-4"></div>
806
807    <div class="max-w-xl">
808      <input id="paste-title" type="text" maxlength="200" placeholder="${t("ui.paste.title_placeholder")}" value="${escapeHtml(_pasteDraft.title)}"
809        class="w-full bg-surface-container rounded-[0.875rem] px-4 py-3 text-sm text-on-surface placeholder:text-on-surface-muted focus:outline-none mb-3">
810      <textarea id="paste-body" rows="12" placeholder="${t("ui.paste.body_placeholder")}"
811        class="w-full resize-y bg-surface-container rounded-[0.875rem] px-4 py-3 text-sm leading-relaxed text-on-surface placeholder:text-on-surface-muted focus:outline-none">${escapeHtml(_pasteDraft.body)}</textarea>
812      <p id="paste-count" class="text-xs text-on-surface-muted mt-2 mb-6 tabular-nums text-right"></p>
813
814      <label for="paste-study-language" class="flex items-start gap-3 mb-6 cursor-pointer select-none rounded-[0.875rem] bg-surface-container-high/40 p-4">
815        <input type="checkbox" id="paste-study-language" ${_pasteDraft.studyLanguage?"checked":""} class="mt-0.5 h-4 w-4 accent-primary shrink-0">
816        <span>
817          <span class="block text-sm font-medium text-on-surface">${t("ui.review.question_language")}</span>
818          <span class="block text-xs text-on-surface-muted mt-1">${t("ui.review.question_language_help")}</span>
819        </span>
820      </label>
821    </div>
822  `;const n=document.getElementById("paste-body"),s=document.getElementById("paste-title"),r=document.getElementById("paste-count");function a(){const i=n.value.trim().length;r.textContent=t("ui.paste.chars",{count:i,max:PASTE_MAX_CHARS}),r.classList.toggle("text-error",i>PASTE_MAX_CHARS),r.classList.toggle("text-on-surface-muted",i<=PASTE_MAX_CHARS)}a(),n.addEventListener("input",()=>{_pasteDraft.body=n.value,a()}),s.addEventListener("input",()=>{_pasteDraft.title=s.value}),document.getElementById("paste-back").addEventListener("click",()=>{(n.value.trim()||s.value.trim())&&!confirm(t("ui.paste.discard_confirm"))||(_pasteDraft={title:"",body:"",studyLanguage:!1},postClientEvent("capture_cancelled",{pages:0,at:"paste"}),renderSourcePicker())}),document.getElementById("paste-study-language").addEventListener("change",i=>{_pasteDraft.studyLanguage=i.target.checked}),document.getElementById("paste-save").addEventListener("click",savePastedStudy),n.focus()}async function savePastedStudy(){const e=document.getElementById("paste-body"),n=document.getElementById("paste-title"),s=document.getElementById("paste-error"),r=document.getElementById("paste-save");if(!e||!r||!s)return;const a=validatePastedText(e.value,PASTE_MAX_CHARS);if(!a.ok){s.textContent=a.code==="empty"?t("ui.paste.empty"):t("ui.paste.too_long",{max:PASTE_MAX_CHARS}),s.classList.remove("hidden");return}postClientEvent("capture_submitted",{pages:1,source:"text",text_pages:1}),s.classList.add("hidden"),r.disabled=!0,r.textContent=t("ui.review.saving");const i={title:(n?.value||"").trim(),pages:[{text:e.value.trim()}],question_language:_pasteDraft.studyLanguage?"source":currentLocale};try{const o=await api("/api/materials/clip",{method:"POST",body:i}),u=clipSaveOutcome(o);if(u!=="saved"){r.disabled=!1,r.textContent=t("ui.review.save"),s.textContent=o?.error||t("ui.review.save_failed"),s.classList.remove("hidden"),u==="capped"&&(_pendingPaywallSource="upload_rejected",capture=null,await api("/api/users/me").then(c=>{c&&!c.error&&(currentUser=c)}).catch(()=>{}),showStudies());return}capture=null,_pasteDraft={title:"",body:"",studyLanguage:!1},await api("/api/users/me").then(c=>{c&&!c.error&&(currentUser=c)}).catch(()=>{}),showStudies(),startStudiesPolling()}catch{r.disabled=!1,r.textContent=t("ui.review.save"),s.textContent=t("ui.review.save_failed"),s.classList.remove("hidden")}}async function renderCaptureView(){postScreenView("capture"),enterCaptureChrome();let e=document.getElementById("capture-overlay");if(e||(e=document.createElement("div"),e.id="capture-overlay",e.className="fixed inset-0 bg-black z-40",document.body.appendChild(e)),e.innerHTML='<div id="capture-host" class="absolute inset-0"></div>',!hasMediaDevices()){renderCameraUnavailable(void 0,"unsupported");return}await openCamera()}function enterCaptureChrome(){document.body.classList.add("capture-mode"),document.getElementById("app-nav")?.classList.add("hidden");const e=document.getElementById("dashboard-content");e?.classList.remove("py-8"),e?.classList.add("py-4")}function hasMediaDevices(){return!!(navigator.mediaDevices&&typeof navigator.mediaDevices.getUserMedia=="function")}function renderCameraUnavailable(e,n="error"){postClientEvent("camera_blocked",{reason:n});const s=document.getElementById("capture-host");s&&(s.innerHTML=`
823    <div class="absolute inset-0 flex flex-col items-center justify-center px-6 text-center">
824      <div class="w-14 h-14 bg-error-container rounded-full flex items-center justify-center mb-4">
825        <svg class="w-7 h-7 text-on-error-container" fill="none" stroke="currentColor" viewBox="0 0 24 24">
826          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
827            d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.
827664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"/>
828          <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3l18 18"/>
829        </svg>
830      </div>
831      <h3 class="font-display text-xl font-semibold mb-2 tracking-tight text-white">${t("ui.capture.unavailable_title")}</h3>
832      <p class="text-white/75 text-sm leading-relaxed max-w-sm">${e?escapeHtml(e):t("ui.capture.unavailable_body")}</p>
833      <button id="cap-cancel" class="mt-6 bg-white/10 hover:bg-white/15 text-white px-5 py-2.5 rounded-[0.875rem] font-medium transition ring-1 ring-white/20">${t("ui.capture.unavailable_back")}</button>
834    </div>
835  `,document.getElementById("cap-cancel")?.addEventListener("click",()=>{stopStream(),exitCaptureChrome(),renderSourcePicker()}))}async function openCamera(){const e=document.getElementById("capture-host");if(e){e.innerHTML=`<p class="absolute inset-0 flex items-center justify-center text-on-primary/70 text-sm">${t("ui.capture.starting")}</p>`;try{const n=await navigator.mediaDevices.getUserMedia({video:{facingMode:{ideal:capture.facing},width:{ideal:1920},height:{ideal:1920}},audio:!1});capture.stream=n}catch(n){const s=n?.name||"";s==="NotAllowedError"||s==="SecurityError"?renderCameraUnavailable(t("ui.capture.permission_denied"),"denied"):renderCameraUnavailable(t("ui.capture.error",{message:n?.message||s||"unknown"}),"error");return}paintViewfinder()}}function paintViewfinder(){const e=document.getElementById("capture-host");if(!e)return;const n=captureMaxPages()-capture.pages.length,s=n<=0;e.innerHTML=`
836    <video id="capture-video" autoplay playsinline muted class="absolute inset-0 w-full h-full object-cover"></video>
837
838    <div class="absolute top-0 left-0 right-0 z-10 px-4 pb-4 bg-black/75 backdrop-blur-md"
839         style="padding-top: max(env(safe-area-inset-top, 0px), 1rem);">
840      <div class="max-w-3xl mx-auto">
841        <div class="flex items-center justify-between gap-3 mb-2">
842          <button id="capture-cancel-top" class="text-white text-sm font-medium hover:opacity-80 transition drop-shadow">${t("ui.review.cancel")}</button>
843          <div class="flex items-center gap-2 text-[0.65rem] uppercase tracking-wider font-mono">
844            <span class="bg-black/70 text-white px-2 py-1 rounded ring-1 ring-white/15">${t("ui.capture.cap_remaining",{remaining:Math.max(0,n)})}</span>
845            <button id="cap-flip" class="bg-black/70 text-white px-2 py-1 rounded hover:bg-black/85 transition ring-1 ring-white/15">${t("ui.capture.flip")}</button>
846          </div>
847        </div>
848        <h1 class="font-display text-lg sm:text-2xl font-semibold tracking-tight text-white drop-shadow">${t("ui.capture.title")}</h1>
849        <p class="text-white/85 text-xs sm:text-sm mt-0.5 drop-shadow">${t("ui.capture.hint")}</p>
850        <p class="text-white/70 text-xs sm:text-sm mt-0.5 drop-shadow">${t("ui.capture.tip")}</p>
851      </div>
852    </div>
853
854    <div class="absolute bottom-0 left-0 right-0 z-10 px-4 pt-4 bg-black/75 backdrop-blur-md"
855         style="padding-bottom: max(env(safe-area-inset-bottom, 0px), 1.25rem);">
856      <div class="max-w-3xl mx-auto">
857        <div id="cap-thumbs" class="flex gap-1.5 mb-3 overflow-x-auto"></div>
858        ${s?`<p class="mb-2 text-center text-sm text-error">${t("ui.capture.cap_reached",{max:captureMaxPages()})}</p>`:""}
859        <div class="flex items-center justify-between gap-3">
860          <button id="cap-cancel" class="bg-black/70 text-white px-4 py-2.5 rounded-full font-medium hover:bg-black/85 transition backdrop-blur-md ring-1 ring-white/20 shelf-shadow">${t("ui.capture.cancel")}</button>
861          <button id="cap-shutter" ${s?"disabled":""}
862                  class="bg-white w-16 h-16 sm:w-20 sm:h-20 rounded-full font-display font-semibold disabled:opacity-40 disabled:cursor-not-allowed shelf-shadow hover:opacity-90 transition flex items-center justify-center shrink-0 ring-2 ring-white/40 ring-offset-4 ring-offset-black/20"
863                  aria-label="${t("ui.capture.shutter")}">
864            <span class="w-12 h-12 sm:w-14 sm:h-14 rounded-full bg-white" style="border: 2px solid #1a1a1a;"></span>
865          </button>
866          <button id="cap-done" ${capture.pages.length===0?"disabled":""}
867                  class="bg-primary text-white px-4 py-2.5 rounded-full font-display font-semibold disabled:opacity-40 disabled:cursor-not-allowed hover:opacity-90 transition ring-1 ring-white/30 shelf-shadow">
868            ${t("ui.capture.done",{count:capture.pages.length})}
869          </button>
870        </div>
871      </div>
872    </div>
873  `;const r=document.getElementById("capture-video");capture.stream&&(r.srcObject=capture.stream),document.getElementById("cap-shutter").addEventListener("click",
873shutterClick),document.getElementById("cap-cancel").addEventListener("click",()=>cancelCapture("viewfinder")),document.getElementById("capture-cancel-top").addEventListener("click",()=>cancelCapture("viewfinder")),document.getElementById("cap-done").addEventListener("click",goToReview),document.getElementById("cap-flip").addEventListener("click",flipCamera),renderCaptureThumbs()}async function shutterClick(){const e=document.getElementById("capture-video");if(!e||!capture?.stream||capture.pages.length>=captureMaxPages())return;const n=e.videoWidth,s=e.videoHeight;if(!n||!s)return;const r=document.createElement("canvas");r.width=n,r.height=s,r.getContext("2d").drawImage(e,0,0,n,s);const i=await new Promise(g=>r.toBlob(g,"image/jpeg",CAPTURE_JPEG_QUALITY));if(!i)return;if(capture.pages.push(i),capture.meta.push({text:null,source:"camera"}),navigator.vibrate)try{navigator.vibrate(15)}catch{}const o=document.getElementById("capture-host");if(o){const g=document.createElement("div");g.className="absolute inset-0 z-40 bg-white pointer-events-none shutter-flash",o.appendChild(g),setTimeout(()=>g.remove(),280);const h=document.createElement("div");h.className="absolute left-1/2 top-1/2 z-50 pointer-events-none shutter-confirm flex items-center gap-2 bg-black/75 text-white rounded-full pl-3 pr-4 py-2 backdrop-blur-md ring-1 ring-white/20 shelf-shadow font-display font-semibold text-base",h.innerHTML=`<svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"></polyline></svg><span>${capture.pages.length}</span>`,o.appendChild(h),setTimeout(()=>h.remove(),800)}const u=captureMaxPages()-capture.pages.length,c=u<=0,l=document.querySelector("#capture-host .bg-black\\/60.text-on-primary.px-2.py-1.rounded");l&&(l.textContent=t("ui.capture.cap_remaining",{remaining:Math.max(0,u)}));const d=document.getElementById("cap-done");d&&(d.disabled=capture.pages.length===0,d.textContent=t("ui.capture.done",{count:capture.pages.length}));const m=document.getElementById("cap-shutter");m&&(m.disabled=c),renderCaptureThumbs()}function renderCaptureThumbs(){const e=document.getElementById("cap-thumbs");e&&(e.innerHTML="",capture.pages.forEach((n,s)=>{const r=URL.createObjectURL(n),a=document.createElement("div");a.className="relative group rounded-[0.625rem] overflow-hidden bg-surface-container-high aspect-square w-12 sm:w-14 shrink-0",a.innerHTML=`
874      <img src="${r}" class="w-full h-full object-cover" alt="">
875      <span class="absolute bottom-0 left-0 right-0 bg-surface/85 text-on-surface text-[10px] px-1.5 py-0.5 font-mono">${s+1}</span>
876      <button data-rm="${s}" aria-label="${t("ui.review.remove_page")}"
877              class="absolute top-1 right-1 w-5 h-5 rounded-full bg-surface/85 text-on-surface text-xs hover:bg-error hover:text-on-primary transition flex items-center justify-center">\xD7</button>
878    `,e.appendChild(a)}),e.querySelectorAll("[data-rm]").forEach(n=>{n.addEventListener("click",()=>{const s=parseInt(n.getAttribute("data-rm"),10);capture.pages.splice(s,1),capture.meta.splice(s,1),paintViewfinder()})}))}async function flipCamera(){capture&&(capture.facing=capture.facing==="environment"?"user":"environment",stopStream(),await openCamera())}function stopStream(){if(capture?.stream){for(const e of capture.stream.getTracks())e.stop();capture.stream=null}}function cancelCapture(e="viewfinder"){capture?.pages.length&&!confirm(t("ui.review.discard_confirm"))||(postClientEvent("capture_cancelled",{pages:capture?.pages.length??0,at:e}),stopStream(),capture=null,exitCaptureChrome(),showStudies())}const PDFJS_LIB_URL="/assets/pdfjs/pdf.min.mjs",PDFJS_WORKER_URL="/assets/pdfjs/pdf.worker.min.mjs",PDF_RENDER_MAX_DIM=1600,PDF_TEXT_MIN_CHARS=64,PDF_TEXT_MAX_CHARS=1e5,PDF_SCAN_TEXT_MAX_CHARS=800,PDF_PICKER_MAX_PAGES=2e3,PDF_THUMB_MAX_DIM=240,PDF_THUMB_QUALITY=.6,PDF_PREVIEW_MAX_DIM=1400,PDF_PREVIEW_QUALITY=.82,PDF_OUTLINE_MAX_ENTRIES=400;let _pdfjsPromise=null;function loadPdfjs(){return _pdfjsPromise||(_pdfjsPromise=import(PDFJS_LIB_URL).then(e=>
878(e.GlobalWorkerOptions.workerSrc=PDFJS_WORKER_URL,e)),_pdfjsPromise.catch(()=>{_pdfjsPromise=null})),_pdfjsPromise}function renderPdfProgress(){const e=document.getElementById("dashboard-content");e.innerHTML=`
879    <div id="pdf-progress" class="py-16 text-center">
880      <p class="text-on-surface-muted text-sm">${t("ui.capture.pdf_reading")}</p>
881    </div>`}function updatePdfProgress(e,n){const s=document.querySelector("#pdf-progress p");s&&(s.textContent=t("ui.capture.pdf_preparing",{done:e,total:n}))}async function renderPdfPageBlob(e,n,{maxDim:s,quality:r,maxScale:a=1}){const i=await e.getPage(n);try{const o=i.getViewport({scale:1}),u=Math.min(a,s/Math.max(o.width,o.height)),c=i.getViewport({scale:u}),l=document.createElement("canvas");l.width=Math.ceil(c.width),l.height=Math.ceil(c.height);const d=l.getContext("2d");d.fillStyle="#ffffff",d.fillRect(0,0,l.width,l.height),await i.render({canvasContext:d,viewport:c}).promise;const m=await new Promise(g=>l.toBlob(g,"image/jpeg",r));return l.width=l.height=0,m}finally{i.cleanup()}}async function resolveOutlineDest(e,n){try{const s=typeof n=="string"?await e.getDestination(n):n;if(!Array.isArray(s)||s.length===0)return null;const r=s[0];return Number.isInteger(r)?r+1:!r||typeof r!="object"?null:await e.getPageIndex(r)+1}catch{return null}}function buildPdfChapterRanges(e,n){const s=e.map(r=>({title:String(r.title||"").trim(),start:r.start,children:Array.isArray(r.children)?r.children:[]})).filter(r=>Number.isInteger(r.start)&&r.start>=1&&r.start<=n).sort((r,a)=>r.start-a.start);return s.map((r,a)=>{const i=a+1<s.length?s[a+1].start:n+1,o=Math.max(r.start,Math.min(n,i-1)),u=r.children.map(l=>({title:String(l.title||"").trim(),start:l.start})).filter(l=>Number.isInteger(l.start)&&l.start>=r.start&&l.start<=o).sort((l,d)=>l.start-d.start),c=u.map((l,d)=>{const m=d+1<u.length?u[d+1].start:o+1;return{title:l.title,start:l.start,end:Math.max(l.start,Math.min(o,m-1))}}).filter(l=>l.title!=="");return{title:r.title,start:r.start,end:o,children:c}}).filter(r=>r.title!=="")}async function loadPdfChapters(e,n){let s;try{s=await e.getOutline()}catch{return[]}if(!Array.isArray(s)||s.length===0)return[];let r=s;r.length===1&&Array.isArray(r[0].items)&&r[0].items.length>1&&(r=r[0].items);const a=r.slice(0,PDF_OUTLINE_MAX_ENTRIES),i=a.map(l=>({title:l.title,start:null,children:[]})),o=a.map((l,d)=>({src:l,slot:i[d]}));let u=PDF_OUTLINE_MAX_ENTRIES-o.length;a.forEach((l,d)=>{const m=l.items||[];if(!(m.length===0||m.length>u)){u-=m.length;for(const g of m){const h={title:g.title,start:null};i[d].children.push(h),o.push({src:g,slot:h})}}}),await Promise.all(o.map(async({src:l,slot:d})=>{d.start=await resolveOutlineDest(e,l.dest)}));const c=buildPdfChapterRanges(i,n);return r.length>a.length?c.slice(0,-1):c}function pickPdfPages(e,n,s){const r=e.numPages,a=r<=n,i=new Set;if(a)for(let o=1;o<=r;o++)i.add(o);return new Promise(o=>{const u=document.getElementById("dashboard-content");u.innerHTML=`
882      <div id="pdf-picker">
883        <button id="pdfpick-back" class="text-sm text-on-surface-muted hover:text-on-surface inline-flex items-center gap-1 transition mb-6">${t("ui.review.cancel")}</button>
884        <div>
885          <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.home.new_study_title")}</span>
886          <h1 class="font-display text-3xl font-semibold mb-2 mt-1 tracking-tight">${t("ui.capture.pdf_pick_title")}</h1>
887          <p class="text-on-surface-muted text-sm mb-6">${t("ui.capture.pdf_pick_subtitle",{total:r,max:n})}</p>
888        </div>
889
890        <div class="flex flex-wrap items-center gap-2 mb-3">
891          <button id="pdfpick-all" ${a?"":"disabled"}
892                  class="px-3 py-2 rounded-[0.75rem] text-sm font-medium bg-surface-container hover:bg-surface-container-high transition disabled:opacity-40 disabled:cursor-not-allowed disabled:hover:bg-surface-container">${t("ui.capture.pdf_pick_all")}</button>
893          <button id="pdfpick-clear" class="px-3 py-2 rounded-[0.75rem] text-sm font-medium bg-surface-container hover:bg-surface-container-high transition">${t("ui.capture.pdf_pick_clear")}</button>
894          <span class="flex flex-wrap items-center gap-2 w-full sm:w-auto sm:ms-auto">
895            <label class="text-sm text-on-surface-muted" for="pdfpick-from">${t("ui.capture.pdf_pick_range_from")}</label>
896            <input id="pdfpick-from" type="number" inputmode="numeric" min="1" max="${r}" value="1"
897                   class="w-20 px-2 py-2 rounded-[0.75rem] bg-surface-container text-on-surface text-sm text-center">
898            <label class="text-sm text-on-surface-muted" for="pdfpick-to">${t("ui.capture.pdf_pick_range_to")}</label>
899            <input id="pdfpick-to" type="number" inputmode="numeric" min="1" max="${r}" value="${Math.min(r,n)}"
900                   class="w-20 px-2 py-2 rounded-[0.75rem] bg-surface-container text-on-surface text-sm text-center">
901            <button id="pdfpick-range" class="px-3 py-2 rounded-[0.75rem] text-sm font-medium bg-surface-container hover:bg-surface-container-high transition">${t("ui.capture.pdf_pick_range_add")}</button>
902          </span>
903        </div>
904
905        <p id="pdfpick-hint" class="text-sm text-on-surface-muted mb-4 min-h-[1.25rem]">${a?"":t("ui.capture.pdf_pick_all_disabled",{max:n})}</p>
906
907        <div id="pdfpick-chapters" class="hidden mb-4">
908          <button type="button" id="pdfpick-chapters-toggle" aria-expanded="true" aria-controls="pdfpick-chapters-list"
909                  class="inline-flex items-center gap-2 px-3 py-2 rounded-[0.75rem] text-sm font-medium bg-surface-container hover:bg-surface-container-high transition">
910            <svg data-chevron class="w-4 h-4 rotate-180 transition-transform" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m6 9 6 6 6-6"/></svg>
911            <span>${t("ui.capture.pdf_pick_chapters")}</span>
912            <span id="pdfpick-chapters-count" class="text-on-surface-muted"></span>
913          </button>
914          <div id="pdfpick-chapters-list" class="mt-2 max-h-56 sm:max-h-72 overflow-y-auto rounded-[0.75rem] bg-surface-container p-1 space-y-0.5"></div>
915        </div>
916
917        <div id="pdfpick-grid" class="grid grid-cols-3 sm:grid-cols-4 gap-2 mb-4"></div>
918
919        <div class="sticky bottom-0 -mx-6 px-6 py-4 bg-surface/95 backdrop-blur flex items-center justify-between gap-3" style="border-top: 1px solid var(--color-outline-variant);">
920          <span id="pdfpick-count" class="text-sm font-mono text-on-surface-muted"></span>
921          <button id="pdfpick-import" class="bg-primary text-on-primary px-6 py-3 rounded-[0.875rem] font-display font-semibold hover:opacity-90 transition disabled:opacity-40 disabled:cursor-not-allowed">${t("ui.capture.pdf_pick_import")}</button>
922        </div>
923      </div>
924    `;const c=document.getElementById("pdfpick-grid"),l=document.getElementById("pdfpick-hint"),d=document.getElementById("pdfpick-count"),m=document.getElementById("pdfpick-import"),g=document.createDocumentFragment();for(let p=1;p<=r;p++){const f=document.createElement("div");f.dataset.page=String(p),f.className="relative rounded-[0.75rem] overflow-hidden bg-surface-container-high aspect-[3/4] ring-2 ring-transparent transition",f.innerHTML=`
925        <button type="button" data-select aria-pressed="false" aria-label="${escapeHtml(t("ui.capture.pdf_pick_page",{n:p}))}"
926                class="absolute inset-0 w-full h-full">
927          <img alt="" class="w-full h-full object-contain opacity-0 transition-opacity duration-200">
928        </button>
929        <span class="pointer-events-none absolute bottom-0 left-0 right-0 bg-surface/85 text-on-surface text-[10px] px-2 py-1 font-mono text-left">${p}</span>
930        <span data-check class="pointer-events-none absolute top-1 right-1 w-6 h-6 rounded-full bg-primary text-on-primary text-sm hidden items-center justify-center">\u2713</span>
931        <button type="button" data-zoom aria-label="${escapeHtml(t("ui.capture.pdf_pick_preview",{n:p}))}"
932                class="absolute top-1 left-1 w-8 h-8 rounded-full bg-surface/85 hover:bg-surface text-on-surface flex items-center justify-center transition">
933          <svg class="w-4 h-4" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><circle cx="11" cy="11" r="7"/><path stroke-linecap="round" d="m20 20-3.5-3.5"/></svg>
934        </button>
935      `,g.appendChild(f)}c.appendChild(g);function h(p){const f=i.has(Number(p.dataset.page));p.querySelector("[data-select]").setAttribute("aria-pressed",f?"true":"false"),p.classList.toggle("ring-primary",f),p.classList.toggle("ring-transparent",!f);const x=p.querySelector("[data-check]");x.classList.toggle("hidden",!f),x.classList.toggle("flex",f)}function C(){d.textContent=t("ui.capture.pdf_pick_count",{selected:i.size,max:n}),m.disabled=i.size===0}function I(){for(const p of c.children)h(p);C(),$&&W()}function L(p){l.textContent=p}function y(p){if(i.has(p))i.delete(p),L("");else{if(i.size>=n)return L(t("ui.capture.pdf_pick_cap",{max:n})),!1;i.add(p)}return h(c.children[p-1]),C(),!0}function _(p,f,x){let v=!1;for(let k=p;k<=f;k++){if(!i.has(k)&&i.size>=n){v=!0;break}i.add(k)}L(v?t("ui.capture.pdf_pick_cap",{max:n}):""),I(),x&&c.children[p-1]?.scrollIntoView({block:"center"})}c.addEventListener("click",p=>{const f=p.target.closest("[data-page]");if(!f)return;const x=Number(f.dataset.page),v=p.target.closest("[data-zoom]");if(v){P(x,v);return}y(x)}),document.getElementById("pdfpick-all").addEventListener("click",()=>{for(let p=1;p<=r;p++)i.add(p);L(""),I()}),document.getElementById("pdfpick-clear").addEventListener("click",()=>{i.clear(),L(""),I()}),document.getElementById("pdfpick-range").addEventListener("click",()=>{const p=parseInt(document.getElementById("pdfpick-from").value,10),f=parseInt(document.getElementById("pdfpick-to").value,10);if(!Number.isInteger(p)||!Number.isInteger(f)||Math.max(p,f)<1||Math.min(p,f)>r){L(t("ui.capture.pdf_pick_range_invalid",{total:r}));return}const x=Math.max(1,Math.min(p,f)),v=Math.min(r,Math.max(p,f));_(x,v,!1)});let w=!1;const B=[];let A=!1,$=null,j=null,D=0,q=null,U=null,H=[];const N=new IntersectionObserver(p=>{for(const f of p)f.isIntersecting&&(N.unobserve(f.target),B.push(f.target));M()},{rootMargin:"400px 0px"});let b=Promise.resolve();function S(p){const f=b.then(p,p);return b=f.catch(()=>{}),f}async function M(){if(!A){for(A=!0;B.length&&!w&&!s()&&j===null;){const p=B.pop(),f=await S(()=>w||s()?null:renderPdfPageBlob(e,Number(p.dataset.page),{maxDim:PDF_THUMB_MAX_DIM,quality:PDF_THUMB_QUALITY})).catch(()=>null);if(!f)continue;const x=p.querySelector("img"),v=URL.createObjectURL(f);x.onload=()=>{URL.revokeObjectURL(v),x.classList.remove("opacity-0")},x.onerror=()=>URL.revokeObjectURL(v),x.src=v}A=!1}}function P(p,f){if($)return;j=p,U=f||null,$=document.createElement("div"),$.id="pdfpick-preview",$.className="fixed inset-0 h-[100dvh] z-[70] bg-surface flex flex-col",$.setAttribute("role","dialog"),$.setAttribute("aria-modal","true"),$.setAttribute("aria-label",t("ui.capture.pdf_pick_preview_title")),$.innerHTML=`
936        <div class="flex items-center justify-between gap-3 px-4 py-3 shrink-0" style="border-bottom: 1px solid var(--color-outline-variant);">
937          <span id="pdfpick-preview-label" class="font-mono text-sm text-on-surface-muted"></span>
938          <button type="button" id="pdfpick-preview-close" aria-label="${escapeHtml(t("ui.capture.pdf_pick_preview_close"))}"
939                  class="w-10 h-10 rounded-full bg-surface-container hover:bg-surface-container-high text-on-surface flex items-center justify-center transition shrink-0">
940            <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" d="M6 6l12 12M18 6L6 18"/></svg>
941          </button>
942        </div>
943        <div id="pdfpick-preview-stage" class="relative flex-1 min-h-0 flex items-center justify-center p-3">
944          <div id="pdfpick-preview-skeleton" data-preview-content class="absolute w-2/3 h-2/3 rounded-[0.5rem] bg-surface-container animate-pulse"></div>
945          <img id="pdfpick-preview-img" data-preview-content alt="" class="relative max-w-full max-h-full object-contain rounded-[0.5rem] opacity-0 transition-opacity duration-200">
946        </div>
947        <p id="pdfpick-preview-hint" class="text-center text-sm text-on-surface-muted px-4 min-h-[1.25rem]"></p>
948        <div class="shrink-0 px-4 py-4 flex items-center justify-center gap-2 sm:gap-3">
949          <button type="button" id="pdfpick-preview-prev" aria-label="${escapeHtml(t("ui.capture.pdf_pick_preview_prev"))}"
950                  class="w-12 h-12 rounded-full bg-surface-container hover:bg-surface-container-high text-on-surface flex items-center justify-center shrink-0 transition disabled:opacity-40 disabled:cursor-not-allowed">
951            <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m15 18-6-6 6-6"/></svg>
952          </button>
953          <button type="button" id="pdfpick-preview-toggle"
954                  class="flex-1 sm:flex-none sm:min-w-[14rem] px-6 py-3 rounded-[0.875rem] font-display font-semibold transition"></button>
955          <button type="button" id="pdfpick-preview-next" aria-label="${escapeHtml(t("ui.capture.pdf_pick_preview_next"))}"
956                  class="w-12 h-12 rounded-full bg-surface-container hover:bg-surface-container-high text-on-surface flex items-center justify-center shrink-0 transition disabled:opacity-40 disabled:cursor-not-allowed">
957            <svg class="w-5 h-5" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m9 18 6-6-6-6"/></svg>
958          </button>
959        </div>
960      `,document.body.appendChild($),document.body.classList.add("overflow-hidden");for(const E of document.body.children)E!==$&&(E.inert=!0,H.push(E));document.getElementById("pdfpick-preview-close").addEventListener("click",R),document.getElementById("pdfpick-preview-prev").addEventListener("click",()=>F(-1)),document.getElementById("pdfpick-preview-next").addEventListener("click",()=>F(1)),document.getElementById("pdfpick-preview-toggle").addEventListener("click",()=>{if(!y(j)){document.getElementById("pdfpick-preview-hint").textContent=t("ui.capture.pdf_pick_cap",{max:n}),W();return}R()}),document.addEventListener("keydown",z);const x=document.getElementById("pdfpick-preview-stage");let v=null,k=0,T=-1/0;x.addEventListener("touchstart",E=>{if(E.touches.length!==1){v=null;return}v=E.touches[0].clientX,k=E.touches[0].clientY},{passive:!0}),x.addEventListener("touchend",E=>{if(v===null){T=E.timeStamp;return}const O=E.changedTouches[0].clientX-v,V=E.changedTouches[0].clientY-k;v=null,(Math.abs(O)>10||Math.abs(V)>10)&&(T=E.timeStamp),!(Math.abs(O)<48||Math.abs(O)<Math.abs(V)*1.5)&&F(O<0?1:-1)},{passive:!0}),$.addEventListener("touchstart",E=>{if(E.touches.length===1){T=-1/0;return}v=null,T=E.timeStamp},{passive:!0}),$.addEventListener("click",E=>{E.timeStamp-T<400||E.target.closest("button, [data-preview-content]")||R()}),document.getElementById("pdfpick-preview-close").focus(),G(p)}function R(){if(!$)return;document.removeEventListener("keydown",z),q&&(URL.revokeObjectURL(q),q=null);const p=c.children[j-1]?.querySelector("[data-zoom]")||U;$.remove();for(const f of H)f.inert=!1;H=[],$=null,j=null,D++,document.body.classList.remove("overflow-hidden"),p?.focus(),U=null,M()}function z(p){if(p.key==="Escape"){R();return}if(p.key==="ArrowLeft"){p.preventDefault(),F(-1);return}p.key==="ArrowRight"&&(p.preventDefault(),F(1))}function F(p){if(j===null)return;const f=j+p;f<1||f>r||G(f)}function W(){const p=document.getElementById("pdfpick-preview-toggle");if(!p)return;const f=i.has(j);p.textContent=t(f?"ui.capture.pdf_pick_preview_selected":"ui.capture.pdf_pick_preview_select"),p.setAttribute("aria-pressed",f?"true":"false"),p.classList.toggle("bg-primary",!f),p.classList.toggle("text-on-primary",!f),p.classList.toggle("bg-surface-container",f),p.classList.toggle("text-on-surface",f)}async function G(p){j=p;const f=++D,x=document.getElementById("pdfpick-preview-img"),v=document.getElementById("pdfpick-preview-skeleton");document.getElementById("pdfpick-preview-label").textContent=t("ui.capture.pdf_pick_preview_position",{n:p,total:r}),document.getElementById("pdfpick-preview-prev").disabled=p<=1,document.getElementById("pdfpick-preview-next").disabled=p>=r,document.getElementById("pdfpick-preview-hint").textContent="",W(),x.classList.add("opacity-0"),v.classList.remove("hidden");const k=await S(()=>w||!$||f!==D?null:renderPdfPageBlob(e,p,{maxDim:PDF_PREVIEW_MAX_DIM,quality:PDF_PREVIEW_QUALITY,maxScale:2})).catch(()=>null);if(w||!$||f!==D)return;if(!k){v.classList.add("hidden");return}q&&URL.revokeObjectURL(q);const T=URL.createObjectURL(k);q=T;const E=O=>{q===T&&(q=null),URL.revokeObjectURL(T),f===D&&(v.classList.add("hidden"),O&&x.classList.remove("opacity-0"))};x.onload=()=>E(!0),x.onerror=()=>E(!1),x.src=T}function X(p,f){const x=p.start===p.end?String(p.start):`${p.start}\u2013${p.end}`,v=p.children.length>=2||p.children.length===1&&p.children[0].start>p.start;return`
961        <div>
962          <div class="flex items-stretch gap-1">
963            <button type="button" data-ch="${f}" title="${escapeHtml(p.title)}"
964                    aria-label="${escapeHtml(t("ui.capture.pdf_pick_chapter_add",{title:p.title,from:p.start,to:p.end}))}"
965                    class="flex-1 min-w-0 text-start px-3 py-2 rounded-[0.5rem] hover:bg-surface-container-high transition flex items-baseline justify-between gap-3">
966              <span class="text-sm truncate">${escapeHtml(p.title)}</span>
967              <span class="text-xs font-mono text-on-surface-muted shrink-0">${x}</span>
968            </button>
969            ${v?`
970            <button type="button" data-ch-expand="${f}" aria-expanded="false" aria-controls="pdfpick-ch-kids-${f}"
971                    aria-label="${escapeHtml(t("ui.capture.pdf_pick_chapter_expand",{title:p.title}))}"
972                    class="w-9 shrink-0 rounded-[0.5rem] text-on-surface-muted hover:bg-surface-container-high hover:text-on-surface transition flex items-center justify-center">
973              <svg data-chevron class="w-4 h-4 transition-transform" fill="none" stroke="currentColor" stroke-width="2" viewBox="0 0 24 24" aria-hidden="true"><path stroke-linecap="round" stroke-linejoin="round" d="m6 9 6 6 6-6"/></svg>
974            </button>`:""}
975          </div>
976          ${v?`
977          <div id="pdfpick-ch-kids-${f}" class="hidden ps-4 mt-0.5 space-y-0.5">
978            ${p.children.map((k,T)=>`
979            <button type="button" data-ch="${f}" data-ch-kid="${T}" title="${escapeHtml(k.title)}"
980                    aria-label="${escapeHtml(t("ui.capture.pdf_pick_chapter_add",{title:k.title,from:k.start,to:k.end}))}"
981                    class="w-full min-w-0 text-start px-3 py-2 rounded-[0.5rem] hover:bg-surface-container-high transition flex items-baseline justify-between gap-3">
982              <span class="text-sm text-on-surface-muted truncate">${escapeHtml(k.title)}</span>
983              <span class="text-xs font-mono text-on-surface-muted shrink-0">${k.start===k.end?k.start:`${k.start}\u2013${k.end}`}</span>
984            </button>`).join("")}
985          </div>`:""}
986        </div>
987      `}function Y(p){const f=document.getElementById("pdfpick-chapters"),x=document.getElementById("pdfpick-chapters-list"),v=document.getElementById("pdfpick-chapters-toggle");!f||!x||!v||(document.getElementById("pdfpick-chapters-count").textContent=t("ui.capture.pdf_pick_chapters_count",{count:p.length}),x.innerHTML=p.map(X).join(""),f.classList.remove("hidden"),v.addEventListener("click",()=>{const k=!x.classList.toggle("hidden");v.setAttribute("aria-expanded",k?"true":"false"),v.querySelector("[data-chevron]").classList.toggle("rotate-180",k)}),x.addEventListener("click",k=>{const T=k.target.closest("[data-ch-expand]");if(T){const K=!document.getElementById(`pdfpick-ch-kids-${T.dataset.chExpand}`).classList.toggle("hidden");T.setAttribute("aria-expanded",K?"true":"false"),T.querySelector("[data-chevron]").classList.toggle("rotate-180",K);return}const E=k.target.closest("[data-ch]");if(!E)return;const O=p[Number(E.dataset.ch)],V=E.dataset.chKid===void 0?O:O.children[Number(E.dataset.chKid)];_(V.start,V.end,!0)}))}for(const p of c.children)N.observe(p);const J=setInterval(()=>{s()&&Q(null)},1e3);function Q(p){w||(w=!0,R(),clearInterval(J),N.disconnect(),b.then(()=>o(p)))}document.getElementById("pdfpick-back").addEventListener("click",()=>Q(null)),m.addEventListener("click",()=>{i.size!==0&&Q([...i].sort((p,f)=>p-f))}),I(),loadPdfChapters(e,r).then(p=>{w||s()||p.length===0||Y(p)}).catch(()=>{})})}async function pdfPageHasImage(e,n){try{const s=await e.getOperatorList(),r=new Set([n.OPS.paintImageXObject,n.OPS.paintImageXObjectRepeat,n.OPS.paintInlineImageXObject]);return(s?.fnArray||[]).some(a=>r.has(a))}catch{return!1}}function pdfImportError(e,n,s){if(postClientEvent("pdf_import_failed",{reason:n}),s)return;renderSourcePicker();const r=document.getElementById("picker-error");r&&(r.textContent=e,r.classList.remove("hidden"))}async function handlePdfFile(e){const n=capture;let s="pdf-progress";const r=()=>capture!==n||!document.getElementById(s);renderPdfProgress();let a,i;try{const u=await loadPdfjs(),c=new Uint8Array(await e.arrayBuffer());i=u.getDocument({data:c,isEvalSupported:!1,cMapUrl:"/assets/pdfjs/cmaps/",cMapPacked:!0,standardFontDataUrl:"/assets/pdfjs/standard_fonts/",wasmUrl:"/assets/pdfjs/wasm/",iccUrl:"/assets/pdfjs/iccs/"}),a=await i.promise}catch(u){i?.destroy().catch(()=>{});const c=u?.name==="PasswordException";pdfImportError(t(c?"ui.capture.pdf_password":"ui.capture.pdf_failed"),c?"password":"parse_error",r());return}const o=n.pages.length;try{if(a.numPages===0){pdfImportError(t("ui.capture.pdf_empty"),"empty",r());return}const u=captureMaxPages()-o;if(u<=0){pdfImportError(t("ui.capture.cap_reached",{max:captureMaxPages()}),"cap_reached",r());return}if(a.numPages>PDF_PICKER_MAX_PAGES){pdfImportError(t("ui.capture.pdf_too_many_pages",{count:a.numPages,max:u}),"too_many_pages",r());return}if(r())return;s="pdf-picker";const c=await pickPdfPages(a,u,r);if(r())return;
987if(!c){renderSourcePicker();return}postClientEvent("pdf_pages_selected",{selected:c.length,total:a.numPages}),s="pdf-progress",renderPdfProgress();let l=0;for(const d of c){if(r())return;updatePdfProgress(++l,c.length);const m=await a.getPage(d),g=await m.getTextContent();let h="";for(const $ of g.items)h+=$.str,h+=$.hasEOL?`
988`:" ";h=h.replace(/[ \t]+\n/g,`
989`).replace(/[ \t]{2,}/g," ").trim();const C=h.replace(/\s+/g,"").length;let I=C>=PDF_TEXT_MIN_CHARS;I&&C<PDF_SCAN_TEXT_MAX_CHARS&&await pdfPageHasImage(m,pdfjs)&&(I=!1);const L=m.getViewport({scale:1}),y=Math.min(2,PDF_RENDER_MAX_DIM/Math.max(L.width,L.height)),_=m.getViewport({scale:y}),w=document.createElement("canvas");w.width=Math.ceil(_.width),w.height=Math.ceil(_.height);const B=w.getContext("2d");B.fillStyle="#ffffff",B.fillRect(0,0,w.width,w.height),await m.render({canvasContext:B,viewport:_}).promise;const A=await new Promise($=>w.toBlob($,"image/jpeg",CAPTURE_JPEG_QUALITY));if(w.width=w.height=0,m.cleanup(),!A)throw new Error("page render produced no image");n.pages.push(A),n.meta.push({text:I?h:null,source:"pdf"})}if(r())return;renderReview()}catch{n.pages.length=o,n.meta.length=o,pdfImportError(t("ui.capture.pdf_failed"),"parse_error",r())}finally{i.destroy().catch(()=>{})}}function goToReview(){!capture||capture.pages.length===0||(stopStream(),exitCaptureChrome(),renderReview())}function exitCaptureChrome(){document.body.classList.remove("capture-mode"),document.getElementById("app-nav")?.classList.remove("hidden");const e=document.getElementById("dashboard-content");e?.classList.remove("py-4"),e?.classList.add("py-8"),document.getElementById("capture-overlay")?.remove()}const REVIEW_QUESTION_TYPES=[["scq","ui.qtypes.scq.title"],["mcq","ui.qtypes.mcq.title"],["free_form","ui.qtypes.flash.title"],["cloze","ui.qtypes.cloze.title"],["numeric","ui.qtypes.numeric.title"],["matching","ui.qtypes.match.title"]],SERVER_DEFAULT_QUESTION_TYPES=["scq","mcq","free_form","cloze","numeric"];function reviewQuestionTypesToSend(e,n,s,r=s){if(e)return["free_form"];const a=s.filter(o=>n.has(o));return a.length===0||a.length===r.length&&r.every(o=>n.has(o))?null:a}function renderReview(){const e=document.getElementById("dashboard-content");capture.questionTypes||(capture.questionTypes=new Set(SERVER_DEFAULT_QUESTION_TYPES));const n=capture.questionTypes;e.innerHTML=`
990    <div class="flex items-center justify-between mb-4 gap-3">
991      <button id="review-back" class="text-sm text-on-surface-muted hover:text-on-surface inline-flex items-center gap-1 transition">${t("ui.review.cancel")}</button>
992      <button id="review-save" class="bg-primary text-on-primary px-6 py-3 rounded-[0.875rem] font-display font-semibold hover:opacity-90 transition">${t("ui.review.save")}</button>
993    </div>
994    <h1 class="font-display text-3xl font-semibold mb-2 tracking-tight">${t("ui.review.title")}</h1>
995    <p class="text-on-surface-muted text-sm mb-6">${t("ui.review.subtitle",{count:capture.pages.length})}</p>
996
997    ${renderStudyPlanNotice()}
998    <div id="review-error" class="hidden text-sm text-error bg-error-container/40 rounded-[0.75rem] p-3 mb-4"></div>
999
1000    <div id="review-thumbs" class="grid grid-cols-3 sm:grid-cols-4 gap-2 mb-6"></div>
1001
1002    <button id="review-add-more" class="text-sm text-primary font-medium hover:underline mb-6 inline-block">${t("ui.review.add_more")}</button>
1003
1004    <label for="review-study-language" class="flex items-start gap-3 mb-4 cursor-pointer select-none rounded-[0.875rem] bg-surface-container-high/40 p-4">
1005      <input type="checkbox" id="review-study-language" ${capture?.generateInStudyLanguage?"checked":""} class="mt-0.5 h-4 w-4 accent-primary shrink-0">
1006      <span>
1007        <span class="block text-sm font-medium text-on-surface">${t("ui.review.question_language")}</span>
1008        <span class="block text-xs text-on-surface-muted mt-1">${t("ui.review.question_language_help")}</span>
1009      </span>
1010    </label>
1011
1012    <label for="review-flashcards-only" class="flex items-start gap-3 mb-6 cursor-pointer select-none rounded-[0.875rem] bg-surface-container-high/40 p-4">
1013      <input type="checkbox" id="review-flashcards-only" ${capture?.flashcardsOnly?"checked":""} class="mt-0.5 h-4 w-4 accent-primary shrink-0">
1014      <span>
1015        <span class="block text-sm font-medium text-on-surface">${t("ui.review.flashcards_only")}</span>
1016        <span class="block text-xs text-on-surface-muted mt-1">${t("ui.review.flashcards_only_help")}</span>
1017      </span>
1018    </label>
1019
1020    <fieldset id="review-question-types" class="mb-6 ${capture?.flashcardsOnly?"hidden":""}">
1021      <legend class="font-mono text-[10px] uppercase tracking-[0.08em] text-on-surface-muted mb-2">${t("ui.review.question_types")}</legend>
1022      <div class="rounded-[0.875rem] bg-surface-container-high/40 divide-y divide-outline-variant/40">
1023        ${REVIEW_QUESTION_TYPES.map(([o,u])=>`
1024        <label class="flex items-center justify-between gap-3 px-4 py-3 cursor-pointer select-none">
1025          <span class="text-sm text-on-surface">${t(u)}</span>
1026          <input type="checkbox" data-qtype="${o}" ${n.has(o)?"checked":""} class="h-4 w-4 accent-primary shrink-0">
1027        </label>`).join("")}
1028      </div>
1029      <p id="review-types-hint" class="text-xs text-error mt-2 ${n.size?"hidden":""}">${t("ui.review.pick_one_type")}</p>
1030    </fieldset>
1031  `,document.getElementById("review-back").addEventListener("click",()=>cancelCapture("review")),document.getElementById("review-add-more").addEventListener("click",async()=>{await openCamera(),renderReviewToCapture()}),document.getElementById("review-save").addEventListener("click",saveCapturedStudy);const s=document.getElementById("review-study-language");s.addEventListener("change",()=>{capture.generateInStudyLanguage=s.checked});const r=document.getElementById("review-flashcards-only"),a=document.getElementById("review-question-types");r.addEventListener("change",()=>{capture.flashcardsOnly=r.checked,a.classList.toggle("hidden",r.checked)});const i=document.getElementById("review-types-hint");a.querySelectorAll("[data-qtype]").forEach(o=>{o.addEventListener("change",()=>{o.checked?n.add(o.getAttribute("data-qtype")):n.delete(o.getAttribute("data-qtype")),i.classList.toggle("hidden",n.size>0)})}),renderReviewThumbs()}function renderReviewToCapture(){enterCaptureChrome();let e=document.getElementById("capture-overlay");e||(e=document.createElement("div"),e.id="capture-overlay",e.className="fixed inset-0 bg-black z-40",document.body.appendChild(e)),e.innerHTML='<div id="capture-host" class="absolute inset-0"></div>',paintViewfinder()}function renderReviewThumbs(){const e=document.getElementById("review-thumbs");e&&(e.innerHTML="",capture.pages.forEach((n,s)=>{const r=URL.createObjectURL(n),a=document.createElement("div");a.className="relative group rounded-[0.875rem] overflow-hidden bg-surface-container-high aspect-square",a.innerHTML=`
1032      <img src="${r}" class="w-full h-full object-cover" alt="">
1033      <span class="absolute bottom-0 left-0 right-0 bg-surface/85 text-on-surface text-[10px] px-2 py-1 font-mono">${s+1}</span>
1034      <button data-rmr="${s}" aria-label="${t("ui.review.remove_page")}"
1035              class="absolute top-1 right-1 w-6 h-6 rounded-full bg-surface/85 text-on-surface text-sm hover:bg-error hover:text-on-primary transition flex items-center justify-center">\xD7</button>
1036    `,e.appendChild(a)}),e.querySelectorAll("[data-rmr]").forEach(n=>{n.addEventListener("click",()=>{const s=parseInt(n.getAttribute("data-rmr"),10);if(capture.pages.splice(s,1),capture.meta.splice(s,1),capture.pages.length===0){cancelCapture("review");return}renderReview()})}))}async function saveCapturedStudy(){const e=document.getElementById("review-error"),n=document.getElementById("review-save");if(!capture?.pages.length){e.textContent=t("ui.review.no_pages"),e.classList.remove("hidden");return}if(capture.pages.length>captureMaxPages()){e.textContent=t("ui.capture.cap_reached",{max:captureMaxPages()}),e.classList.remove("hidden");return}if(!capture.flashcardsOnly&&capture.questionTypes&&capture.questionTypes.size===0){e.textContent=t("ui.review.pick_one_type"),e.classList.remove("hidden");return}const s=new Set(capture.meta.map(c=>c.source)),r=s.size>1?"mixed":s.has("pdf")?"pdf":"camera",a=capture.meta.map(c=>c.text??null),i=a.filter(c=>c!==null).length;postClientEvent("capture_submitted",{pages:capture.pages.length,source:r,text_pages:i}),e.classList.add("hidden"),n.disabled=!0,n.textContent=t("ui.review.saving");const o=new FormData;
1036capture.pages.forEach((c,l)=>{o.append("files",c,`page-${String(l+1).padStart(3,"0")}.jpg`)}),i>0&&o.append("page_texts",JSON.stringify(a)),o.append("question_language",capture.generateInStudyLanguage?"source":currentLocale);const u=reviewQuestionTypesToSend(!!capture.flashcardsOnly,capture.questionTypes||new Set,REVIEW_QUESTION_TYPES.map(([c])=>c),SERVER_DEFAULT_QUESTION_TYPES);u&&o.append("question_types",JSON.stringify(u));try{const c=await apiUpload("/api/materials/upload",o);if(c.error){n.disabled=!1,n.textContent=t("ui.review.save"),e.textContent=c.error,e.classList.remove("hidden"),c.reason==="free_page_limit_reached"&&await api("/api/users/me").then(l=>{l&&!l.error&&(currentUser=l)}).catch(()=>{}),c.reason==="upload_limit_reached"&&(_pendingPaywallSource="upload_rejected",capture=null,await api("/api/users/me").then(l=>{l&&!l.error&&(currentUser=l)}).catch(()=>{}),showStudies());return}capture=null,await api("/api/users/me").then(l=>{l&&!l.error&&(currentUser=l)}).catch(()=>{}),showStudies(),startStudiesPolling()}catch{n.disabled=!1,n.textContent=t("ui.review.save"),e.textContent=t("ui.review.save_failed"),e.classList.remove("hidden")}}async function showConnect(){setActiveTab("connect"),stopStudiesPolling();const e=document.getElementById("dashboard-content");e.innerHTML=`<p class="text-on-surface-muted">${t("ui.connect.title")}\u2026</p>`;const[n,s]=await Promise.all([api("/api/users/me").catch(()=>null),api("/api/materials").catch(()=>null)]);n&&!n.error&&(currentUser=n),s?.data&&(_studiesMaterials=s.data),paintConnect()}function paintConnect(){const e=document.getElementById("dashboard-content"),n=!!currentUser?.telegramLinked,s=currentUser?.telegramBotUsername,r=n&&s?`https://t.me/${encodeURIComponent(s)}`:null,i=t(s?"ui.studies.telegram_help_body_named":"ui.studies.telegram_help_body_unnamed",{username:s||""}),o=n?_studiesMaterials.filter(d=>d.is_focused):[],u=n?o.length?`${t("ui.connect.quizzing_from",{titles:o.map(d=>escapeHtml(d.title)).join(", ")})} <span class="text-on-surface-muted/70">${t("ui.connect.change_current_hint")}</span>`:t("ui.connect.no_current"):"",c=!n&&!!_pendingLinkCode;if(!n&&_studiesMaterials.length===0){e.innerHTML=`
1037      <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.connect.title")}</span>
1038      <h1 class="font-display text-3xl font-semibold mb-3 mt-1 tracking-tight">${t("ui.connect.no_studies_headline")}</h1>
1039      <p class="text-on-surface-muted text-base leading-normal mb-8 max-w-prose">${t("ui.connect.no_studies_body")}</p>
1040      <button id="connect-go-studies" class="bg-primary text-on-primary px-5 py-3 rounded-[0.875rem] font-display font-semibold hover:opacity-90 transition">${t("ui.connect.no_studies_cta")}</button>
1041      ${mcpConnectSectionHtml()}
1042    `,document.getElementById("connect-go-studies")?.addEventListener("click",showStudies),wireMcpCopy(e);return}const l=t(n?"ui.connect.linked_headline":c?"ui.connect.code_ready_headline":"ui.connect.unlinked_headline");e.innerHTML=`
1043    <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.connect.title")}</span>
1044    <h1 class="font-display text-3xl font-semibold mb-3 mt-1 tracking-tight">${l}</h1>
1045
1046    ${n?`
1047      <p class="text-on-surface-muted text-sm leading-relaxed mb-6">${u}</p>
1048      ${r?`
1049        <div class="flex items-center justify-between gap-3 mb-8 flex-wrap">
1050          <a href="${escapeHtml(r)}" target="_blank" rel="noopener"
1051             class="inline-flex items-center gap-2 bg-primary text-on-primary px-5 py-3 rounded-[0.875rem] font-display font-semibold hover:opacity-90 transition">
1052            <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M9.78 18.65l.28-4.23 7.68-6.92c.34-.31-.07-.46-.52-.19l-9.49 5.99-4.1-1.28c-.88-.27-.89-.88.2-1.32l15.97-6.16c.73-.27 1.43.18 1.15 1.3l-2.72 12.81c-.19.91-.74 1.13-1.5.71l-4.13-3.05-1.99 1.93c-.23.23-.42.42-.86.42z"/></svg>
1053            ${t("ui.connect.open")}
1054          </a>
1055          <button id="connect-help-toggle" type="button"
1056                  class="text-[0.65rem] font-mono uppercase tracking-wider text-on-surface-muted hover:text-on-surface transition underline decoration-dotted underline-offset-2"
1057                  aria-expanded="${_connectHelpOpen?"true":"false"}">
1058            ${t("ui.studies.telegram_help")}
1059          </button>
1060        </div>
1061        ${_connectHelpOpen?`
1062          <div class="mb-8 p-3 rounded-[0.625rem] bg-surface-container-highest text-on-surface-muted text-sm leading-relaxed">
1063            ${i}
1064          </div>
1065        `:""}
1066      `:""}
1067      <div class="mt-12">
1068        <button id="connect-unlink" type="button"
1069                class="text-[0.7rem] font-mono uppercase tracking-wider text-on-surface-muted hover:text-error transition underline decoration-dotted underline-offset-4">
1070          ${t("ui.connect.unlink")}
1071        </button>
1072      </div>
1073    `:c?`
1074      <p class="text-on-surface-muted text-base leading-normal mb-8 max-w-prose">${t("ui.connect.code_ready_body")}</p>
1075      <div id="connect-code"></div>
1076    `:`
1077      <p class="text-on-surface-muted text-base leading-normal mb-8 max-w-prose">${t("ui.connect.unlinked_body")}</p>
1078      <button id="connect-generate" class="bg-primary text-on-primary px-5 py-3 rounded-[0.875rem] font-display font-semibold hover:opacity-90 transition">${t("ui.connect.generate")}</button>
1079    `}
1080    ${mcpConnectSectionHtml()}
1081  `,document.getElementById("connect-unlink")?.addEventListener("click",unlinkTelegram),document.getElementById("connect-generate")?.addEventListener("click",generateTelegramCode),document.getElementById("connect-help-toggle")?.addEventListener("click",()=>{_connectHelpOpen=!_connectHelpOpen,paintConnect()}),c&&renderPendingLinkCode(),wireMcpCopy(e)}function mcpConnectSectionHtml(){const e=`${location.origin}/mcp`,n="flex items-center gap-2",s="flex-1 font-mono text-xs sm:text-sm bg-surface-container-highest text-on-surface rounded-[0.5rem] px-3 py-2 select-all truncate",r="shrink-0 text-xs font-medium px-3 py-2 rounded-[0.5rem] bg-surface-container-highest text-on-surface hover:bg-primary hover:text-on-primary transition";return`
1082    <div class="mt-16 pt-10 border-t border-outline-variant max-w-xl">
1083      <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.connect.mcp.eyebrow")}</span>
1084      <h2 class="font-display text-2xl font-semibold mb-2 mt-1 tracking-tight">${t("ui.connect.mcp.headline")}</h2>
1085      <p class="text-on-surface-muted text-sm leading-relaxed mb-5">${t("ui.connect.mcp.body")}</p>
1086      <div class="bg-surface-container-high rounded-[0.875rem] p-4 space-y-4">
1087        <div>
1088          <p class="text-[0.7rem] font-mono uppercase tracking-wider text-on-surface-muted mb-2">${t("ui.connect.mcp.url_label")}</p>
1089          <div class="${n}">
1090            <code class="${s}">${escapeHtml(e)}</code>
1091            <button class="mcp-copy ${r}" data-text="${escapeHtml(e)}">${t("ui.connect.copy")}</button>
1092          </div>
1093        </div>
1094        <div>
1095          <p class="text-[0.7rem] font-mono uppercase tracking-wider text-on-surface-muted mb-2">${t("ui.connect.mcp.claude_hint")}</p>
1096          <p class="text-[0.7rem] font-mono uppercase tracking-wider text-on-surface-muted mb-2 mt-3">${t("ui.connect.mcp.terminal_hint")}</p>
1097          <div class="${n} mb-1.5">
1098            <code class="${s}">claude mcp add --transport http pupilbot ${escapeHtml(e)}</code>
1099            <button class="mcp-copy ${r}" data-text="claude mcp add --transport http pupilbot ${escapeHtml(e)}">${t("ui.connect.copy")}</button>
1100          </div>
1101          <div class="${n}">
1102            <code class="${s}">codex mcp add pupilbot --url ${escapeHtml(e)}</code>
1103            <button class="mcp-copy ${r}" data-text="codex mcp add pupilbot --url ${escapeHtml(e)}">${t("ui.connect.copy")}</button>
1104          </div>
1105        </div>
1106        <p class="text-xs text-on-surface-muted leading-relaxed">${t("ui.connect.mcp.auth_note")}</p>
1107      </div>
1108    </div>`}function wireMcpCopy(e){e.querySelectorAll(".mcp-copy").forEach(n=>{n.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(n.getAttribute("data-text")||"");const s=n.textContent;n.textContent=t("ui.connect.copied"),setTimeout(()=>{n.textContent=s},1500)}catch{}})})}function renderPendingLinkCode(){const e=document.getElementById("connect-code");if(!e||!_pendingLinkCode)return;const{code:n,deepLink:s,botUsername:r,expiresInMinutes:a}=_pendingLinkCode,i=`/link ${n}`,o=r?`@${r}`:null,u=t("ui.connect.or"),c=u==="ui.connect.or"?"or manually (if Open in Telegram doesn't work)":u,l=t("ui.connect.step_find_bot"),d=l==="ui.connect.step_find_bot"?"Start a chat with this bot in Telegram (just like any other contact)":l,m=t("ui.connect.step_send_message"),g=m==="ui.connect.step_send_message"?"Then send this in the chat":m,h=t("ui.connect.manual_intro_unnamed"),C=h==="ui.connect.manual_intro_unnamed"?"Open Telegram, start a chat with the PupilBot bot, and send:":h,I="inline-flex items-center justify-center w-5 h-5 rounded-full bg-surface-container-highest text-on-surface-muted text-[0.65rem] font-mono font-semibold shrink-0",L="text-[0.7rem] font-mono uppercase tracking-wider text-on-surface-muted",y="flex items-center gap-2",_="flex-1 font-mono text-sm bg-surface-container-highest text-on-surface rounded-[0.5rem] px-3 py-2 select-all truncate",w="shrink-0 text-xs font-medium px-3 py-2 rounded-[0.5rem] bg-surface-container-highest text-on-surface hover:bg-primary hover:text-on-primary transition";e.innerHTML=`
1109    ${s?`
1110      <a href="${escapeHtml(s)}" target="_blank" rel="noopener"
1111         class="inline-flex items-center gap-2 bg-primary text-on-primary px-5 py-3 rounded-[0.875rem] font-display font-semibold hover:opacity-90 transition">
1112        <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"><path d="M9.78 18.65l.28-4.23 7.68-6.92c.34-.31-.07-.46-.52-.19l-9.49 5.99-4.1-1.28c-.88-.27-.89-.88.2-1.32l15.97-6.16c.73-.27 1.43.18 1.15 1.3l-2.72 12.81c-.19.91-.74 1.13-1.5.71l-4.13-3.05-1.99 1.93c-.23.23-.42.42-.86.42z"/></svg>
1113        ${t("ui.connect.open")}
1114      </a>
1115      <div class="flex items-center gap-3 my-6" aria-hidden="true">
1116        <span class="flex-1 h-px bg-outline-variant"></span>
1117        <span class="text-[0.65rem] font-mono uppercase tracking-[0.2em] text-on-surface-muted">${escapeHtml(c)}</span>
1118        <span class="flex-1 h-px bg-outline-variant"></span>
1119      </div>
1120    `:""}
1121    <div class="bg-surface-container-high rounded-[0.875rem] p-4 space-y-4">
1122      ${o?`
1123        <div>
1124          <div class="flex items-center gap-2 mb-2">
1125            <span class="${I}">1</span>
1126            <span class="${L}">${escapeHtml(d)}</span>
1127          </div>
1128          <div class="${y}">
1129            <code class="${_}">${escapeHtml(o)}</code>
1130            <button class="connect-copy ${w}" data-text="${escapeHtml(o)}">${t("ui.connect.copy")}</button>
1131          </div>
1132        </div>
1133        <div>
1134          <div class="flex items-center gap-2 mb-2">
1135            <span class="${I}">2</span>
1136            <span class="${L}">${escapeHtml(g)}</span>
1137          </div>
1138          <div class="${y}">
1139            <code class="${_}">${escapeHtml(i)}</code>
1140            <button class="connect-copy ${w}" data-text="${escapeHtml(i)}">${t("ui.connect.copy")}</button>
1141          </div>
1142        </div>
1143      `:`
1144        <p class="text-xs text-on-surface-muted leading-relaxed">${escapeHtml(C)}</p>
1145        <div class="${y}">
1146          <code class="${_}">${escapeHtml(i)}</code>
1147          <button class="connect-copy ${w}" data-text="${escapeHtml(i)}">${t("ui.connect.copy")}</button>
1148        </div>
1149      `}
1150    </div>
1151    <p class="text-[0.65rem] uppercase tracking-wider font-mono text-on-surface-muted mt-3">${t("ui.connect.code_expires",{minutes:a})}</p>
1152  `,e.querySelectorAll(".connect-copy").forEach(B=>{B.addEventListener("click",async()=>{try{await navigator.clipboard.writeText(B.getAttribute("data-text")||"");const A=B.textContent;B.textContent=t("ui.connect.copied"),setTimeout(()=>{B.textContent=A},1500)}catch{}})})}async function generateTelegramCode(){const e=document.getElementById("connect-generate");e&&(e.disabled=!0,e.classList.add("opacity-70","cursor-wait"),e.textContent=t("ui.connect.generating"));const n=await api("/api/telegram/link-code",{method:"POST"});if(!n?.data?.code){e&&(e.disabled=!1,e.classList.remove("opacity-70","cursor-wait"),e.textContent=t("ui.connect.generate"));return}_pendingLinkCode=n.data,paintConnect(),pollConnect(_pendingLinkCode.expiresInMinutes)}let _connectPoll=null;function pollConnect(e){_connectPoll&&clearInterval(_connectPoll);const n=Date.now()+(e||5)*60*1e3;_connectPoll=setInterval(async()=>{if(currentTab!=="connect"){clearInterval(_connectPoll),_connectPoll=null;return}if(Date.now()>=n){clearInterval(_connectPoll),_connectPoll=null,_pendingLinkCode=null,paintConnect();return}const s=await api("/api/users/me").catch(()=>null);s&&!s.error&&s.telegramLinked&&(currentUser=s,clearInterval(_connectPoll),_connectPoll=null,_pendingLinkCode=null,paintConnect())},3e3)}async function unlinkTelegram(){await api("/api/telegram/unlink",{method:"DELETE"});const e=await api("/api/users/me").catch(()=>null);e&&!e.error&&(currentUser=e),_pendingLinkCode=null,paintConnect()}async function showSettings(){setActiveTab("settings"),stopStudiesPolling();const e=document.getElementById("dashboard-content");e.innerHTML=`<p class="text-on-surface-muted">${t("ui.settings.loading")}</p>`;const s=await api("/api/users/me");s&&!s.error&&(currentUser=s);const r=s?.language||currentLocale,a="inline-block bg-surface-container-high text-on-surface rounded-[0.75rem] px-5 py-3 font-medium cursor-pointer hover:opacity-90 transition",i=s?.isPremium?t("ui.settings.account_premium"):t("ui.settings.account_free");e.innerHTML=`
1153    <span class="font-mono text-[0.65rem] uppercase tracking-[0.2em] text-secondary">${t("ui.settings.eyebrow")}</span>
1154    <h1 class="font-display text-3xl font-semibold mb-8 mt-1 tracking-tight">${t("ui.settings.title")}</h1>
1155    <div class="space-y-4 pb-72">
1156      <div class="bg-surface-container rounded-[1rem] p-6">
1157        <h3 class="font-display text-lg font-semibold mb-4 tracking-tight">${t("ui.settings.account")}</h3>
1158        <p class="text-sm text-on-surface-muted">${t("ui.settings.plan_label")} <span class="text-on-surface">${i}</span></p>
1159        ${s?.isPremium?`<div class="mt-4">${renderManageSubscriptionLink(s,a)}</div>`:""}
1160      </div>
1161      <div class="bg-surface-container rounded-[1rem] p-6">
1162        <h3 class="font-display text-lg font-semibold mb-4 tracking-tight">${t("ui.settings.profile")}</h3>
1163        <p class="text-sm text-on-surface-muted">${t("ui.settings.email_label")} <span class="text-on-surface">${escapeHtml(s?.email||t("ui.settings.guest"))}</span></p>
1164        <label for="name-input" class="block text-sm text-on-surface-muted mt-3 mb-2">${t("ui.settings.name_label")}</label>
1165        <div class="flex flex-col sm:flex-row gap-2">
1166          <input id="name-input"
1167                 type="text"
1168                 value="${escapeHtml(s?.name||"")}"
1169                 placeholder="${escapeHtml(t("ui.settings.name_placeholder"))}"
1170                 maxlength="120"
1171                 autocomplete="name"
1172                 class="flex-1 bg-surface-container-high rounded-[0.75rem] px-4 py-3 text-on-surface outline-none border-b-2 border-transparent focus:border-primary transition" />
1173          <button id="name-save"
1174                  type="button"
1175                  class="bg-primary text-on-primary rounded-[0.75rem] px-5 py-3 font-medium cursor-pointer hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed transition">
1176            ${t("ui.settings.name_save")}
1177          </button>
1178        </div>
1179        <p id="name-status" class="text-sm text-on-surface-muted mt-2 min-h-[1.25rem]" aria-l
1179ive="polite"></p>
1180      </div>
1181      <div class="bg-surface-container rounded-[1rem] p-6">
1182        <h3 id="lang-picker-label" class="font-display text-lg font-semibold mb-3 tracking-tight">${t("ui.settings.language")}</h3>
1183        <p class="text-sm text-on-surface-muted mb-4 leading-relaxed">${t("ui.settings.language_help")}</p>
1184        <div id="lang-picker" class="relative">
1185          <button id="lang-picker-trigger"
1186                  type="button"
1187                  aria-labelledby="lang-picker-label"
1188                  aria-haspopup="listbox"
1189                  aria-expanded="false"
1190                  class="w-full flex items-center justify-between bg-surface-container-high rounded-[0.75rem] px-4 py-3 text-on-surface text-left outline-none border-b-2 border-transparent focus:border-primary transition cursor-pointer">
1191            <span id="lang-picker-current">${t(`ui.lang.${r}`)}</span>
1192            <svg class="w-4 h-4 ml-2 shrink-0 text-on-surface-muted" fill="none" stroke="currentColor" viewBox="0 0 24 24" aria-hidden="true">
1193              <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/>
1194            </svg>
1195          </button>
1196          <ul id="lang-picker-panel"
1197              role="listbox"
1198              aria-labelledby="lang-picker-label"
1199              class="hidden absolute left-0 right-0 top-full mt-2 bg-surface-container-high rounded-[0.75rem] shadow-lg overflow-hidden z-10 max-h-72 overflow-y-auto">
1200            ${SUPPORTED_LOCALES.map(d=>`
1201              <li>
1202                <button type="button"
1203                        role="option"
1204                        data-lang="${d}"
1205                        aria-selected="${d===r}"
1206                        class="w-full text-left px-4 py-3 text-on-surface hover:bg-surface-container transition flex items-center justify-between cursor-pointer">
1207                  <span>${t(`ui.lang.${d}`)}</span>
1208                  ${d===r?'<span class="text-primary" aria-hidden="true">\u2713</span>':""}
1209                </button>
1210              </li>
1211            `).join("")}
1212          </ul>
1213        </div>
1214      </div>
1215    </div>
1216  `,initLangPicker(r),document.getElementById("open-stripe-portal")?.addEventListener("click",openStripePortal);const o=document.getElementById("name-input"),u=document.getElementById("name-save"),c=document.getElementById("name-status");o.dataset.initial=(currentUser?.name||"").trim();const l=()=>{const d=o.value.trim();u.disabled=d.length===0||d===o.dataset.initial};l(),o.addEventListener("input",()=>{c.textContent="",l()}),o.addEventListener("keydown",d=>{d.key==="Enter"&&!u.disabled&&(d.preventDefault(),saveName())}),u.addEventListener("click",saveName)}async function saveName(){const e=document.getElementById("name-input"),n=document.getElementById("name-save"),s=document.getElementById("name-status");if(!e||!n)return;const r=e.value.trim();if(!r)return;n.disabled=!0,s.textContent="";const a=await api("/api/users/me",{method:"PATCH",body:{name:r}}).catch(()=>null);a&&!a.error&&a.name?(currentUser={...currentUser||{},name:a.name},e.value=a.name,e.dataset.initial=a.name,s.textContent=t("ui.settings.name_saved")):(s.textContent=a&&a.error||t("ui.settings.name_failed"),n.disabled=!1)}
1216async function changeLanguage(e){SUPPORTED_LOCALES.includes(e)&&(await api("/api/users/me",{method:"PATCH",body:{language:e}}).catch(()=>{}),currentUser&&(currentUser.language=e),localStorage.setItem("pbot_lang",e),writeLocaleCookie(e),currentLocale=e,await loadDictionary(e),showDashboard(),showSettings())}function initLangPicker(e){const n=document.getElementById("lang-picker"),s=document.getElementById("lang-picker-trigger"),r=document.getElementById("lang-picker-panel");if(!n||!s||!r)return;const a=()=>{r.classList.add("hidden"),s.setAttribute("aria-expanded","false"),document.removeEventListener("click",o,!0),document.removeEventListener("keydown",u)},i=()=>{r.classList.remove("hidden"),s.setAttribute("aria-expanded","true"),document.addEventListener("click",o,!0),document.addEventListener("keydown",u),requestAnimationFrame(()=>r.scrollIntoView({block:"nearest",behavior:"smooth"}))},o=c=>{n.contains(c.target)||a()},u=c=>{c.key==="Escape"&&(a(),s.focus())};s.addEventListener("click",()=>{r.classList.contains("hidden")?i():a()}),r.querySelectorAll("button[data-lang]").forEach(c=>{c.addEventListener("click",()=>{const l=c.dataset.lang;a(),l&&l!==e&&changeLanguage(l)})})}function showLanding(){location.reload()}function translateStaticDom(){const e=hasTranslation;e("ui.meta.title")&&(document.title=t("ui.meta.title"));const n=document.querySelector('meta[name="description"]');n&&e("ui.meta.description")&&n.setAttribute("content",t("ui.meta.description")),document.querySelectorAll("[data-i18n]").forEach(s=>{const r=s.getAttribute("data-i18n");!r||!e(r)||(s.hasAttribute("data-i18n-html")?s.innerHTML=t(r):s.textContent=t(r))}),document.querySelectorAll("[data-i18n-attr]").forEach(s=>{const r=s.getAttribute("data-i18n-attr");for(const a of r.split(",")){const[i,o]=a.split(":").map(u=>u.trim());i&&o&&e(o)&&s.setAttribute(i,t(o))}})}async function switchLocale(e){if(!SUPPORTED_LOCALES.includes(e)||e===currentLocale)return;writeLocaleCookie(e),localStorage.setItem("pbot_lang",e),accessToken&&await api("/api/users/me",{method:"PATCH",body:{language:e}}).catch(()=>{});const n=pathWithoutLocale(location.pathname),s=localizePath(n,e)+location.search+location.hash;location.assign(s)}window._pbot_switchLocale=switchLocale;function langPickerMarkup(e){const n=LOCALE_META[currentLocale]||LOCALE_META.en;return`
1217    <div id="${e}" class="relative inline-block">
1218      <button id="${e}-trigger" type="button"
1219              aria-haspopup="listbox" aria-expanded="false"
1220              aria-label="${tOr("ui.footer.language","Language")}: ${n.short||currentLocale.toUpperCase()}"
1221              class="inline-flex items-center gap-1.5 bg-transparent border border-outline-variant rounded-[0.5rem] px-2.5 py-1.5 sm:py-1 text-on-surface-muted hover:text-on-surface hover:border-on-surface-muted transition cursor-pointer outline-none">
1222        <span aria-hidden="true" class="text-base sm:text-sm leading-none">${n.flag}</span>
1223        <span class="text-sm sm:text-xs uppercase tracking-wider leading-none">${n.short||currentLocale.toUpperCase()}</span>
1224        <svg aria-hidden="true" class="w-3 h-3 shrink-0" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5">
1225          <path d="M3 5l3 3 3-3" stroke-linecap="round" stroke-linejoin="round"/>
1226        </svg>
1227      </button>
1228      <ul id="${e}-panel" role="listbox"
1229          data-placement="bottom"
1230          class="hidden absolute min-w-[10rem] bg-surface-container-high rounded-[0.75rem] shadow-lg overflow-hidden z-50 border border-outline-variant">
1231        ${SUPPORTED_LOCALES.map(s=>{const r=LOCALE_META[s]||{};return`
1232            <li>
1233              <button type="button" role="option" data-lang="${s}"
1234                      aria-selected="${s===currentLocale}"
1235                      class="w-full text-left px-3 py-2.5 text-on-surface text-base hover:bg-surface-container transition flex items-center gap-2.5 cursor-pointer">
1236                <span aria-hidden="true" class="text-base">${r.flag||""}</span>
1237                <span class="flex-1">${tOr(`ui.lang.${s}`,r.short||s.toUpperCase())}</span>
1238                ${s===currentLocale?'<span class="text-primary" aria-hidden="true">\u2713</span>':""}
1239              </button>
1240            </li>
1241          `}).join("")}
1242      </ul>
1243    </div>
1244  `}function wireLangPicker(e){const n=document.getElementById(e);if(!n)return;const s=document.getElementById(`${e}-trigger`),r=document.getElementById(`${e}-panel`);if(!s||!r)return;const a=d=>{n.contains(d.target)||c()},i=d=>{d.key==="Escape"&&(c(),s.focus())},o=["top-full","mt-1","bottom-full","mb-1","left-0","right-0"],u=()=>{const d=s.getBoundingClientRect(),m=window.innerHeight-d.bottom,g=d.top,h=window.innerWidth-d.left,C=d.right,I=m<240&&g>m,L=h>=C;r.classList.remove(...o),r.classList.add(I?"bottom-full":"top-full",I?"mb-1":"mt-1",L?"left-0":"right-0"),r.dataset.placement=I?"top":"bottom"},c=()=>{r.classList.add("hidden"),s.setAttribute("aria-expanded","false"),document.removeEventListener("click",a,!0),document.removeEventListener("keydown",i)},l=()=>{u(),r.classList.remove("hidden"),s.setAttribute("aria-expanded","true"),document.addEventListener("click",a,!0),document.addEventListener("keydown",i)};s.addEventListener("click",()=>{r.classList.contains("hidden")?l():c()}),r.querySelectorAll("button[data-lang]").forEach(d=>{d.addEventListener("click",()=>{const m=d.dataset.lang;c(),m&&switchLocale(m)})})}function renderLandingLanguagePicker(){const e=document.getElementById("lang-picker-host");e&&(e.innerHTML=langPickerMarkup("footer-lang-picker"),wireLangPicker("footer-lang-picker")),document.querySelectorAll("a[data-localize-href]").forEach(n=>{const s=n.getAttribute("data-localize-href");n.setAttribute("href",localizePath(s,currentLocale))})}function initAuthLinks(){location.search&&document.querySelectorAll('a[data-localize-href="/login"], a[data-localize-href="/signup"]').forEach(e=>{e.setAttribute("href",e.getAttribute("href")+location.search)})}function legacyAuthEntryTarget(e,n){const s=new URLSearchParams(e),r=s.get("auth")==="login";if(!r&&n!=="#login")return null;s.delete("auth");const a=s.toString();return(r?"/login":"/signup")+(a?"?"+a:"")}window._pbot_supported=SUPPORTED_LOCALES,document.addEventListener("DOMContentLoaded",async()=>{if(await loadDictionary(currentLocale),isResetPasswordRoute()){const n=new URLSearchParams(window.location.search).get("token")||"";window.history.replaceState({},"",window.location.pathname),showResetPassword(n);return}const e=authPageMode();if(e){if(accessToken){location.replace(localizePath("/",currentLocale)+location.search);return}const n=localizePath("/"+e,currentLocale);if(servedLocale!==currentLocale&&n!==location.pathname){location.replace(n+location.search);return}document.documentElement.lang=servedLocale,canRepaintServedCopy()&&renderLandingLanguagePicker(),initAuthLinks(),setAuthMode(e),initAuth();return}if(accessToken){await consumeStripeReturnParam();const n=await api("/api/users/me").catch(()=>null);n&&!n.error&&(currentUser=n,await applyServerLanguage(n)),document.getElementById("locale-suggest")?.remove(),showDashboard(),consumeMaterialReturnParam()}else{const n=legacyAuthEntryTarget(window.location.search,window.location.hash);if(n){location.replace(localizePath(n,currentLocale));return}translateStaticDom(),renderLandingLanguagePicker(),initAuthLinks(),initNav(),initLandingPricing(),initFadeIn(),initMascotGreeter()}});function registerServiceWorker(){if(!("serviceWorker"in navigator))return;if(location.hostname==="localhost"||location.hostname==="127.0.0.1"){navigator.serviceWorker.getRegistrations().then(r=>{r.forEach(a=>a.unregister())}).catch(()=>{});return}const n=!!navigator.serviceWorker.controller;let s=!1;navigator.serviceWorker.addEventListener("controllerchange",()=>{!n||s||(s=!0,location.reload())}),navigator.serviceWorker.register("/sw.js").then(r=>{const a=()=>
1244{r.update().catch(()=>{})};setInterval(a,1800*1e3),document.addEventListener("visibilitychange",()=>{document.visibilityState==="visible"&&a()})}).catch(()=>{})}window.addEventListener("load",registerServiceWorker);

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.