1(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[8959],{4748:(e,t,a)=>{"use strict";a.d(t,{BK:()=>n,Hf:()=>s,Tw:()=>h,ZH:()=>r,_P:()=>o,c0:()=>c,en:()=>i,kl:()=>d,td:()=>l});let s=e=>{let t=Math.max(0,Number(e)||0),a=Math.floor(t/60),s=Math.floor(t%60),l=Math.floor(t%1*10);return`${a}:${String(s).padStart(2,"0")}.${l}`},l=(e,t)=>`${e} ${t}${1===e?"":"s"}`,r=e=>e?e.charAt(0).toUpperCase()+e.slice(1):e,n=e=>null!=e&&Number.isFinite(e)?e>=80?"good":e>=60?"ok":"bad":"none",i={good:"text-success30",ok:"text-warning-600",bad:"text-error40",none:"text-neutral60"},o={good:"Good",ok:"Okay",needs_work:"Work on it",unknown:"n/a"},d={good:"bg-success95 text-success30",ok:"bg-warning-50 text-warning-700",needs_work:"bg-error95 text-error30",unknown:"bg-neutral95 text-neutral60"},c={good:"bg-success30",ok:"bg-warning",needs_work:"bg-error"},u=e=>{let t,a=[],s=/\*\*(.+?)\*\*|\*(?!\s)(.+?)\*/g,l=0;for(;t=s.exec(e);)t.index>l&&a.push({text:e.slice(l,t.index)}),void 0!==t[1]?a.push({text:t[1],bold:!0}):a.push({text:t[2],italic:!0}),l=t.index+t[0].length;return l<e.length&&a.push({text:e.slice(l)}),a},h=e=>{let t=[],a=null;for(let s of String(e||"").split("\n")){let e,l=s.replace(/\s+$/,"");if(!l.trim()){a=null;continue}if(e=l.match(/^#{1,6}\s+(.*)/))a=null,t.push({kind:"heading",segments:u(e[1])});else if((e=l.match(/^\s{2,}[-*]\s+(.*)/))&&a?.items.length)a.items[a.items.length-1].children.push(u(e[1]));else if((e=l.match(/^[-*]\s+(.*)/))||(e=l.match(/^\d+[.)]\s+(.*)/))){let s=/^\d/.test(l);a&&a.ordered===s||(a={kind:"list",ordered:s,items:[]},t.push(a)),a.items.push({segments:u(e[1]),children:[]})}else a=null,t.push({kind:"paragraph",segments:u(l.trim())})}return t}},19453:(e,t,a)=>{"use strict";a.d(t,{O:()=>r});var s=a(14232),l=a(13524);let r=e=>{let{setPageName:t}=(0,l.O)();(0,s.useEffect)(()=>(t(""),t(e),()=>{t("")}),[t,e])}},48637:(e,t,a)=>{"use strict";a.d(t,{A:()=>n});var s=a(37876);a(14232);var l=a(30016),r=a(4748);function n({score:e,size:t=96}){let a=t/10,i=(t-a)/2,o=2*Math.PI*i,d=Math.max(0,Math.min(100,e??0));return(0,s.jsxs)("div",{className:(0,l.cn)("relative shrink-0",r.en[(0,r.BK)(e)]),style:{width:t,height:t},children:[(0,s.jsxs)("svg",{width:t,height:t,viewBox:`0 0 ${t} ${t}`,className:"-rotate-90",children:[(0,s.jsx)("circle",{cx:t/2,cy:t/2,r:i,fill:"none",strokeWidth:a,className:"stroke-neutral95"}),(0,s.jsx)("circle",{cx:t/2,cy:t/2,r:i,fill:"none",stroke:"currentColor",strokeWidth:a,strokeLinecap:"round",strokeDasharray:`${d/100*o} ${o}`})]}),(0,s.jsxs)("div",{className:"absolute inset-0 flex flex-col items-center justify-center",children:[(0,s.jsx)("span",{className:"font-bold text-neutral10 leading-none",style:{fontSize:t/3.4},children:e??"â"}),t>=64&&(0,s.jsx)("span",{className:"text-xs text-neutral60",children:"score"})]})]})}},70015:(e,t,a)=>{"use strict";a.r(t),a.d(t,{default:()=>P});var s=a(37876),l=a(14232),r=a(77328),n=a.n(r),i=a(89099),o=a(85461),d=a(23920),c=a(19189),u=a(19453),h=a(91942),p=a(42268),m=a(86565),x=a(99270),f=a(96005),b=a(80863),g=a(30016);let y=/\.(mp4|mov|m4v|webm|mkv|avi)$/i,v=[{key:"15",label:"Fast (15 fps)"},{key:"30",label:"Standard (30 fps)"},{key:"60",label:"Precise (60 fps, slower)"}],j=["Film from behind the baseline, camera centered, at chest height or higher.","Keep your whole body in the frame the entire time.","Use a tripod or lean the phone on the fence â no panning.","Record at 60 fps if your phone allows it.","Make sure you are the largest person in the frame."];function w({onAnalyze:e}){let t=(0,l.useRef)(null),[a,r]=(0,l.useState)(null),[n,i]=(0,l.useState)(null),[o,d]=(0,l.useState)(!1),[u,h]=(0,l.useState)("right"),[N,k]=(0,l.useState)("30"),$=async e=>{if(e){if(!/^video\//.test(e.type)&&!y.test(e.name))return void(0,c.$U)({description:"Please choose a video file.",color:"warning",variant:"flat"});if(e.size>2e8)return void(0,c.$U)({description:"This video is larger than 200 MB. Trim it or lower its quality before uploading.",color:"warning",variant:"flat"});r(e),i(null);try{i(await new Promise((t,a)=>{let s=document.createElement("video");s.preload="metadata",s.src=URL.createObjectURL(e),s.onloadedmetadata=()=>{URL.revokeObjectURL(s.src),t(s.duration)},s.onerror=()=>{URL.revokeObjectURL(s.src),a(Error("Could not read video metadata."))}}))}catch{}}};return(0,s.jsxs)("div",{className:"grid grid-cols-1 lg:grid-cols-[3fr_2fr] gap-4",children:[(0,s.jsxs)("div",{className:"bg-white rounded border border-neutral90 p-4 sm:p-6",children:[(0,s.jsx)("p",{className:"font-bold text-neutral30 text-lg",children:"Analyze your tennis"}),(0,s.jsx)("p",{className:"text-sm text-neutral40 mt-1",children:"Upload a clip of yourself playing. We detect your body movement, find each stroke and give you technique feedback."}),(0,s.jsxs)("button",{type:"button",onClick:()=>t.current?.click(),onDragOver:e=>{e.preventDefault(),d(!0)},onDragLeave:()=>d(!1),onDrop:e=>{e.preventDefault(),d(!1),$(e.dataTransfer.files?.[0])},className:(0,g.cn)("w-full mt-4 mb-4 flex flex-col items-center justify-center gap-1 rounded border-2 border-dashed px-4 py-8 text-center transition-colors",o?"border-primary bg-primary99":"border-neutral80 bg-neutral99 hover:border-primary60"),children:[(0,s.jsx)("span",{className:"flex h-12 w-12 items-center justify-center rounded-full bg-primary text-white text-lg",children:"â¶"}),(0,s.jsxs)("span",{className:"font-semibold text-neutral30 mt-2",children:["Drop a video here or ",(0,s.jsx)("span",{className:"text-primary underline",children:"browse"})]}),(0,s.jsx)("span",{className:"text-sm text-neutral60",children:"MP4 / MOV / WebM \xb7 a few minutes works best"}),a&&(0,s.jsxs)("span",{className:"text-sm font-semibold text-primary mt-2 break-all",children:[a.name," \xb7 ",(a.size/1e6).toFixed(1)," MB"]})]}),(0,s.jsx)("input",{ref:t,type:"file",accept:"video/*,.mov,.mp4,.m4v,.webm",className:"hidden",onChange:e=>$(e.target.files?.[0])}),(0,s.jsxs)("div",{className:"flex flex-wrap gap-6 mb-6",children:[(0,s.jsxs)(m.U,{label:"Player is",orientation:"horizontal",value:u,onValueChange:e=>h(e),classNames:{label:"text-sm text-neutral40"},children:[(0,s.jsx)(x.O,{value:"right",children:"Right-handed"}),(0,s.jsx)(x.O,{value:"left",children:"Left-handed"})]}),(0,s.jsx)(f.d,{label:"Detail",labelPlacement:"outside",size:"sm",className:"w-[220px]",selectedKeys:[N],onChange:e=>e.target.value&&k(e.target.value),"aria-label":"Analysis detail",children:v.map(e=>(0,s.jsx)(b.y,{children:e.label},e.key))})]}),(0,s.jsx)(p.T,{color:"primary",className:"font-bold w-full",isDisabled:!a,onPress:()=>
1{if(!a)return;let t=Number(N),s=null!=n?Math.ceil(n*t):null;null!=s&&s>108e3?(0,c.$U)({description:`This clip is too long at this detail level (~${s.toLocaleString()} frames, max ${108e3.toLocaleString()}). Pick a lower detail level or a shorter clip.`,color:"warning",variant:"flat"}):e({file:a,handedness:u,sampleFps:t})},children:"Analyze video"})]}),(0,s.jsxs)("div",{className:"bg-white rounded border border-neutral90 p-4 sm:p-6",children:[(0,s.jsx)("p",{className:"font-bold text-neutral30 mb-3",children:"Filming tips"}),(0,s.jsx)("ul",{className:"list-disc pl-5 flex flex-col gap-2 text-sm text-neutral40",children:j.map(e=>(0,s.jsx)("li",{children:e},e))}),(0,s.jsx)("p",{className:"text-xs text-neutral60 mt-4",children:"Your video is processed in this browser to detect body position, then uploaded to your CourtMatch account for analysis. Only you can see your sessions."})]})]})}let N=[{key:"model",label:"Load pose model"},{key:"pose",label:"Detect body pose"},{key:"upload",label:"Upload"},{key:"analyze",label:"Analyze strokes"}],k=(0,l.forwardRef)(function({step:e,title:t,percent:a,detail:l,onCancel:r},n){let i=N.findIndex(t=>t.key===e);return(0,s.jsxs)("div",{className:"bg-white rounded border border-neutral90 p-4 sm:p-6 max-w-[720px] mx-auto w-full text-center",children:[(0,s.jsx)("p",{className:"font-bold text-neutral30 text-lg",children:t}),(0,s.jsx)("div",{className:"flex flex-wrap justify-center gap-2 my-4",children:N.map((e,t)=>(0,s.jsx)("span",{className:(0,g.cn)("px-3 py-1 rounded text-xs font-semibold",t<i&&"bg-success95 text-success30",t===i&&"bg-primary text-white",t>i&&"bg-neutral95 text-neutral60"),children:e.label},e.key))}),(0,s.jsx)("div",{className:"h-2 rounded bg-neutral95 overflow-hidden",role:"progressbar","aria-valuenow":Math.round(a),children:(0,s.jsx)("div",{className:"h-full bg-primary transition-all",style:{width:`${Math.round(a)}%`}})}),(0,s.jsx)("p",{className:"text-sm text-neutral60 mt-2 min-h-[20px]",children:l}),(0,s.jsx)("div",{className:"my-4 max-w-[560px] mx-auto",children:(0,s.jsx)("canvas",{ref:n,className:"w-full rounded bg-neutral10 block"})}),(0,s.jsx)(p.T,{variant:"light",onPress:r,children:"Cancel"})]})});var $=a(48230),A=a.n($),M=a(10841),C=a.n(M),U=a(43431),_=a(48637),D=a(92764),O=a(4748);function S({items:e,isLoading:t}){return(0,s.jsxs)("div",{className:"bg-white rounded border border-neutral90 p-4 sm:p-6",children:[(0,s.jsx)("p",{className:"font-bold text-neutral30 mb-2",children:"Past sessions"}),t&&(0,s.jsx)("div",{className:"flex justify-center py-8",children:(0,s.jsx)(U.o,{})}),!t&&0===e.length&&(0,s.jsx)("p",{className:"text-sm text-neutral60",children:"No sessions yet."}),(0,s.jsx)("div",{className:"flex flex-col",children:e.map(e=>(0,s.jsxs)(A(),{href:(0,D.Qv)(e.id),className:"grid grid-cols-[52px_1fr_auto] items-center gap-3 py-3 px-1 border-t border-neutral90 first:border-t-0 hover:bg-neutral99",children:[(0,s.jsx)(_.A,{score:e.overallScore,size:44}),(0,s.jsxs)("span",{className:"min-w-0",children:[(0,s.jsx)("span",{className:"block font-semibold text-sm text-neutral30 truncate",children:e.originalFileName||"Untitled session"}),(0,s.jsxs)("span",{className:"block text-xs text-neutral60",children:[C()(e.createdAt).format("MM/DD/YYYY hh:mm A")," \xb7 ",(0,O.td)(e.strokeCount,"stroke")," \xb7"," ",Math.round(e.durationSec),"s \xb7 ",e.handedness,"-handed"]})]}),(0,s.jsx)("span",{className:"text-neutral60",children:"âº"})]},e.id))})]})}function P(){(0,u.O)("AI Coach");let e=(0,i.useRouter)(),t=(0,o.jE)(),r=(0,l.useRef)(null),p=(0,l.useRef)(null),[m,x]=(0,l.useState)(null),{data:f,isLoading:b}=(0,d.I)(["ai-coach-analyses"],()=>(0,h.NH)().then(e=>e.data??e)),{data:g}=(0,d.I)(["ai-coach-status"],()=>(0,h.fW)().then(e=>e.data??e)),y=async({file:s,handedness:l,sampleFps:n})=>{let i=new AbortController;p.current=i,x({step:"model",title:"Loading pose modelâ¦",percent:2,detail:"The first run downloads the model (~9 MB)."});try{let{extractPoses:o,loadPoseLandmarkers:d}=await a.e(9684).then(a.bind(a,99684));await d();let c=performance.now(),u=await o(s,{sampleFps:n,handedness:l,signal:i.signal,previewCanvas:r.current,onProgress:({done:e,total:t,t:a,detected:s})=>{let l=(performance.now()-c)/1e3,r=e>5?Math.round(l/e*(t-e)):null;x({step:"pose",title:"Detecting body poseâ¦",percent:5+e/t*80,detail:`${(0,O.Hf)(a)} \xb7 player found in ${Math.round(s/e*100)}% of frames${null!=r?` \xb7 ~${r}s left`:""}`})}});if(i.signal.aborted)throw new DOMException("Cancelled","AbortError");x({step:"upload",title:"Uploadingâ¦",percent:86});let p=await (0,h.ZM)({poses:u,handedness:l,video:s},e=>x(e<1?{step:"upload",title:"Uploadingâ¦",percent:86+8*e,detail:`${Math.round(100*e)}%`}:{step:"analyze",title:"Analyzing strokes and writing feedbackâ¦",percent:96}),i.signal);if(i.signal.aborted)throw new DOMException("Cancelled","AbortError");let m=p.data??p;t.setQueryData(["ai-coach-analysis",m.id],m),t.setQueryData(["ai-coach-poses",m.id],u),t.invalidateQueries(["ai-coach-analyses"]),e.push((0,D.Qv)(m.id))}catch(e){e?.name==="AbortError"||i.signal.aborted?(0,c.$U)({description:"Analysis cancelled.",color:"default",variant:"flat"}):(0,c.$U)({description:e?.response?.data?.message||e?.message||"Could not analyze this video.",color:"danger",variant:"flat"}),x(null)}finally{p.current=null}};return(0,s.jsxs)("div",{className:"custom_container",children:[(0,s.jsxs)(n(),{children:[(0,s.jsx)("title",{children:"AI Coach"}),(0,s.jsx)("meta",{name:"description",content:"AI Coach â video technique analysis"})]}),(0,s.jsxs)("div",{className:"flex flex-col gap-4 mt-6 mb-10",children:[(0,s.jsx)("div",{className:"flex items-center justify-end",children:(0,s.jsx)("span",{className:"px-2 py-1 rounded text-xs font-semibold bg-primary99 text-primary",children:g?.aiEnabled?`AI coach: ${g.model}`:"Rule-based coach"})}),m?(0,s.jsx)(k,{ref:r,step:m.step,title:m.title,percent:m.percent,detail
1:m.detail,onCancel:()=>p.current?.abort()}):(0,s.jsxs)(s.Fragment,{children:[(0,s.jsx)(w,{onAnalyze:y}),(0,s.jsx)(S,{items:f??[],isLoading:b})]})]})]})}},77328:(e,t,a)=>{e.exports=a(88022)},91942:(e,t,a)=>{"use strict";a.d(t,{Cs:()=>h,KT:()=>u,NH:()=>r,ZM:()=>d,fW:()=>l,hN:()=>i,if:()=>c,nS:()=>o,ri:()=>n});var s=a(87710);let l=()=>s.Ay.get("/ai-coach/status"),r=e=>s.Ay.get("/ai-coach/analyses",{params:e?{limit:e}:void 0}),n=e=>s.Ay.get(`/ai-coach/analyses/${e}`),i=e=>s.Ay.get(`/ai-coach/analyses/${e}/poses`),o=e=>s.Ay.get(`/ai-coach/analyses/${e}/video-url`),d=({poses:e,handedness:t,video:a},l,r)=>{let n=new FormData;return n.append("poses",new Blob([JSON.stringify(e)],{type:"application/json"}),"poses.json"),n.append("handedness",t),a&&n.append("video",a,a.name),s.Ay.post("/ai-coach/analyses",n,{headers:{"Content-Type":"multipart/form-data"},signal:r,onUploadProgress:e=>{l&&e.total&&l(e.loaded/e.total)}})},c=e=>s.Ay.post(`/ai-coach/analyses/${e}/feedback`),u=(e,t)=>s.Ay.post(`/ai-coach/analyses/${e}/reanalyze`,t?{handedness:t}:{}),h=e=>s.Ay.delete(`/ai-coach/analyses/${e}`)},93233:(e,t,a)=>{(window.__NEXT_P=window.__NEXT_P||[]).push(["/ai-coach",function(){return a(70015)}])}},e=>{e.O(0,[636,6593,8792],()=>e(e.s=93233)),_N_E=e.O()}]);
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.