bitsnap-react 1.12.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (90) hide show
  1. package/README.md +40 -3
  2. package/dist/CartProvider-F7ER3WSS.js +2 -0
  3. package/dist/CartProvider-F7ER3WSS.js.map +1 -0
  4. package/dist/CartProvider-N73XNL7S.mjs +2 -0
  5. package/dist/analytics.js +1 -1
  6. package/dist/analytics.js.map +1 -1
  7. package/dist/analytics.mjs +1 -1
  8. package/dist/catalog.js +1 -1
  9. package/dist/catalog.js.map +1 -1
  10. package/dist/catalog.mjs +1 -1
  11. package/dist/{chunk-ICFYSZTV.js → chunk-3A4YOO7Z.js} +2 -2
  12. package/dist/chunk-3A4YOO7Z.js.map +1 -0
  13. package/dist/{chunk-UCESDAEC.mjs → chunk-3DGHF373.mjs} +2 -2
  14. package/dist/chunk-5GMWRCHK.mjs +4 -0
  15. package/dist/chunk-5GMWRCHK.mjs.map +1 -0
  16. package/dist/{chunk-MLSKHOA3.js → chunk-6CUCRTIK.js} +2 -2
  17. package/dist/chunk-6CUCRTIK.js.map +1 -0
  18. package/dist/{chunk-DGUIG334.js → chunk-EWLOGFF2.js} +2 -2
  19. package/dist/chunk-EWLOGFF2.js.map +1 -0
  20. package/dist/chunk-GAVLY6RX.mjs +2 -0
  21. package/dist/chunk-GAVLY6RX.mjs.map +1 -0
  22. package/dist/{chunk-XTIY6PXH.js → chunk-IP4OOWZ6.js} +2 -2
  23. package/dist/chunk-IP4OOWZ6.js.map +1 -0
  24. package/dist/chunk-JSDGZ4NY.js.map +1 -1
  25. package/dist/{chunk-3QG7H2AI.mjs → chunk-K2EITRNW.mjs} +2 -2
  26. package/dist/{chunk-5R3WR22A.mjs → chunk-KH7DFTJY.mjs} +2 -2
  27. package/dist/chunk-KOYBZORL.js.map +1 -1
  28. package/dist/{chunk-E5DCEFI2.mjs → chunk-L6J6H73J.mjs} +2 -2
  29. package/dist/chunk-M4KEUK6I.js +2 -0
  30. package/dist/chunk-M4KEUK6I.js.map +1 -0
  31. package/dist/{chunk-QSIKRVZL.mjs → chunk-OC7YS3UY.mjs} +2 -2
  32. package/dist/chunk-TMBC2BHN.js +4 -0
  33. package/dist/chunk-TMBC2BHN.js.map +1 -0
  34. package/dist/{chunk-DF2IK2GN.js → chunk-TSJCVFOX.js} +2 -2
  35. package/dist/chunk-TSJCVFOX.js.map +1 -0
  36. package/dist/{chunk-6IACFTJR.js → chunk-UFQVOGBS.js} +2 -2
  37. package/dist/chunk-UFQVOGBS.js.map +1 -0
  38. package/dist/chunk-XIHGWNTS.js.map +1 -1
  39. package/dist/{chunk-FF2ICGFQ.mjs → chunk-YNGXQJPE.mjs} +2 -2
  40. package/dist/{chunk-FF2ICGFQ.mjs.map → chunk-YNGXQJPE.mjs.map} +1 -1
  41. package/dist/consent.js.map +1 -1
  42. package/dist/course.js +1 -1
  43. package/dist/course.js.map +1 -1
  44. package/dist/course.mjs +1 -1
  45. package/dist/embed/analytics.js +1 -1
  46. package/dist/errors.js.map +1 -1
  47. package/dist/index.css +1 -1
  48. package/dist/index.css.map +1 -1
  49. package/dist/index.d.mts +59 -2
  50. package/dist/index.d.ts +59 -2
  51. package/dist/index.js +1 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/index.mjs +1 -1
  54. package/dist/index.mjs.map +1 -1
  55. package/dist/internal.js.map +1 -1
  56. package/dist/methods-ADH5XI4I.js +2 -0
  57. package/dist/methods-ADH5XI4I.js.map +1 -0
  58. package/dist/methods-BCR54EWX.mjs +2 -0
  59. package/dist/models.js.map +1 -1
  60. package/dist/returns.js +1 -1
  61. package/dist/returns.js.map +1 -1
  62. package/dist/returns.mjs +1 -1
  63. package/package.json +26 -51
  64. package/dist/CartProvider-XNFSUOCB.js +0 -2
  65. package/dist/CartProvider-XNFSUOCB.js.map +0 -1
  66. package/dist/CartProvider-YMJ7RFO4.mjs +0 -2
  67. package/dist/chunk-4BW6J7FP.js +0 -4
  68. package/dist/chunk-4BW6J7FP.js.map +0 -1
  69. package/dist/chunk-6IACFTJR.js.map +0 -1
  70. package/dist/chunk-DF2IK2GN.js.map +0 -1
  71. package/dist/chunk-DGUIG334.js.map +0 -1
  72. package/dist/chunk-ERL4Z7M7.js +0 -2
  73. package/dist/chunk-ERL4Z7M7.js.map +0 -1
  74. package/dist/chunk-ICFYSZTV.js.map +0 -1
  75. package/dist/chunk-MLSKHOA3.js.map +0 -1
  76. package/dist/chunk-P4Q5KXKS.mjs +0 -2
  77. package/dist/chunk-P4Q5KXKS.mjs.map +0 -1
  78. package/dist/chunk-PNTO3L2C.mjs +0 -4
  79. package/dist/chunk-PNTO3L2C.mjs.map +0 -1
  80. package/dist/chunk-XTIY6PXH.js.map +0 -1
  81. package/dist/methods-DDGOXLP6.mjs +0 -2
  82. package/dist/methods-RQPDIUUN.js +0 -2
  83. package/dist/methods-RQPDIUUN.js.map +0 -1
  84. /package/dist/{CartProvider-YMJ7RFO4.mjs.map → CartProvider-N73XNL7S.mjs.map} +0 -0
  85. /package/dist/{chunk-UCESDAEC.mjs.map → chunk-3DGHF373.mjs.map} +0 -0
  86. /package/dist/{chunk-3QG7H2AI.mjs.map → chunk-K2EITRNW.mjs.map} +0 -0
  87. /package/dist/{chunk-5R3WR22A.mjs.map → chunk-KH7DFTJY.mjs.map} +0 -0
  88. /package/dist/{chunk-E5DCEFI2.mjs.map → chunk-L6J6H73J.mjs.map} +0 -0
  89. /package/dist/{chunk-QSIKRVZL.mjs.map → chunk-OC7YS3UY.mjs.map} +0 -0
  90. /package/dist/{methods-DDGOXLP6.mjs.map → methods-BCR54EWX.mjs.map} +0 -0
package/dist/course.js CHANGED
@@ -1,2 +1,2 @@
1
- "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _chunkDGUIG334js = require('./chunk-DGUIG334.js');var _chunk6IACFTJRjs = require('./chunk-6IACFTJR.js');var _chunkERL4Z7M7js = require('./chunk-ERL4Z7M7.js');var _chunkDF2IK2GNjs = require('./chunk-DF2IK2GN.js');require('./chunk-XIHGWNTS.js');require('./chunk-JSDGZ4NY.js');var _lucidereact = require('lucide-react');var _react = require('react');function De(e){let t={},s=_optionalChain([e, 'optionalAccess', _2 => _2.brand, 'optionalAccess', _3 => _3.trim, 'call', _4 => _4()]),o=_optionalChain([e, 'optionalAccess', _5 => _5.brandDark, 'optionalAccess', _6 => _6.trim, 'call', _7 => _7()])||s;if(s){t["--course-accent"]=s;let n=_chunk6IACFTJRjs.s.call(void 0, s);n&&(t["--course-accent-fg"]=n)}if(o){t["--course-accent-dark"]=o;let n=_chunk6IACFTJRjs.s.call(void 0, o);n&&(t["--course-accent-dark-fg"]=n)}return t}var He=_react.createContext.call(void 0, void 0),qe=He.Provider;function h(){let e=_react.useContext.call(void 0, He);if(e==null)throw new Error("Course components have to be rendered inside PanelCourse");return e}function Q(){let{analytics:e,preview:t}=h();return _react.useCallback.call(void 0, (s,o)=>{t||_optionalChain([e, 'optionalAccess', _8 => _8.courseId])==null||e.instrument.track(s,{...o,course_id:e.courseId})},[e,t])}function ce(e){return[...e.sections.flatMap(t=>t.items.map(s=>({...s,sectionId:t.id}))),...e.finalItems.map(t=>({...t,sectionId:""}))]}function le(e,t){if(!(t==null||t===""))return ce(e).find(s=>s.id===t)}function ye(e,t){let s=ce(e),o=s.findIndex(n=>n.id===t);return o<0?{}:{prev:s[o-1],next:s[o+1]}}function Oe(e,t){let{next:s}=ye(e,t);return s!=null&&s.kind==="lesson"&&!s.locked?s:void 0}function zt(e){let t=0,s=0;for(let o of e.sections)for(let n of o.items)n.kind==="lesson"&&(t+=1,n.completed&&(s+=1));return{total:t,completed:s,percent:t===0?0:Math.round(s/t*100)}}function $e(e,t){let s=e.sections.map(n=>({...n,items:n.items.map(i=>i.id===t&&i.kind==="lesson"?{...i,completed:!0}:i)})),o=zt({sections:s});return{...e,sections:s,totalLessons:o.total,completedLessons:o.completed,progressPercent:o.percent}}function et(e,t){return le(e,t)!=null?t:_optionalChain([le, 'call', _9 => _9(e,e.resumeItemId), 'optionalAccess', _10 => _10.locked])===!1?e.resumeItemId:_optionalChain([ce, 'call', _11 => _11(e), 'access', _12 => _12.find, 'call', _13 => _13(s=>!s.locked), 'optionalAccess', _14 => _14.id])}function w(e,t){return e.replace(/\{(\w+)\}/g,(s,o)=>o in t?String(t[o]):s)}function tt(e,t){return t>0&&e/t>=.8}function st(e,t){return e<5||t>0&&e>=t-5?0:Math.floor(e)}function Se(e){let t=Math.max(0,Math.floor(e)),s=Math.floor(t/3600),o=Math.floor(t%3600/60),n=String(t%60).padStart(2,"0");return s>0?`${s}:${String(o).padStart(2,"0")}:${n}`:`${o}:${n}`}function nt(e){if(!(e>0))return"0 B";let t=["B","KB","MB","GB"],s=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),o=e/1024**s;return`${s===0||o>=10?Math.round(o):o.toFixed(1)} ${t[s]}`}function ot(e){switch(e.type){case"mcq":return{type:"mcq",selectedIds:[]};case"todolist":return{type:"todolist",checkedIds:[]};case"fill_gap":return{type:"fill_gap",values:{}};case"sort_order":return{type:"sort_order",orderedIds:e.items.map(t=>t.id)}}}function Qt(e,t){if(t==null||t.type!==e.config.type)return!1;switch(t.type){case"mcq":return t.selectedIds.length>0;case"todolist":return t.checkedIds.length>0;case"fill_gap":return Object.values(t.values).some(s=>s.trim()!=="");case"sort_order":return t.orderedIds.length>0}}function rt(e,t){return e.filter(s=>!Qt(s,t[s.id])).length}function at(e,t,s){if(t===s||t<0||s<0||t>=e.length||s>=e.length)return[...e];let o=[...e],[n]=o.splice(t,1);return o.splice(s,0,n),o}function he(e,t){let s=[],o=/\{\{\s*([^{}\s]+)\s*\}\}/g,n=0;for(let i of e.matchAll(o)){let a=i[1];t.includes(a)&&(i.index>n&&s.push({type:"text",text:e.slice(n,i.index)}),s.push({type:"blank",id:a}),n=i.index+i[0].length)}return n<e.length&&s.push({type:"text",text:e.slice(n)}),s}function it(e,t){let{config:s}=e;if(t==null||t.type!==s.type)return"";let o=(n,i)=>_nullishCoalesce(_optionalChain([n, 'access', _15 => _15.find, 'call', _16 => _16(a=>a.id===i), 'optionalAccess', _17 => _17.label]), () => (i));return s.type==="mcq"&&t.type==="mcq"?t.selectedIds.map(n=>o(s.choices,n)).join(", "):s.type==="todolist"&&t.type==="todolist"?t.checkedIds.map(n=>o(s.steps,n)).join(", "):s.type==="sort_order"&&t.type==="sort_order"?t.orderedIds.map(n=>o(s.items,n)).join(" \u2192 "):s.type==="fill_gap"&&t.type==="fill_gap"?he(s.template,s.blanks).map(n=>n.type==="text"?n.text:`[${_nullishCoalesce(t.values[n.id], () => (""))}]`).join(""):""}var _jsxruntime = require('react/jsx-runtime');function lt({item:e}){return e.locked?_jsxruntime.jsx.call(void 0, _lucidereact.Lock,{className:"size-4 text-neutral-500","aria-hidden":!0}):e.completed?_jsxruntime.jsx.call(void 0, _lucidereact.CheckCircle2,{className:"size-4 text-emerald-600 dark:text-emerald-400","aria-hidden":!0}):e.kind==="test"?_jsxruntime.jsx.call(void 0, _lucidereact.ClipboardCheck,{className:"size-4 text-neutral-500","aria-hidden":!0}):e.durationSeconds>0?_jsxruntime.jsx.call(void 0, _lucidereact.PlayCircle,{className:"size-4 text-neutral-500","aria-hidden":!0}):_jsxruntime.jsx.call(void 0, _lucidereact.FileText,{className:"size-4 text-neutral-500","aria-hidden":!0})}function Jt({item:e,selected:t,variant:s,onSelect:o}){let{texts:n}=h(),i=e.lockReason?w(n.lockedByTest,{test:e.lockReason.gatingTestTitle}):n.locked,a=e.locked?i:e.completed?n.completed:void 0;return _jsxruntime.jsx.call(void 0, "li",{children:_jsxruntime.jsxs.call(void 0, "button",{type:"button","data-item-id":e.id,"data-locked":e.locked||void 0,"aria-current":t?"true":void 0,"aria-disabled":e.locked||void 0,title:e.locked?i:void 0,onClick:()=>{e.locked||o(e)},className:_chunkERL4Z7M7js.d.call(void 0, "flex w-full items-start gap-3 rounded-lg p-2 text-left text-sm outline-none transition focus-visible:ring-[3px] focus-visible:ring-ring/50",t?"bg-neutral-200 dark:bg-neutral-700":"hover:bg-neutral-100 dark:hover:bg-neutral-800",e.locked&&"cursor-not-allowed opacity-60 hover:bg-transparent dark:hover:bg-transparent"),children:[s==="playlist"&&e.kind==="lesson"&&_jsxruntime.jsxs.call(void 0, "span",{className:"relative aspect-video w-24 shrink-0 overflow-hidden rounded bg-neutral-200 dark:bg-neutral-800",children:[e.thumbnailUrl&&_jsxruntime.jsx.call(void 0, "img",{src:e.thumbnailUrl,alt:"",loading:"lazy",draggable:!1,className:"size-full object-cover"}),e.durationSeconds>0&&_jsxruntime.jsx.call(void 0, "span",{className:"absolute right-1 bottom-1 rounded bg-black/75 px-1 text-[10px] text-white",children:Se(e.durationSeconds)})]}),(s==="modules"||e.kind==="test")&&_jsxruntime.jsx.call(void 0, "span",{className:"mt-0.5 shrink-0",children:_jsxruntime.jsx.call(void 0, lt,{item:e})}),_jsxruntime.jsxs.call(void 0, "span",{className:"min-w-0 flex-1",children:[_jsxruntime.jsx.call(void 0, "span",{className:"block font-medium break-words",children:e.title}),_jsxruntime.jsxs.call(void 0, "span",{className:"mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-neutral-500 dark:text-neutral-400",children:[e.kind==="test"&&_jsxruntime.jsx.call(void 0, "span",{children:n.testBadge}),e.isFree&&!e.locked&&_jsxruntime.jsx.call(void 0, "span",{children:n.free}),s==="modules"&&e.durationSeconds>0&&_jsxruntime.jsx.call(void 0, "span",{children:Se(e.durationSeconds)}),a&&_jsxruntime.jsx.call(void 0, "span",{children:a})]})]}),s==="playlist"&&e.kind==="lesson"&&_jsxruntime.jsx.call(void 0, "span",{className:"mt-0.5 shrink-0",children:_jsxruntime.jsx.call(void 0, lt,{item:e})})]})})}function ct({course:e,selectedId:t,onSelect:s,variant:o,className:n}){let{texts:i}=h(),a=r=>_jsxruntime.jsx.call(void 0, "ul",{className:"flex flex-col gap-1",children:r.map(c=>_jsxruntime.jsx.call(void 0, Jt,{item:c,selected:c.id===t,variant:o,onSelect:s},c.id))});return _jsxruntime.jsxs.call(void 0, "nav",{"aria-label":i.contents,className:_chunkERL4Z7M7js.d.call(void 0, "space-y-4",n),children:[e.sections.map(r=>{let c=r.items.filter(y=>y.kind==="lesson"),b=c.filter(y=>y.completed).length;return _jsxruntime.jsxs.call(void 0, "section",{"data-section-id":r.id,children:[_jsxruntime.jsxs.call(void 0, "h3",{className:"mb-1 flex items-baseline justify-between gap-2 px-2 text-sm font-semibold",children:[_jsxruntime.jsx.call(void 0, "span",{className:"break-words",children:r.title}),c.length>0&&_jsxruntime.jsxs.call(void 0, "span",{className:"shrink-0 text-xs font-normal text-neutral-500 dark:text-neutral-400",children:[b,"/",c.length]})]}),a(r.items)]},r.id)}),e.finalItems.length>0&&_jsxruntime.jsx.call(void 0, "section",{"data-section-id":"final",children:a(e.finalItems)})]})}var _connect = require('@connectrpc/connect');var _codegenv2 = require('@bufbuild/protobuf/codegenv2');var _wkt = require('@bufbuild/protobuf/wkt');var qt=_codegenv2.fileDesc.call(void 0, "CiFwdWJsaWMvdjEvdXNlcl9wYW5lbF9jb3Vyc2UucHJvdG8SDXB1YmxpY19hcGkudjEiRAoPUGFuZWxMb2NrUmVhc29uEhYKDmdhdGluZ190ZXN0X2lkGAEgASgJEhkKEWdhdGluZ190ZXN0X3RpdGxlGAIgASgJIrQCCg9QYW5lbENvdXJzZUl0ZW0SCgoCaWQYASABKAkSMAoEa2luZBgCIAEoDjIiLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VJdGVtS2luZBINCgV0aXRsZRgDIAEoCRIPCgdpc19mcmVlGAQgASgIEg4KBmxvY2tlZBgFIAEoCBI4Cgtsb2NrX3JlYXNvbhgGIAEoCzIeLnB1YmxpY19hcGkudjEuUGFuZWxMb2NrUmVhc29uSACIAQESEQoJY29tcGxldGVkGAcgASgIEhgKEGR1cmF0aW9uX3NlY29uZHMYCCABKAUSGgoNdGh1bWJuYWlsX3VybBgJIAEoCUgBiAEBEg4KBmdhdGluZxgKIAEoCEIOCgxfbG9ja19yZWFzb25CEAoOX3RodW1ibmFpbF91cmwicwoSUGFuZWxDb3Vyc2VTZWN0aW9uEgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEi0KBWl0ZW1zGAQgAygLMh4ucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUl0ZW0i3wIKC1BhbmVsQ291cnNlEgoKAmlkGAEgASgJEiwKBHR5cGUYAiABKA4yHi5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlVHlwZRINCgV0aXRsZRgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIWCgljb3Zlcl91cmwYBSABKAlIAIgBARIVCg10b3RhbF9sZXNzb25zGAYgASgFEhkKEWNvbXBsZXRlZF9sZXNzb25zGAcgASgFEhgKEHByb2dyZXNzX3BlcmNlbnQYCCABKAUSMwoIc2VjdGlvbnMYCSADKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlU2VjdGlvbhIzCgtmaW5hbF9pdGVtcxgKIAMoCzIeLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VJdGVtEhYKDnJlc3VtZV9pdGVtX2lkGAsgASgJQgwKCl9jb3Zlcl91cmwiRAoZVXNlclBhbmVsR2V0Q291cnNlUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJIosBChpVc2VyUGFuZWxHZXRDb3Vyc2VSZXNwb25zZRItCgdzdWNjZXNzGAEgASgLMhoucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAQggKBnJlc3VsdCJfCgpQYW5lbFZpZGVvEg8KB2hsc191cmwYASABKAkSFwoKcG9zdGVyX3VybBgCIAEoCUgAiAEBEhgKEGR1cmF0aW9uX3NlY29uZHMYAyABKAVCDQoLX3Bvc3Rlcl91cmwiRgoPUGFuZWxBdHRhY2htZW50EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSDAoEc2l6ZRgDIAEoAxILCgN1cmwYBCABKAkiTAoMUGFuZWxJdGVtUmVmEgoKAmlkGAEgASgJEjAKBGtpbmQYAiABKA4yIi5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlSXRlbUtpbmQigQQKC1BhbmVsTGVzc29uEgoKAmlkGAEgASgJEhIKCnNlY3Rpb25faWQYAiABKAkSDQoFdGl0bGUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFAoMY29udGVudF9odG1sGAUgASgJEi0KBXZpZGVvGAYgASgLMhkucHVibGljX2FwaS52MS5QYW5lbFZpZGVvSACIAQESMwoLYXR0YWNobWVudHMYByADKAsyHi5wdWJsaWNfYXBpLnYxLlBhbmVsQXR0YWNobWVudBIuCgRwcmV2GAggASgLMhsucHVibGljX2FwaS52MS5QYW5lbEl0ZW1SZWZIAYgBARIuCgRuZXh0GAkgASgLMhsucHVibGljX2FwaS52MS5QYW5lbEl0ZW1SZWZIAogBARIRCgljb21wbGV0ZWQYCiABKAgSGAoQcG9zaXRpb25fc2Vjb25kcxgLIAEoBRIRCglteV9yYXRpbmcYDCABKAUSFgoOYXZlcmFnZV9yYXRpbmcYDSABKAISFAoMcmF0aW5nX2NvdW50GA4gASgFEjcKDnVybHNfZXhwaXJlX2F0GA8gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgDiAEBQggKBl92aWRlb0IHCgVfcHJldkIHCgVfbmV4dEIRCg9fdXJsc19leHBpcmVfYXQiVwoZVXNlclBhbmVsR2V0TGVzc29uUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEhEKCWxlc3Nvbl9pZBgDIAEoCSKLAQoaVXNlclBhbmVsR2V0TGVzc29uUmVzcG9uc2USLQoHc3VjY2VzcxgBIAEoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25IABI0CgdmYWlsdXJlGAIgASgOMiEucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUZhaWx1cmVIAEIICgZyZXN1bHQipwEKIlVzZXJQYW5lbFNhdmVMZXNzb25Qcm9ncmVzc1JlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIRCglsZXNzb25faWQYAyABKAkSEQoJY29tcGxldGVkGAQgASgIEhgKEHBvc2l0aW9uX3NlY29uZHMYBSABKAUSGAoQZHVyYXRpb25fc2Vjb25kcxgGIAEoBSKFAgojVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2USTAoHc3VjY2VzcxgBIAEoCzI5LnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaUAoGUmVzdWx0EhEKCWNvbXBsZXRlZBgBIAEoCBIZChFjb21wbGV0ZWRfbGVzc29ucxgCIAEoBRIYChBwcm9ncmVzc19wZXJjZW50GAMgASgFQggKBnJlc3VsdCIoCgtQYW5lbENob2ljZRIKCgJpZBgBIAEoCRINCgVsYWJlbBgCIAEoCSJPCg5QYW5lbE1jcUNvbmZpZxIrCgdjaG9pY2VzGAEgAygLMhoucHVibGljX2FwaS52MS5QYW5lbENob2ljZRIQCghtdWx0aXBsZRgCIAEoCCKCAQoTUGFuZWxUb2RvbGlzdENvbmZpZxI2CgVzdGVwcxgBIAMoCzInLnB1YmxpY19hcGkudjEuUGFuZWxUb2RvbGlzdENvbmZpZy5TdGVwGjMKBFN0ZXASCgoCaWQYASABKAkSDQoFbGFiZWwYAiABKAkSEAoIcmVxdWlyZWQYAyABKAgiNgoSUGFuZWxGaWxsR2FwQ29uZmlnEhAKCHRlbXBsYXRlGAEgASgJEg4KBmJsYW5rcxgCIAMoCSJBChRQYW5lbFNvcnRPcmRlckNvbmZpZxIpCgVpdGVtcxgBIAMoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxDaG9pY2UioQIKEVBhbmVsVGVzdFF1ZXN0aW9uEgoKAmlkGAEgASgJEg4KBnByb21wdBgCIAEoCRIOCgZwb2ludHMYAyABKAUSLAoDbWNxGAogASgLMh0ucHVibGljX2FwaS52MS5QYW5lbE1jcUNvbmZpZ0gAEjYKCHRvZG9saXN0GAsgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRvZG9saXN0Q29uZmlnSAASNQoIZmlsbF9nYXAYDCABKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsRmlsbEdhcENvbmZpZ0gAEjkKCnNvcnRfb3JkZXIYDSABKAsyIy5wdWJsaWNfYXBpLnYxLlBhbmVsU29ydE9yZGVyQ29uZmlnSABCCAoGY29uZmlnIqsCCglQYW5lbFRlc3QSCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSDgoGZ2F0aW5nGAQgASgIEh4KFnBhc3NfdGhyZXNob2xkX3BlcmNlbnQYBSABKAUSGAoQYXR0ZW1wdHNfYWxsb3dlZBgGIAEoBRIaCg1hdHRlbXB0c19sZWZ0GAcgASgFSACIAQESFQoNYXR0ZW1wdF9jb3VudBgIIAEoBRIaChJiZXN0X3Njb3JlX3BlcmNlbnQYCSABKAUSDgoGcGFzc2VkGAogASgIEjMKCXF1ZXN0aW9ucxgLIAMoCzIgLnB1YmxpY19hcGkudjEuUGFuZWxUZXN0UXVlc3Rpb25CEAoOX2F0dGVtcHRzX2xlZnQiUwoXVXNlclBhbmVsR2V0VGVzdFJlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIPCgd0ZXN0X2lkGAMgASgJIocBChhVc2VyUGFuZWxHZXRUZXN0UmVzcG9uc2USKwoHc3VjY2VzcxgBIAEoCzIYLnB1YmxpY19hcGkudjEuUGFuZWxUZXN0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSABCCAoGcmVzdWx0IiYKDlBhbmVsTWNxQW5zd2VyEhQKDHNlbGVjdGVkX2lkcxgBIAMoCSIqChNQYW5lbFRvZG9saXN0QW5zd2VyEhMKC2NoZWNrZWRfaWRzGAEgAygJIoIBChJQYW5lbEZpbGxHYXBBbnN3ZXISPQoGdmFsdWVzGAEgAygLMi0ucHVibGljX2FwaS52MS5QYW5lbEZpbGxHYXBBbnN3ZXIuVmFsdWVzRW50cnkaLQoLVmFsdWVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASIrChRQYW5lbFNvcnRPcmRlckFuc3dlchITCgtvcmRlcmVkX2lkcxgBIAMoCSKDAgoLUGFuZWxBbnN3ZXISEwoLcXVlc3Rpb25faWQYASABKAkSLAoDbWNxGAogASgLMh0ucHVibGljX2FwaS52MS5QYW5lbE1jcUFuc3dlckgAEjYKCHRvZG9saXN0GAsgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRvZG9saXN0QW5zd2VySAASNQoIZmlsbF9nYXAYDCABKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsRmlsbEdhcEFuc3dlckgAEjkKCnNvcnRfb3JkZXIYDSABKAsyIy5wdWJsaWNfYXBpLnYxLlBhbmVsU29ydE9yZGVyQW5zd2VySABCBwoFdmFsdWUijQEKE1BhbmVsVGVzdFN1Ym1pc3Npb24SCgoCaWQYASABKAkSFQoNc2NvcmVfcGVyY2VudBgCIAEoBRIOCgZwYXNzZWQYAyABKAgSEwoLZHVyYXRpb25fbXMYBCABKAMSLgoKY3JlYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAilwEKE1BhbmVsUXVlc3Rpb25SZXN1bHQSEwoLcXVlc3Rpb25faWQYASABKAkSDwoHY29ycmVjdBgCIAEoCBIOCgZwb2ludHMYAyABKAUSNwoOY29ycmVjdF9hbnN3ZXIYBCABKAsyGi5wdWJsaWNfYXBpLnYxLlBhbmVsQW5zd2VySACIAQFCEQoPX2NvcnJlY3RfYW5zd2VyIpgBChpVc2VyUGFuZWxTdWJtaXRUZXN0UmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEg8KB3Rlc3RfaWQYAyABKAkSKwoHYW5zd2VycxgEIAMoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxBbnN3ZXISEwoLZHVyYXRpb25fbXMYBSABKAMiyQIKG1VzZXJQYW5lbFN1Ym1pdFRlc3RSZXNwb25zZRJECgdzdWNjZXNzGAEgASgLMjEucHVibGljX2FwaS52MS5Vc2VyUGFuZWxTdWJtaXRUZXN0UmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaowEKBlJlc3VsdBI2CgpzdWJtaXNzaW9uGAEgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRlc3RTdWJtaXNzaW9uEjMKB3Jlc3VsdHMYAiADKAsyIi5wdWJsaWNfYXBpLnYxLlBhbmVsUXVlc3Rpb25SZXN1bHQSGgoNYXR0ZW1wdHNfbGVmdBgDIAEoBUgAiAEBQhAKDl9hdHRlbXB0c19sZWZ0QggKBnJlc3VsdCJfCiNVc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEg8KB3Rlc3RfaWQYAyABKAkiqwIKJFVzZXJQYW5lbExpc3RUZXN0U3VibWlzc2lvbnNSZXNwb25zZRJNCgdzdWNjZXNzGAEgASgLMjoucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAadAoGUmVzdWx0EjcKC3N1Ym1pc3Npb25zGAEgAygLMiIucHVibGljX2FwaS52MS5QYW5lbFRlc3RTdWJtaXNzaW9uEhoKEmJlc3Rfc2NvcmVfcGVyY2VudBgCIAEoBRIVCg1hdHRlbXB0X2NvdW50GAMgASgFQggKBnJlc3VsdCJWChNQYW5lbFF1ZXN0aW9uQW5zd2VyEg8KB2NvbnRlbnQYASABKAkSLgoKY3JlYXRlZF9hdBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAizAEKE1BhbmVsTGVzc29uUXVlc3Rpb24SCgoCaWQYASABKAkSDwoHY29udGVudBgCIAEoCRITCgthdXRob3JfbmFtZRgDIAEoCRIPCgdpc19taW5lGAQgASgIEi4KCmNyZWF0ZWRfYXQYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjcKBmFuc3dlchgGIAEoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxRdWVzdGlvbkFuc3dlckgAiAEBQgkKB19hbnN3ZXIiYQojVXNlclBhbmVsTGlzdExlc3NvblF1ZXN0aW9uc1JlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIRCglsZXNzb25faWQYAyABKAki9gEKJFVzZXJQYW5lbExpc3RMZXNzb25RdWVzdGlvbnNSZXNwb25zZRJNCgdzdWNjZXNzGAEgASgLMjoucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0TGVzc29uUXVlc3Rpb25zUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaPwoGUmVzdWx0EjUKCXF1ZXN0aW9ucxgBIAMoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25RdWVzdGlvbkIICgZyZXN1bHQicAohVXNlclBhbmVsQXNrTGVzc29uUXVlc3Rpb25SZXF1ZXN0EhQKDGFjY2Vzc190b2tlbhgBIAEoCRIRCglhY2Nlc3NfaWQYAiABKAkSEQoJbGVzc29uX2lkGAMgASgJEg8KB2NvbnRlbnQYBCABKAkimwEKIlVzZXJQYW5lbEFza0xlc3NvblF1ZXN0aW9uUmVzcG9uc2USNQoHc3VjY2VzcxgBIAEoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25RdWVzdGlvbkgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAQggKBnJlc3VsdCJ4ChpVc2VyUGFuZWxSYXRlTGVzc29uUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEhEKCWxlc3Nvbl9pZBgDIAEoCRINCgVzY29yZRgEIAEoBRIPCgdjb21tZW50GAUgASgJIu4BChtVc2VyUGFuZWxSYXRlTGVzc29uUmVzcG9uc2USRAoHc3VjY2VzcxgBIAEoCzIxLnB1YmxpY19hcGkudjEuVXNlclBhbmVsUmF0ZUxlc3NvblJlc3BvbnNlLlJlc3VsdEgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAGkkKBlJlc3VsdBIRCglteV9yYXRpbmcYASABKAUSFgoOYXZlcmFnZV9yYXRpbmcYAiABKAISFAoMcmF0aW5nX2NvdW50GAMgASgFQggKBnJlc3VsdCr6AgoSUGFuZWxDb3Vyc2VGYWlsdXJlEiAKHFBBTkVMX0NPVVJTRV9GQUlMVVJFX1VOS05PV04QABIlCiFQQU5FTF9DT1VSU0VfRkFJTFVSRV9VTkFVVEhPUklaRUQQARIpCiVQQU5FTF9DT1VSU0VfRkFJTFVSRV9DT1VSU0VfTk9UX0ZPVU5EEAISJwojUEFORUxfQ09VUlNFX0ZBSUxVUkVfSVRFTV9OT1RfRk9VTkQQAxIkCiBQQU5FTF9DT1VSU0VfRkFJTFVSRV9JVEVNX0xPQ0tFRBAEEikKJVBBTkVMX0NPVVJTRV9GQUlMVVJFX05PX0FUVEVNUFRTX0xFRlQQBRIpCiVQQU5FTF9DT1VSU0VfRkFJTFVSRV9JTlZBTElEX0FSR1VNRU5UEAYSJQohUEFORUxfQ09VUlNFX0ZBSUxVUkVfUkFURV9MSU1JVEVEEAcSJAogUEFORUxfQ09VUlNFX0ZBSUxVUkVfVU5BVkFJTEFCTEUQCCptCg9QYW5lbENvdXJzZVR5cGUSHQoZUEFORUxfQ09VUlNFX1RZUEVfVU5LTk9XThAAEh4KGlBBTkVMX0NPVVJTRV9UWVBFX1NUQU5EQVJEEAESGwoXUEFORUxfQ09VUlNFX1RZUEVfVklERU8QAip9ChNQYW5lbENvdXJzZUl0ZW1LaW5kEiIKHlBBTkVMX0NPVVJTRV9JVEVNX0tJTkRfVU5LTk9XThAAEiEKHVBBTkVMX0NPVVJTRV9JVEVNX0tJTkRfTEVTU09OEAESHwobUEFORUxfQ09VUlNFX0lURU1fS0lORF9URVNUEAIy+QcKFlVzZXJQYW5lbENvdXJzZVNlcnZpY2USYAoJR2V0Q291cnNlEigucHVibGljX2FwaS52MS5Vc2VyUGFuZWxHZXRDb3Vyc2VSZXF1ZXN0GikucHVibGljX2FwaS52MS5Vc2VyUGFuZWxHZXRDb3Vyc2VSZXNwb25zZRJgCglHZXRMZXNzb24SKC5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldExlc3NvblJlcXVlc3QaKS5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldExlc3NvblJlc3BvbnNlEnsKElNhdmVMZXNzb25Qcm9ncmVzcxIxLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVxdWVzdBoyLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2USWgoHR2V0VGVzdBImLnB1YmxpY19hcGkudjEuVXNlclBhbmVsR2V0VGVzdFJlcXVlc3QaJy5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldFRlc3RSZXNwb25zZRJjCgpTdWJtaXRUZXN0EikucHVibGljX2FwaS52MS5Vc2VyUGFuZWxTdWJtaXRUZXN0UmVxdWVzdBoqLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU3VibWl0VGVzdFJlc3BvbnNlEn4KE0xpc3RUZXN0U3VibWlzc2lvbnMSMi5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbExpc3RUZXN0U3VibWlzc2lvbnNSZXF1ZXN0GjMucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVzcG9uc2USfgoTTGlzdExlc3NvblF1ZXN0aW9ucxIyLnB1YmxpY19hcGkudjEuVXNlclBhbmVsTGlzdExlc3NvblF1ZXN0aW9uc1JlcXVlc3QaMy5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbExpc3RMZXNzb25RdWVzdGlvbnNSZXNwb25zZRJ4ChFBc2tMZXNzb25RdWVzdGlvbhIwLnB1YmxpY19hcGkudjEuVXNlclBhbmVsQXNrTGVzc29uUXVlc3Rpb25SZXF1ZXN0GjEucHVibGljX2FwaS52MS5Vc2VyUGFuZWxBc2tMZXNzb25RdWVzdGlvblJlc3BvbnNlEmMKClJhdGVMZXNzb24SKS5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbFJhdGVMZXNzb25SZXF1ZXN0GioucHVibGljX2FwaS52MS5Vc2VyUGFuZWxSYXRlTGVzc29uUmVzcG9uc2ViBnByb3RvMw",[_wkt.file_google_protobuf_timestamp]);var ut=_codegenv2.serviceDesc.call(void 0, qt,0);var Z=class extends Error{constructor(t,s){super(_nullishCoalesce(s, () => (t))),this.name="CourseError",this.code=t}};var $t={0:"unknown",1:"unauthorized",2:"course_not_found",3:"item_not_found",4:"item_locked",5:"no_attempts_left",6:"invalid_argument",7:"rate_limited",8:"unavailable"};function es(e){return _nullishCoalesce($t[e], () => ("unknown"))}function ts(e){if(e instanceof Z)return e;let t=_connect.ConnectError.from(e);switch(t.code){case _connect.Code.Unauthenticated:case _connect.Code.PermissionDenied:return new Z("unauthorized",t.rawMessage);case _connect.Code.NotFound:return new Z("course_not_found",t.rawMessage);case _connect.Code.ResourceExhausted:return new Z("rate_limited",t.rawMessage);case _connect.Code.Unavailable:case _connect.Code.DeadlineExceeded:return new Z("unavailable",t.rawMessage);default:return e}}function F(e){let{result:t}=e;if(t.case==="success")return t.value;throw t.case==="failure"?new Z(es(t.value)):new Z("unknown","empty response")}function dt(e){return{id:e.id,kind:e.kind===2?"test":"lesson",title:e.title,isFree:e.isFree,locked:e.locked,lockReason:e.locked&&e.lockReason!=null?{gatingTestId:e.lockReason.gatingTestId,gatingTestTitle:e.lockReason.gatingTestTitle}:void 0,completed:e.completed,durationSeconds:e.durationSeconds,thumbnailUrl:e.thumbnailUrl||void 0,gating:e.gating}}function ss(e){return{id:e.id,type:e.type===2?"video":"standard",title:e.title,description:e.description,coverUrl:e.coverUrl||void 0,totalLessons:e.totalLessons,completedLessons:e.completedLessons,progressPercent:e.progressPercent,sections:e.sections.map(t=>({id:t.id,title:t.title,description:t.description,items:t.items.map(dt)})),finalItems:e.finalItems.map(dt),resumeItemId:e.resumeItemId}}function pt(e){return e==null?void 0:{id:e.id,kind:e.kind===2?"test":"lesson"}}function ns(e){return{id:e.id,sectionId:e.sectionId,title:e.title,description:e.description,contentHtml:e.contentHtml,video:e.video!=null&&e.video.hlsUrl!==""?{hlsUrl:e.video.hlsUrl,posterUrl:e.video.posterUrl||void 0,durationSeconds:e.video.durationSeconds}:void 0,attachments:e.attachments.map(t=>({id:t.id,name:t.name,size:Number(t.size),url:t.url})),prev:pt(e.prev),next:pt(e.next),completed:e.completed,positionSeconds:e.positionSeconds,myRating:e.myRating,averageRating:e.averageRating,ratingCount:e.ratingCount,urlsExpireAt:e.urlsExpireAt!=null?_wkt.timestampDate.call(void 0, e.urlsExpireAt):void 0}}function os(e){let t={id:e.id,prompt:e.prompt,points:e.points},{config:s}=e;switch(s.case){case"mcq":return{...t,config:{type:"mcq",choices:s.value.choices.map(o=>({id:o.id,label:o.label})),multiple:s.value.multiple}};case"todolist":return{...t,config:{type:"todolist",steps:s.value.steps.map(o=>({id:o.id,label:o.label,required:o.required}))}};case"fillGap":return{...t,config:{type:"fill_gap",template:s.value.template,blanks:[...s.value.blanks]}};case"sortOrder":return{...t,config:{type:"sort_order",items:s.value.items.map(o=>({id:o.id,label:o.label}))}};default:return}}function rs(e){return{id:e.id,title:e.title,description:e.description,gating:e.gating,passThresholdPercent:e.passThresholdPercent,attemptsAllowed:e.attemptsAllowed,attemptsLeft:e.attemptsLeft,attemptCount:e.attemptCount,bestScorePercent:e.bestScorePercent,passed:e.passed,questions:e.questions.map(os).filter(t=>t!=null)}}function mt(e){return{id:e.id,scorePercent:e.scorePercent,passed:e.passed,durationMs:Number(e.durationMs),createdAt:e.createdAt!=null?_wkt.timestampDate.call(void 0, e.createdAt):void 0}}function as(e){let{value:t}=e;switch(t.case){case"mcq":return{type:"mcq",selectedIds:[...t.value.selectedIds]};case"todolist":return{type:"todolist",checkedIds:[...t.value.checkedIds]};case"fillGap":return{type:"fill_gap",values:{...t.value.values}};case"sortOrder":return{type:"sort_order",orderedIds:[...t.value.orderedIds]};default:return}}function is(e,t){switch(t.type){case"mcq":return{questionId:e,value:{case:"mcq",value:{selectedIds:t.selectedIds}}};case"todolist":return{questionId:e,value:{case:"todolist",value:{checkedIds:t.checkedIds}}};case"fill_gap":return{questionId:e,value:{case:"fillGap",value:{values:t.values}}};case"sort_order":return{questionId:e,value:{case:"sortOrder",value:{orderedIds:t.orderedIds}}}}}function gt(e){return{id:e.id,content:e.content,authorName:e.authorName,isMine:e.isMine,createdAt:e.createdAt!=null?_wkt.timestampDate.call(void 0, e.createdAt):void 0,answer:e.answer!=null?{content:e.answer.content,createdAt:e.answer.createdAt!=null?_wkt.timestampDate.call(void 0, e.answer.createdAt):void 0}:void 0}}function ve(e,t){let s=()=>({accessId:t.accessId,accessToken:typeof t.accessToken=="function"?_nullishCoalesce(t.accessToken(), () => ("")):t.accessToken});async function o(n){try{return await n()}catch(i){throw ts(i)}}return{getCourse:()=>o(async()=>ss(F(await e.getCourse(s())))),getLesson:n=>o(async()=>ns(F(await e.getLesson({...s(),lessonId:n})))),saveProgress:n=>o(async()=>{let i=F(await e.saveLessonProgress({...s(),...n}));return{completed:i.completed,completedLessons:i.completedLessons,progressPercent:i.progressPercent}}),getTest:n=>o(async()=>rs(F(await e.getTest({...s(),testId:n})))),submitTest:(n,i,a)=>o(async()=>{let r=F(await e.submitTest({...s(),testId:n,durationMs:BigInt(Math.max(0,Math.round(a))),answers:Object.entries(i).map(([c,b])=>is(c,b))}));if(r.submission==null)throw new Z("unknown","missing submission");return{submission:mt(r.submission),results:r.results.map(c=>({questionId:c.questionId,correct:c.correct,points:c.points,correctAnswer:c.correctAnswer!=null?as(c.correctAnswer):void 0})),attemptsLeft:r.attemptsLeft}}),listSubmissions:n=>o(async()=>{let i=F(await e.listTestSubmissions({...s(),testId:n}));return{submissions:i.submissions.map(mt),bestScorePercent:i.bestScorePercent,attemptCount:i.attemptCount}}),listQuestions:n=>o(async()=>F(await e.listLessonQuestions({...s(),lessonId:n})).questions.map(gt)),askQuestion:(n,i)=>o(async()=>gt(F(await e.askLessonQuestion({...s(),lessonId:n,content:i})))),rate:(n,i,a="")=>o(async()=>{let r=F(await e.rateLesson({...s(),lessonId:n,score:i,comment:a}));return{myRating:r.myRating,averageRating:r.averageRating,ratingCount:r.ratingCount}})}}function Ce(e){let t=_nullishCoalesce(e.host, () => (_chunk6IACFTJRjs.m.call(void 0, ))),s=_connect.createClient.call(void 0, ut,_chunk6IACFTJRjs.n.call(void 0, t));return ve(s,{accessId:e.accessId,accessToken:_nullishCoalesce(e.accessToken, () => ((()=>{let{projectID:o,host:n}=_chunkDGUIG334js.b.call(void 0, );return _chunkDGUIG334js.k.call(void 0, o,n)})))})}function bt({value:e,onSelect:t,disabled:s}){let{texts:o}=h(),[n,i]=_react.useState.call(void 0, 0),a=n||e;return _jsxruntime.jsx.call(void 0, "div",{className:"flex items-center gap-1",role:t?"radiogroup":"img","aria-label":w(o.rateStar,{n:Math.round(e)}),children:[1,2,3,4,5].map(r=>{let c=r<=Math.round(a),b=_jsxruntime.jsx.call(void 0, _lucidereact.Star,{className:_chunkERL4Z7M7js.d.call(void 0, "size-5",c?"fill-amber-400 text-amber-400":"text-neutral-300 dark:text-neutral-600"),"aria-hidden":!0});return t?_jsxruntime.jsx.call(void 0, "button",{type:"button",role:"radio","aria-checked":r===e,"aria-label":w(o.rateStar,{n:r}),disabled:s,className:"rounded p-0.5 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50",onMouseEnter:()=>i(r),onMouseLeave:()=>i(0),onClick:()=>t(r),children:b},r):_jsxruntime.jsx.call(void 0, "span",{children:b},r)})})}var ft="bg-[var(--course-accent,var(--primary))] text-[var(--course-accent-fg,var(--primary-foreground))] dark:bg-[var(--course-accent-dark,var(--course-accent,var(--primary)))] dark:text-[var(--course-accent-dark-fg,var(--course-accent-fg,var(--primary-foreground)))]",$=_chunkERL4Z7M7js.d.call(void 0, ft,"hover:bg-[var(--course-accent,var(--primary))] hover:opacity-90 dark:hover:bg-[var(--course-accent-dark,var(--course-accent,var(--primary)))]"),z="rounded-xl bg-white p-4 drop-shadow-xl sm:p-6 dark:border dark:border-neutral-700 dark:bg-neutral-900 dark:drop-shadow-none";function J(e,t){if(e instanceof Z)switch(e.code){case"unauthorized":return t.errorUnauthorized;case"course_not_found":case"item_not_found":return t.errorNotFound;case"unavailable":return t.errorUnavailable}return t.errorGeneric}function ee({error:e,onRetry:t,className:s}){let{texts:o}=h();return _jsxruntime.jsxs.call(void 0, "div",{role:"alert",className:_chunkERL4Z7M7js.d.call(void 0, "flex flex-col items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700 dark:border-red-900 dark:bg-red-950 dark:text-red-300",s),children:[_jsxruntime.jsxs.call(void 0, "p",{className:"flex items-center gap-2 font-medium",children:[_jsxruntime.jsx.call(void 0, _lucidereact.AlertTriangle,{className:"size-4 shrink-0","aria-hidden":!0}),J(e,o)]}),t&&_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{variant:"outline",size:"sm",onClick:t,children:o.retry})]})}function ue({label:e}){let{texts:t}=h();return _jsxruntime.jsxs.call(void 0, "div",{role:"status","aria-label":_nullishCoalesce(e, () => (t.loading)),className:"space-y-3",children:[_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.u,{className:"aspect-video w-full"}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.u,{className:"h-6 w-2/3"}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.u,{className:"h-4 w-full"}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.u,{className:"h-4 w-5/6"})]})}function At({percent:e,label:t}){let s=Math.max(0,Math.min(100,Math.round(e)));return _jsxruntime.jsx.call(void 0, "div",{role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":s,"aria-label":t,className:"h-2 w-full overflow-hidden rounded-full bg-neutral-200 dark:bg-neutral-700",children:_jsxruntime.jsx.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, "h-full rounded-full transition-all duration-300",ft),style:{width:`${s}%`}})})}function j(e,t,s){let[o,n]=_react.useState.call(void 0, s!==void 0?{status:"success",data:s}:{status:"loading"}),[i,a]=_react.useState.call(void 0, 0),r=_react.useRef.call(void 0, 0),c=_react.useRef.call(void 0, e);c.current=e;let b=_react.useRef.call(void 0, s!==void 0);_react.useEffect.call(void 0, ()=>{if(b.current){b.current=!1;return}let p=++r.current;return n({status:"loading"}),c.current().then(A=>{p===r.current&&n({status:"success",data:A})}).catch(A=>{p===r.current&&n({status:"error",error:A})}),()=>{r.current+=1}},[...t,i]);let y=_react.useCallback.call(void 0, ()=>a(p=>p+1),[]),f=_react.useCallback.call(void 0, ()=>{let p=++r.current;c.current().then(A=>{p===r.current&&n({status:"success",data:A})}).catch(()=>{})},[]),d=_react.useCallback.call(void 0, p=>{n(A=>A.status==="success"?{status:"success",data:p(A.data)}:A)},[]);return{...o,reload:y,refresh:f,mutate:d}}function St({lessonId:e,initial:t}){let{dataSource:s,texts:o,preview:n}=h(),i=Q(),[a,r]=_react.useState.call(void 0, t),[c,b]=_react.useState.call(void 0, t.myRating),[y,f]=_react.useState.call(void 0, ""),[d,p]=_react.useState.call(void 0, "idle"),[A,C]=_react.useState.call(void 0, );async function I(){if(!(c<1)){p("saving");try{r(await s.rate(e,c,y.trim())),p("saved"),i("lesson_rated",{lesson_id:e,rating:c})}catch(g){C(g),p("error")}}}return _jsxruntime.jsxs.call(void 0, "section",{"aria-labelledby":`rate-${e}`,className:"space-y-3",children:[_jsxruntime.jsx.call(void 0, "h3",{id:`rate-${e}`,className:"text-lg font-semibold",children:o.rateTitle}),_jsxruntime.jsx.call(void 0, bt,{value:c,onSelect:g=>{b(g),p("idle")},disabled:n||d==="saving"}),a.ratingCount>0&&_jsxruntime.jsx.call(void 0, "p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:w(o.rateAverage,{average:a.averageRating.toFixed(1),count:a.ratingCount})}),!n&&c>0&&_jsxruntime.jsxs.call(void 0, "div",{className:"space-y-2",children:[_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.v,{value:y,maxLength:1e3,placeholder:o.ratePlaceholder,onChange:g=>{f(g.target.value),p("idle")}}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{size:"sm",className:$,disabled:d==="saving",onClick:I,children:o.rateSubmit})]}),d==="saved"&&_jsxruntime.jsx.call(void 0, "p",{role:"status",className:"text-sm text-emerald-700 dark:text-emerald-400",children:o.rateSaved}),d==="error"&&_jsxruntime.jsx.call(void 0, "p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:J(A,o)})]})}function yt(e){return e==null?"":e.toLocaleDateString()}function ht({lessonId:e}){let{dataSource:t,texts:s,preview:o}=h(),n=j(()=>t.listQuestions(e),[t,e]),[i,a]=_react.useState.call(void 0, ""),[r,c]=_react.useState.call(void 0, "idle"),[b,y]=_react.useState.call(void 0, );async function f(){let d=i.trim();if(d!==""){c("sending");try{let p=await t.askQuestion(e,d);n.mutate(A=>[p,...A]),a(""),c("sent")}catch(p){y(p),c("error")}}}return _jsxruntime.jsxs.call(void 0, "section",{"aria-labelledby":`qa-${e}`,className:"space-y-4",children:[_jsxruntime.jsx.call(void 0, "h3",{id:`qa-${e}`,className:"text-lg font-semibold",children:s.questionsTitle}),_jsxruntime.jsxs.call(void 0, "form",{className:"space-y-2",onSubmit:d=>{d.preventDefault(),f()},children:[_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.v,{value:i,maxLength:2e3,placeholder:s.questionPlaceholder,"aria-label":s.questionPlaceholder,onChange:d=>{a(d.target.value),c("idle")}}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{type:"submit",size:"sm",className:$,disabled:r==="sending"||i.trim()==="",children:s.questionSubmit}),r==="sent"&&_jsxruntime.jsx.call(void 0, "p",{role:"status",className:"text-sm text-emerald-700 dark:text-emerald-400",children:s.questionSent}),r==="error"&&_jsxruntime.jsx.call(void 0, "p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:J(b,s)})]}),n.status==="loading"&&_jsxruntime.jsx.call(void 0, "p",{role:"status",className:"text-sm text-neutral-500",children:s.loading}),n.status==="error"&&_jsxruntime.jsx.call(void 0, ee,{error:n.error,onRetry:n.reload}),n.status==="success"&&(n.data.length===0?_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:s.questionsEmpty}):_jsxruntime.jsx.call(void 0, "ul",{className:"space-y-3",children:n.data.map(d=>_jsxruntime.jsxs.call(void 0, "li",{className:"rounded-lg bg-neutral-100 p-3 text-sm dark:bg-neutral-800",children:[_jsxruntime.jsxs.call(void 0, "p",{className:"mb-1 text-xs text-neutral-500 dark:text-neutral-400",children:[d.isMine?s.questionMine:d.authorName,d.createdAt&&` \xB7 ${yt(d.createdAt)}`]}),_jsxruntime.jsx.call(void 0, "p",{className:"break-words whitespace-pre-wrap",children:d.content}),d.answer&&_jsxruntime.jsxs.call(void 0, "div",{className:"mt-2 border-l-2 border-neutral-400 pl-3 dark:border-neutral-500",children:[_jsxruntime.jsxs.call(void 0, "p",{className:"mb-1 text-xs font-medium text-neutral-500 dark:text-neutral-400",children:[s.questionTeamAnswer,d.answer.createdAt&&` \xB7 ${yt(d.answer.createdAt)}`]}),_jsxruntime.jsx.call(void 0, "p",{className:"break-words whitespace-pre-wrap",children:d.answer.content})]})]},d.id))}))]})}var _dompurify = require('dompurify'); var _dompurify2 = _interopRequireDefault(_dompurify);var gs={USE_PROFILES:{html:!0},FORBID_TAGS:["style","form","input","button","textarea","select","iframe","frame","object","embed","base","meta","link"],FORBID_ATTR:["style","srcdoc","formaction"],ALLOW_DATA_ATTR:!1,RETURN_TRUSTED_TYPE:!1};function bs(e){if(e.isSupported===!1||e.sanitize==null)return()=>"";let t=e.sanitize.bind(e);return _optionalChain([e, 'access', _18 => _18.addHook, 'optionalCall', _19 => _19("afterSanitizeAttributes",s=>{s.tagName==="A"&&s.hasAttribute("href")&&(s.setAttribute("target","_blank"),s.setAttribute("rel","noopener noreferrer nofollow")),s.tagName==="IMG"&&(s.setAttribute("loading","lazy"),s.setAttribute("referrerpolicy","no-referrer"))})]),s=>s==null||s===""?"":String(t(s,gs))}var vt;function Pe(e){return typeof window>"u"?"":(vt??=bs(_dompurify2.default),vt(e))}var _hlsjs = require('hls.js'); var _hlsjs2 = _interopRequireDefault(_hlsjs);function xs(e){return e.canPlayType("application/vnd.apple.mpegurl")!==""}function kt({src:e,poster:t,startAt:s=0,durationHint:o=0,autoPlay:n=!1,lessonId:i,onReport:a,onEnded:r,onExpired:c,className:b}){let{texts:y}=h(),f=Q(),d=_react.useRef.call(void 0, f);d.current=f;let p=_react.useRef.call(void 0, null),[A,C]=_react.useState.call(void 0, !1),[I,g]=_react.useState.call(void 0, 0),k=_react.useRef.call(void 0, {onReport:a,onEnded:r,onExpired:c,durationHint:o});k.current={onReport:a,onEnded:r,onExpired:c,durationHint:o};let M=_react.useRef.call(void 0, void 0),E=_react.useCallback.call(void 0, (l,R=p.current)=>{if(R==null||k.current.onReport==null)return;let m=Math.floor(R.currentTime);if(m<=0&&l!=="ended")return;let T=Number.isFinite(R.duration)?R.duration:k.current.durationHint;M.current=m,k.current.onReport({positionSeconds:m,durationSeconds:Math.floor(T||0),reason:l})},[]);return _react.useEffect.call(void 0, ()=>{let l=p.current;if(l==null)return;C(!1);let R,m=!1,T=!1,B,q=()=>{s>0&&(l.currentTime=s),n&&l.play().catch(()=>{})};xs(l)?(l.src=e,l.addEventListener("loadedmetadata",q,{once:!0})):_hlsjs2.default.isSupported()?(R=new _hlsjs2.default,R.attachMedia(l),R.loadSource(e),R.on(_hlsjs2.default.Events.MANIFEST_PARSED,q),R.on(_hlsjs2.default.Events.ERROR,(O,Y)=>{if(!Y.fatal)return;let Be=_optionalChain([Y, 'access', _20 => _20.response, 'optionalAccess', _21 => _21.code]);Be===401||Be===403?(_optionalChain([k, 'access', _22 => _22.current, 'access', _23 => _23.onExpired, 'optionalCall', _24 => _24()]),C(!0)):Y.type===_hlsjs2.default.ErrorTypes.NETWORK_ERROR&&!m?(m=!0,_optionalChain([R, 'optionalAccess', _25 => _25.startLoad, 'call', _26 => _26()])):Y.type===_hlsjs2.default.ErrorTypes.MEDIA_ERROR&&!T?(T=!0,_optionalChain([R, 'optionalAccess', _27 => _27.recoverMediaError, 'call', _28 => _28()])):C(!0)})):l.src=e;let pe=()=>{clearInterval(B),B=setInterval(()=>E("periodic"),15e3)},te=()=>{clearInterval(B),l.ended||E("pause")},oe=new Set,S=l.currentTime,W=0,re=O=>{if(i!=null)for(let Y of _chunkDF2IK2GNjs.s.call(void 0, O,oe))d.current("video_progress",{lesson_id:i,milestone:Y})},ae=()=>{let O=l.currentTime,Y=Number.isFinite(l.duration)?l.duration:k.current.durationHint;Y>0&&!l.seeking&&O>=S&&O-S<=2&&(W=Math.max(W,O),re(W/Y*100)),S=O},Ue=()=>{clearInterval(B),i!=null&&!oe.has(100)&&(oe.add(100),d.current("video_progress",{lesson_id:i,milestone:100})),E("ended"),_optionalChain([k, 'access', _29 => _29.current, 'access', _30 => _30.onEnded, 'optionalCall', _31 => _31()])},Le=()=>{R==null&&C(!0)};return l.addEventListener("play",pe),l.addEventListener("pause",te),l.addEventListener("ended",Ue),l.addEventListener("error",Le),l.addEventListener("timeupdate",ae),()=>{clearInterval(B),l.removeEventListener("play",pe),l.removeEventListener("pause",te),l.removeEventListener("ended",Ue),l.removeEventListener("error",Le),l.removeEventListener("timeupdate",ae),l.removeEventListener("loadedmetadata",q),!l.ended&&l.currentTime>0&&E("leave",l),_optionalChain([R, 'optionalAccess', _32 => _32.destroy, 'call', _33 => _33()]),l.removeAttribute("src"),l.load()}},[e,I]),_jsxruntime.jsxs.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, "relative overflow-hidden rounded-xl bg-black",b),children:[_jsxruntime.jsx.call(void 0, "video",{ref:p,className:"aspect-video w-full",poster:t,controls:!0,playsInline:!0,preload:"metadata",controlsList:"nodownload"}),A&&_jsxruntime.jsxs.call(void 0, "div",{role:"alert",className:"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-black/80 p-4 text-center text-sm text-white",children:[_jsxruntime.jsx.call(void 0, "p",{children:y.videoError}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{variant:"secondary",size:"sm",onClick:()=>{_optionalChain([k, 'access', _34 => _34.current, 'access', _35 => _35.onExpired, 'optionalCall', _36 => _36()]),g(l=>l+1)},children:y.videoRetry})]})]})}function Vt({lesson:e,position:t=0,courseType:s,prev:o,next:n,autoplay:i,onAutoplayChange:a,startPlaying:r,onNavigate:c,onProgressSaved:b,onEnded:y,onUrlsExpired:f}){let{dataSource:d,texts:p,preview:A}=h(),C=Q(),[I,g]=_react.useState.call(void 0, e.completed),k=_react.useRef.call(void 0, e.completed),M=_react.useRef.call(void 0, void 0),E=_react.useRef.call(void 0, e.positionSeconds),[l,R]=_react.useState.call(void 0, !1);_react.useEffect.call(void 0, ()=>{C("lesson_started",{lesson_id:e.id,position:t})},[e.id]);let m=_optionalChain([e, 'access', _37 => _37.urlsExpireAt, 'optionalAccess', _38 => _38.getTime, 'call', _39 => _39()]);_react.useEffect.call(void 0, ()=>{if(m==null)return;let S=m-Date.now()-6e4;if(S<=0||S>2**31-1)return;let W=setTimeout(f,S);return()=>clearTimeout(W)},[m]);let T=_react.useMemo.call(void 0, ()=>Pe(e.contentHtml),[e.contentHtml]);async function B(S,W,re){if(A)return;let ae=await d.saveProgress({lessonId:e.id,completed:re,positionSeconds:S,durationSeconds:W});M.current=S,ae.completed&&!k.current&&(k.current=!0,g(!0),C("lesson_completed",{lesson_id:e.id,position:t})),b(e.id,ae)}function q(S){if(E.current=S.positionSeconds,A||S.reason==="periodic"&&S.positionSeconds===M.current)return;let W=S.reason==="ended"||tt(S.positionSeconds,S.durationSeconds);B(S.positionSeconds,S.durationSeconds,W&&!k.current).catch(re=>{console.warn("Saving the progress of the lesson failed",re)})}async function pe(){R(!0);try{await B(E.current,_nullishCoalesce(_optionalChain([e, 'access', _40 => _40.video, 'optionalAccess', _41 => _41.durationSeconds]), () => (0)),!0)}catch(S){console.warn("Marking the lesson as completed failed",S)}finally{R(!1)}}let te=e.description.trim(),oe=T!==""||te!=="";return _jsxruntime.jsxs.call(void 0, "article",{"data-lesson-id":e.id,className:"min-w-0 space-y-5","aria-labelledby":`lesson-${e.id}`,children:[e.video&&_jsxruntime.jsx.call(void 0, kt,{src:e.video.hlsUrl,poster:e.video.posterUrl,lessonId:e.id,startAt:st(E.current,e.video.durationSeconds),durationHint:e.video.durationSeconds,autoPlay:r,onReport:q,onEnded:y,onExpired:f}),_jsxruntime.jsxs.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, z,"space-y-5"),children:[_jsxruntime.jsxs.call(void 0, "header",{className:"flex flex-wrap items-start justify-between gap-3",children:[_jsxruntime.jsx.call(void 0, "h2",{id:`lesson-${e.id}`,className:"text-2xl font-semibold break-words",children:e.title}),!A&&_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{size:"sm",variant:I?"outline":"default",className:_chunkERL4Z7M7js.d.call(void 0, !I&&$),disabled:I||l,onClick:pe,children:I?p.markedComplete:p.markComplete})]}),s==="video"&&e.video&&_jsxruntime.jsxs.call(void 0, "label",{className:"flex w-fit cursor-pointer items-center gap-2 text-sm text-neutral-600 dark:text-neutral-300",children:[_jsxruntime.jsx.call(void 0, "input",{type:"checkbox",checked:i,onChange:S=>a(S.target.checked)}),p.autoplay]}),te!==""&&_jsxruntime.jsx.call(void 0, "p",{className:"text-sm break-words whitespace-pre-wrap text-neutral-700 dark:text-neutral-300",children:te}),T!==""&&_jsxruntime.jsx.call(void 0, "div",{className:"bitsnap-course-html break-words [&_a]:underline [&_blockquote]:border-l-4 [&_blockquote]:pl-4 [&_h1]:text-2xl [&_h1]:font-semibold [&_h2]:text-xl [&_h2]:font-semibold [&_h3]:text-lg [&_h3]:font-semibold [&_img]:h-auto [&_img]:max-w-full [&_ol]:list-decimal [&_ol]:pl-6 [&_p]:my-3 [&_pre]:overflow-x-auto [&_table]:block [&_table]:max-w-full [&_table]:overflow-x-auto [&_ul]:list-disc [&_ul]:pl-6",dangerouslySetInnerHTML:{__html:T}}),!oe&&!e.video&&_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:p.noContent}),e.attachments.length>0&&_jsxruntime.jsxs.call(void 0, "section",{"aria-labelledby":`files-${e.id}`,className:"space-y-2",children:[_jsxruntime.jsxs.call(void 0, "h3",{id:`files-${e.id}`,className:"flex items-center gap-2 text-lg font-semibold",children:[_jsxruntime.jsx.call(void 0, _lucidereact.Paperclip,{className:"size-4","aria-hidden":!0}),p.attachments]}),_jsxruntime.jsx.call(void 0, "ul",{className:"space-y-2",children:e.attachments.map(S=>_jsxruntime.jsx.call(void 0, "li",{children:_jsxruntime.jsxs.call(void 0, "a",{href:S.url,target:"_blank",rel:"noopener noreferrer",download:S.name,className:"flex items-center justify-between gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm transition hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-700",children:[_jsxruntime.jsxs.call(void 0, "span",{className:"min-w-0 break-words",children:[S.name,_jsxruntime.jsx.call(void 0, "span",{className:"ml-2 text-xs text-neutral-500 dark:text-neutral-400",children:nt(S.size)})]}),_jsxruntime.jsxs.call(void 0, "span",{className:"flex shrink-0 items-center gap-1 text-xs font-medium",children:[p.download,_jsxruntime.jsx.call(void 0, _lucidereact.Download,{className:"size-3.5","aria-hidden":!0})]})]})},S.id))})]}),_jsxruntime.jsxs.call(void 0, "nav",{className:"flex items-center justify-between gap-3 border-t pt-4",children:[_jsxruntime.jsxs.call(void 0, _chunk6IACFTJRjs.q,{variant:"outline",size:"sm",disabled:o==null,onClick:()=>o&&c(o),children:[_jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft,{"aria-hidden":!0}),p.prevItem]}),_jsxruntime.jsxs.call(void 0, _chunk6IACFTJRjs.q,{variant:"outline",size:"sm",disabled:n==null,onClick:()=>n&&c(n),children:[p.nextItem,_jsxruntime.jsx.call(void 0, _lucidereact.ChevronRight,{"aria-hidden":!0})]})]})]}),_jsxruntime.jsx.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, z),children:_jsxruntime.jsx.call(void 0, St,{lessonId:e.id,initial:{myRating:e.myRating,averageRating:e.averageRating,ratingCount:e.ratingCount}})}),_jsxruntime.jsx.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, z),children:_jsxruntime.jsx.call(void 0, ht,{lessonId:e.id})})]})}var It={pl:{back:"Wr\xF3\u0107",loading:"\u0141adowanie kursu...",retry:"Spr\xF3buj ponownie",errorGeneric:"Nie uda\u0142o si\u0119 za\u0142adowa\u0107 danych. Spr\xF3buj ponownie za chwil\u0119.",errorUnauthorized:"Nie masz dost\u0119pu do tego kursu. Zaloguj si\u0119 ponownie lub skontaktuj si\u0119 z pomoc\u0105.",errorNotFound:"Nie znaleziono tego kursu lub lekcji.",errorUnavailable:"Kurs jest chwilowo niedost\u0119pny. Spr\xF3buj ponownie za chwil\u0119.",previewBadge:"Podgl\u0105d",progressLessons:"{done} z {total} lekcji",progressPercent:"{percent}% uko\u0144czono",contents:"Spis tre\u015Bci",free:"Darmowa",locked:"Zablokowane",lockedByTest:"Odblokuje je zaliczenie testu: {test}",lockedPage:"Ta lekcja jest zablokowana. Zalicz test \u201E{test}\u201D, aby j\u0105 odblokowa\u0107.",testBadge:"Test",completed:"Uko\u0144czono",continueCourse:"Kontynuuj kurs",noItem:"Wybierz lekcj\u0119 z listy.",markComplete:"Oznacz jako uko\u0144czon\u0105",markedComplete:"Lekcja uko\u0144czona",prevItem:"Poprzednia",nextItem:"Nast\u0119pna",attachments:"Materia\u0142y do pobrania",download:"Pobierz",autoplay:"Autoodtwarzanie nast\u0119pnej lekcji",videoError:"Nie uda\u0142o si\u0119 odtworzy\u0107 wideo.",videoRetry:"Od\u015Bwie\u017C wideo",noContent:"Ta lekcja nie ma jeszcze tre\u015Bci.",testGating:"Zaliczenie tego testu odblokowuje kolejne lekcje.",testAttemptsLeft:"Pozosta\u0142o pr\xF3b: {count}",testAttemptsUnlimited:"Liczba pr\xF3b nie jest ograniczona.",testNoAttemptsLeft:"Wykorzystano wszystkie pr\xF3by.",testPassThreshold:"Pr\xF3g zaliczenia: {percent}%",testBestScore:"Najlepszy wynik: {percent}%",testSubmit:"Sprawd\u017A odpowiedzi",testSubmitting:"Sprawdzanie...",testUnanswered:"Pytania bez odpowiedzi: {count}",testPassed:"Test zaliczony",testFailed:"Test niezaliczony",testScore:"Wynik: {percent}%",testCorrect:"Poprawnie",testIncorrect:"Niepoprawnie",testCorrectAnswer:"Poprawna odpowied\u017A",testTryAgain:"Spr\xF3buj ponownie",testPreviousAttempts:"Poprzednie podej\u015Bcia",testAttemptRow:"{percent}% \xB7 {date}",testNoQuestions:"Ten test nie ma jeszcze pyta\u0144.",mcqSingle:"Wybierz jedn\u0105 odpowied\u017A",mcqMultiple:"Zaznacz wszystkie poprawne odpowiedzi",todoHint:"Zaznacz wykonane kroki",todoRequired:"wymagany",gapPlaceholder:"uzupe\u0142nij",sortHint:"U\u0142\xF3\u017C elementy w poprawnej kolejno\u015Bci",moveUp:"Przesu\u0144 w g\xF3r\u0119",moveDown:"Przesu\u0144 w d\xF3\u0142",questionsTitle:"Pytania do lekcji",questionsEmpty:"Nikt jeszcze nie zada\u0142 pytania.",questionPlaceholder:"Zadaj pytanie autorowi kursu",questionSubmit:"Wy\u015Blij pytanie",questionSent:"Pytanie wys\u0142ane.",questionMine:"Ty",questionTeamAnswer:"Odpowied\u017A autora",rateTitle:"Oce\u0144 lekcj\u0119",rateStar:"{n} z 5",ratePlaceholder:"Komentarz (opcjonalnie)",rateSubmit:"Zapisz ocen\u0119",rateSaved:"Dzi\u0119kujemy za ocen\u0119.",rateAverage:"\u015Arednia ocena {average} ({count})"},en:{back:"Back",loading:"Loading the course...",retry:"Try again",errorGeneric:"Could not load the data. Please try again in a moment.",errorUnauthorized:"You do not have access to this course. Sign in again or contact support.",errorNotFound:"This course or lesson was not found.",errorUnavailable:"The course is temporarily unavailable. Please try again in a moment.",previewBadge:"Preview",progressLessons:"{done} of {total} lessons",progressPercent:"{percent}% complete",contents:"Contents",free:"Free",locked:"Locked",lockedByTest:"Pass the test to unlock it: {test}",lockedPage:"This lesson is locked. Pass the test \u201C{test}\u201D to unlock it.",testBadge:"Test",completed:"Completed",continueCourse:"Continue the course",noItem:"Pick a lesson from the list.",markComplete:"Mark as completed",markedComplete:"Lesson completed",prevItem:"Previous",nextItem:"Next",attachments:"Downloads",download:"Download",autoplay:"Autoplay the next lesson",videoError:"The video could not be played.",videoRetry:"Reload the video",noContent:"This lesson has no content yet.",testGating:"Passing this test unlocks the following lessons.",testAttemptsLeft:"Attempts left: {count}",testAttemptsUnlimited:"The number of attempts is unlimited.",testNoAttemptsLeft:"You have used all attempts.",testPassThreshold:"Passing score: {percent}%",testBestScore:"Best score: {percent}%",testSubmit:"Check answers",testSubmitting:"Checking...",testUnanswered:"Unanswered questions: {count}",testPassed:"Test passed",testFailed:"Test not passed",testScore:"Score: {percent}%",testCorrect:"Correct",testIncorrect:"Incorrect",testCorrectAnswer:"Correct answer",testTryAgain:"Try again",testPreviousAttempts:"Previous attempts",testAttemptRow:"{percent}% \xB7 {date}",testNoQuestions:"This test has no questions yet.",mcqSingle:"Choose one answer",mcqMultiple:"Select all correct answers",todoHint:"Check the steps you have done",todoRequired:"required",gapPlaceholder:"fill in",sortHint:"Put the items in the correct order",moveUp:"Move up",moveDown:"Move down",questionsTitle:"Questions about this lesson",questionsEmpty:"Nobody has asked a question yet.",questionPlaceholder:"Ask the course author a question",questionSubmit:"Send question",questionSent:"Question sent.",questionMine:"You",questionTeamAnswer:"Answer from the author",rateTitle:"Rate this lesson",rateStar:"{n} of 5",ratePlaceholder:"Comment (optional)",rateSubmit:"Save rating",rateSaved:"Thanks for your rating.",rateAverage:"Average rating {average} ({count})"}};function Te(e,t=_chunkERL4Z7M7js.m.call(void 0, )){return _chunkERL4Z7M7js.i.call(void 0, It,t,e)}function Ns({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=h();if(e.config.type!=="mcq")return null;let{choices:i,multiple:a}=e.config;return _jsxruntime.jsxs.call(void 0, "fieldset",{className:"space-y-2",disabled:s,children:[_jsxruntime.jsx.call(void 0, "legend",{className:"mb-2 text-xs text-neutral-500 dark:text-neutral-400",children:a?n.mcqMultiple:n.mcqSingle}),i.map(r=>{let c=t.selectedIds.includes(r.id);return _jsxruntime.jsxs.call(void 0, "label",{className:"flex cursor-pointer items-start gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[_jsxruntime.jsx.call(void 0, "input",{className:"mt-0.5",type:a?"checkbox":"radio",name:`q-${e.id}`,checked:c,onChange:()=>o({type:"mcq",selectedIds:a?c?t.selectedIds.filter(b=>b!==r.id):[...t.selectedIds,r.id]:[r.id]})}),_jsxruntime.jsx.call(void 0, "span",{className:"break-words",children:r.label})]},r.id)})]})}function Ps({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=h();return e.config.type!=="todolist"?null:_jsxruntime.jsxs.call(void 0, "fieldset",{className:"space-y-2",disabled:s,children:[_jsxruntime.jsx.call(void 0, "legend",{className:"mb-2 text-xs text-neutral-500 dark:text-neutral-400",children:n.todoHint}),e.config.steps.map(i=>{let a=t.checkedIds.includes(i.id);return _jsxruntime.jsxs.call(void 0, "label",{className:"flex cursor-pointer items-start gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[_jsxruntime.jsx.call(void 0, "input",{className:"mt-0.5",type:"checkbox",checked:a,onChange:()=>o({type:"todolist",checkedIds:a?t.checkedIds.filter(r=>r!==i.id):[...t.checkedIds,i.id]})}),_jsxruntime.jsxs.call(void 0, "span",{className:"break-words",children:[i.label,i.required&&_jsxruntime.jsxs.call(void 0, "span",{className:"ml-2 text-xs text-neutral-500 dark:text-neutral-400",children:["(",n.todoRequired,")"]})]})]},i.id)})]})}function Vs({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=h();if(e.config.type!=="fill_gap")return null;let i=he(e.config.template,e.config.blanks);return _jsxruntime.jsx.call(void 0, "p",{className:"text-sm leading-10 break-words",children:i.map((a,r)=>a.type==="text"?_jsxruntime.jsx.call(void 0, "span",{className:"whitespace-pre-wrap",children:a.text},r):_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.r,{className:"mx-1 inline-block h-8 w-36 align-middle","aria-label":`${n.gapPlaceholder} ${a.id}`,placeholder:n.gapPlaceholder,disabled:s,value:_nullishCoalesce(t.values[a.id], () => ("")),onChange:c=>o({type:"fill_gap",values:{...t.values,[a.id]:c.target.value}})},r))})}function Is({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=h();if(e.config.type!=="sort_order")return null;let i=new Map(e.config.items.map(r=>[r.id,r.label])),a=(r,c)=>o({type:"sort_order",orderedIds:at(t.orderedIds,r,c)});return _jsxruntime.jsxs.call(void 0, "div",{className:"space-y-2",children:[_jsxruntime.jsx.call(void 0, "p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:n.sortHint}),_jsxruntime.jsx.call(void 0, "ol",{className:"space-y-2",children:t.orderedIds.map((r,c)=>_jsxruntime.jsxs.call(void 0, "li",{className:"flex items-center gap-2 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[_jsxruntime.jsxs.call(void 0, "span",{className:"w-5 text-xs text-neutral-500",children:[c+1,"."]}),_jsxruntime.jsx.call(void 0, "span",{className:"min-w-0 flex-1 break-words",children:_nullishCoalesce(i.get(r), () => (r))}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{type:"button",variant:"ghost",size:"icon-xs","aria-label":n.moveUp,disabled:s||c===0,onClick:()=>a(c,c-1),children:_jsxruntime.jsx.call(void 0, _lucidereact.ArrowUp,{"aria-hidden":!0})}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{type:"button",variant:"ghost",size:"icon-xs","aria-label":n.moveDown,disabled:s||c===t.orderedIds.length-1,onClick:()=>a(c,c+1),children:_jsxruntime.jsx.call(void 0, _lucidereact.ArrowDown,{"aria-hidden":!0})})]},r))})]})}function Ts({question:e,answer:t,disabled:s,onChange:o}){let n={question:e,disabled:s,onChange:o};switch(t.type){case"mcq":return _jsxruntime.jsx.call(void 0, Ns,{...n,answer:t});case"todolist":return _jsxruntime.jsx.call(void 0, Ps,{...n,answer:t});case"fill_gap":return _jsxruntime.jsx.call(void 0, Vs,{...n,answer:t});case"sort_order":return _jsxruntime.jsx.call(void 0, Is,{...n,answer:t})}}function ws({list:e}){let{texts:t}=h();return e.submissions.length===0?null:_jsxruntime.jsxs.call(void 0, "section",{className:"space-y-2",children:[_jsxruntime.jsx.call(void 0, "h3",{className:"text-sm font-semibold",children:t.testPreviousAttempts}),_jsxruntime.jsx.call(void 0, "ul",{className:"space-y-1 text-sm",children:e.submissions.map(s=>_jsxruntime.jsxs.call(void 0, "li",{className:"flex items-center gap-2 text-neutral-700 dark:text-neutral-300",children:[s.passed?_jsxruntime.jsx.call(void 0, _lucidereact.Check,{className:"size-4 text-emerald-600","aria-hidden":!0}):_jsxruntime.jsx.call(void 0, _lucidereact.X,{className:"size-4 text-red-600","aria-hidden":!0}),w(t.testAttemptRow,{percent:s.scorePercent,date:_nullishCoalesce(_optionalChain([s, 'access', _42 => _42.createdAt, 'optionalAccess', _43 => _43.toLocaleString, 'call', _44 => _44()]), () => (""))})]},s.id))})]})}function Es({test:e,onSubmitted:t,onTryAgain:s}){let{dataSource:o,texts:n}=h(),[i,a]=_react.useState.call(void 0, {}),[r,c]=_react.useState.call(void 0, ),[b,y]=_react.useState.call(void 0, !1),[f,d]=_react.useState.call(void 0, ),p=_react.useRef.call(void 0, Date.now()),A=_react.useRef.call(void 0, e.questions).current,C=e.attemptsLeft!=null&&e.attemptsLeft<=0,I=r?r.attemptsLeft:e.attemptsLeft,g=rt(A,i),k=l=>_nullishCoalesce(i[l.id], () => (ot(l.config)));async function M(){y(!0),d(void 0);try{let l={};for(let m of A)l[m.id]=k(m);let R=await o.submitTest(e.id,l,Date.now()-p.current);c(R),t(R)}catch(l){d(l)}finally{y(!1)}}let E=new Map(_optionalChain([r, 'optionalAccess', _45 => _45.results, 'access', _46 => _46.map, 'call', _47 => _47(l=>[l.questionId,l])]));return A.length===0?_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:n.testNoQuestions}):_jsxruntime.jsxs.call(void 0, "form",{className:"space-y-6",onSubmit:l=>{l.preventDefault(),r==null&&!C&&M()},children:[r&&_jsxruntime.jsxs.call(void 0, "div",{role:"status",className:_chunkERL4Z7M7js.d.call(void 0, "rounded-xl p-4 text-sm",r.submission.passed?"bg-emerald-50 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300":"bg-red-50 text-red-800 dark:bg-red-950 dark:text-red-300"),children:[_jsxruntime.jsx.call(void 0, "p",{className:"text-base font-semibold",children:r.submission.passed?n.testPassed:n.testFailed}),_jsxruntime.jsx.call(void 0, "p",{children:w(n.testScore,{percent:r.submission.scorePercent})})]}),_jsxruntime.jsx.call(void 0, "ol",{className:"space-y-6",children:A.map((l,R)=>{let m=E.get(l.id),T=_optionalChain([m, 'optionalAccess', _48 => _48.correctAnswer])!=null?it(l,m.correctAnswer):"";return _jsxruntime.jsxs.call(void 0, "li",{className:"space-y-3","data-question-id":l.id,children:[_jsxruntime.jsxs.call(void 0, "p",{className:"font-medium break-words",children:[R+1,". ",l.prompt]}),_jsxruntime.jsx.call(void 0, Ts,{question:l,answer:k(l),disabled:r!=null||b||C,onChange:B=>a(q=>({...q,[l.id]:B}))}),m&&_jsxruntime.jsxs.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, "flex items-start gap-2 text-sm",m.correct?"text-emerald-700 dark:text-emerald-400":"text-red-700 dark:text-red-400"),children:[m.correct?_jsxruntime.jsx.call(void 0, _lucidereact.Check,{className:"mt-0.5 size-4 shrink-0","aria-hidden":!0}):_jsxruntime.jsx.call(void 0, _lucidereact.X,{className:"mt-0.5 size-4 shrink-0","aria-hidden":!0}),_jsxruntime.jsxs.call(void 0, "span",{children:[m.correct?n.testCorrect:n.testIncorrect,T!==""&&!m.correct&&_jsxruntime.jsxs.call(void 0, "span",{className:"block text-neutral-700 dark:text-neutral-300",children:[n.testCorrectAnswer,": ",T]})]})]})]},l.id)})}),f!=null&&_jsxruntime.jsx.call(void 0, "p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:J(f,n)}),r==null?_jsxruntime.jsxs.call(void 0, "div",{className:"space-y-2",children:[g>0&&!C&&_jsxruntime.jsx.call(void 0, "p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:w(n.testUnanswered,{count:g})}),_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{type:"submit",className:$,disabled:b||C,children:b?n.testSubmitting:n.testSubmit})]}):I!==0&&!r.submission.passed&&_jsxruntime.jsx.call(void 0, _chunk6IACFTJRjs.q,{type:"button",variant:"outline",onClick:s,children:n.testTryAgain})]})}function Gt({testId:e,title:t,onSubmitted:s}){let{dataSource:o,texts:n}=h(),i=Q(),a=j(()=>o.getTest(e),[o,e]),r=j(()=>o.listSubmissions(e).catch(()=>{}),[o,e]),[c,b]=_react.useState.call(void 0, 0);return _jsxruntime.jsxs.call(void 0, "article",{"data-test-id":e,className:_chunkERL4Z7M7js.d.call(void 0, z,"min-w-0 space-y-5"),children:[_jsxruntime.jsx.call(void 0, "h2",{className:"text-2xl font-semibold break-words",children:a.status==="success"?a.data.title:t}),a.status==="loading"&&_jsxruntime.jsx.call(void 0, ue,{}),a.status==="error"&&_jsxruntime.jsx.call(void 0, ee,{error:a.error,onRetry:a.reload}),a.status==="success"&&_jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment,{children:[a.data.description.trim()!==""&&_jsxruntime.jsx.call(void 0, "p",{className:"text-sm break-words whitespace-pre-wrap text-neutral-700 dark:text-neutral-300",children:a.data.description}),_jsxruntime.jsxs.call(void 0, "ul",{className:"space-y-1 text-sm text-neutral-600 dark:text-neutral-400",children:[a.data.gating&&_jsxruntime.jsx.call(void 0, "li",{children:n.testGating}),_jsxruntime.jsx.call(void 0, "li",{children:w(n.testPassThreshold,{percent:a.data.passThresholdPercent})}),_jsxruntime.jsx.call(void 0, "li",{children:a.data.attemptsLeft==null?n.testAttemptsUnlimited:w(n.testAttemptsLeft,{count:a.data.attemptsLeft})}),a.data.attemptCount>0&&_jsxruntime.jsx.call(void 0, "li",{children:w(n.testBestScore,{percent:a.data.bestScorePercent})})]}),a.data.attemptsLeft!=null&&a.data.attemptsLeft<=0&&_jsxruntime.jsx.call(void 0, "p",{role:"alert",className:"text-sm font-medium text-red-700 dark:text-red-400",children:n.testNoAttemptsLeft}),_jsxruntime.jsx.call(void 0, Es,{test:a.data,onSubmitted:y=>{i("test_submitted",{lesson_id:e,score_bucket:_chunkDF2IK2GNjs.r.call(void 0, y.submission.scorePercent)}),s(y),r.refresh(),a.refresh()},onTryAgain:()=>b(y=>y+1)},c),r.status==="success"&&r.data&&_jsxruntime.jsx.call(void 0, ws,{list:r.data})]})]})}var Ut="bitsnap-course-autoplay";function Zs(){let[e,t]=_react.useState.call(void 0, !1);_react.useEffect.call(void 0, ()=>{try{t(window.localStorage.getItem(Ut)==="1")}catch (e2){}},[]);let s=_react.useCallback.call(void 0, o=>{t(o);try{window.localStorage.setItem(Ut,o?"1":"0")}catch (e3){}},[]);return[e,s]}function Lt(e){return e==null?void 0:{id:e.id,kind:e.kind}}function Xs({item:e}){let{texts:t}=h();return _jsxruntime.jsxs.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, z,"space-y-2"),role:"status",children:[_jsxruntime.jsx.call(void 0, "h2",{className:"text-2xl font-semibold break-words",children:e.title}),_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-600 dark:text-neutral-300",children:e.lockReason?w(t.lockedPage,{test:e.lockReason.gatingTestTitle}):t.locked})]})}function Ms({course:e,item:t,autoplay:s,onAutoplayChange:o,startPlaying:n,onNavigate:i,onProgressSaved:a,onEnded:r,onLocked:c}){let{dataSource:b,texts:y}=h(),f=j(()=>b.getLesson(t.id),[b,t.id]),d=f.status==="error"&&f.error instanceof Z&&f.error.code==="item_locked";if(_react.useEffect.call(void 0, ()=>{d&&c()},[d]),f.status==="loading")return _jsxruntime.jsx.call(void 0, ue,{});if(f.status==="error")return d?_jsxruntime.jsxs.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, z,"space-y-2"),role:"alert",children:[_jsxruntime.jsx.call(void 0, "h2",{className:"text-2xl font-semibold break-words",children:t.title}),_jsxruntime.jsx.call(void 0, "p",{className:"text-sm",children:J(f.error,y)})]}):_jsxruntime.jsx.call(void 0, ee,{error:f.error,onRetry:f.reload});let p=ye(e,t.id),A=ce(e).filter(C=>C.kind!=="test").findIndex(C=>C.id===t.id)+1;return _jsxruntime.jsx.call(void 0, Vt,{lesson:f.data,position:A,courseType:e.type,prev:_nullishCoalesce(f.data.prev, () => (Lt(p.prev))),next:_nullishCoalesce(f.data.next, () => (Lt(p.next))),autoplay:s,onAutoplayChange:o,startPlaying:n,onNavigate:i,onProgressSaved:a,onEnded:r,onUrlsExpired:f.refresh},f.data.id)}function zs({course:e,selectedId:t,onSelect:s,onBack:o,onCourseChange:n,onCourseRefresh:i}){let{texts:a,preview:r}=h(),c=Q(),b=_react.useRef.call(void 0, null),[y,f]=_react.useState.call(void 0, !1),[d,p]=Zs(),[A,C]=_react.useState.call(void 0, ),I=et(e,t),g=le(e,I);_react.useEffect.call(void 0, ()=>{c("course_opened",{})},[e.id]);let k=(m,T=!1)=>{C(T?m:void 0),f(!1),s(m),_optionalChain([b, 'access', _49 => _49.current, 'optionalAccess', _50 => _50.scrollIntoView, 'call', _51 => _51({block:"start",behavior:"smooth"})])},M=_jsxruntime.jsx.call(void 0, ct,{course:e,selectedId:I,variant:e.type==="video"?"playlist":"modules",onSelect:m=>k(m.id)}),E;g==null?E=_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-500",children:a.noItem}):g.locked?E=_jsxruntime.jsx.call(void 0, Xs,{item:g}):g.kind==="test"?E=_jsxruntime.jsx.call(void 0, Gt,{testId:g.id,title:g.title,onSubmitted:()=>i()},g.id):E=_jsxruntime.jsx.call(void 0, Ms,{course:e,item:g,autoplay:d,onAutoplayChange:p,startPlaying:A===g.id,onNavigate:m=>k(m.id),onProgressSaved:(m,T)=>n(B=>({...T.completed?$e(B,m):B,completedLessons:T.completedLessons,progressPercent:T.progressPercent})),onEnded:()=>{if(!d)return;let m=Oe(e,g.id);m&&k(m.id,!0)},onLocked:i},g.id);let l=e.type==="video",R=e.resumeItemId!==""&&e.resumeItemId!==I&&_optionalChain([le, 'call', _52 => _52(e,e.resumeItemId), 'optionalAccess', _53 => _53.locked])===!1;return _jsxruntime.jsxs.call(void 0, "div",{ref:b,className:"space-y-4 scroll-mt-4",children:[_jsxruntime.jsxs.call(void 0, "header",{className:"space-y-3",children:[_jsxruntime.jsxs.call(void 0, "div",{className:"flex flex-wrap items-center gap-2",children:[o&&_jsxruntime.jsxs.call(void 0, _chunk6IACFTJRjs.q,{variant:"ghost",size:"sm",onClick:o,children:[_jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft,{"aria-hidden":!0}),a.back]}),r&&_jsxruntime.jsx.call(void 0, "span",{"data-course-preview":!0,className:"rounded-full bg-amber-100 px-3 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900/40 dark:text-amber-200",children:a.previewBadge})]}),_jsxruntime.jsxs.call(void 0, "div",{className:"flex flex-wrap items-end justify-between gap-4",children:[_jsxruntime.jsx.call(void 0, "div",{className:"min-w-0 space-y-1",children:_jsxruntime.jsx.call(void 0, "h1",{className:"text-2xl font-semibold break-words sm:text-3xl",children:e.title})}),R&&_jsxruntime.jsxs.call(void 0, _chunk6IACFTJRjs.q,{size:"sm",variant:"outline",onClick:()=>k(e.resumeItemId),children:[_jsxruntime.jsx.call(void 0, _lucidereact.Play,{"aria-hidden":!0}),a.continueCourse]})]}),_jsxruntime.jsxs.call(void 0, "div",{className:"max-w-xl space-y-1",children:[_jsxruntime.jsx.call(void 0, At,{percent:e.progressPercent,label:w(a.progressPercent,{percent:e.progressPercent})}),_jsxruntime.jsxs.call(void 0, "p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:[w(a.progressLessons,{done:e.completedLessons,total:e.totalLessons})," \xB7 ",w(a.progressPercent,{percent:e.progressPercent})]})]})]}),_jsxruntime.jsxs.call(void 0, "div",{className:_chunkERL4Z7M7js.d.call(void 0, "grid gap-6",l?"lg:grid-cols-[minmax(0,1fr)_380px]":"lg:grid-cols-[320px_minmax(0,1fr)]"),children:[!l&&_jsxruntime.jsx.call(void 0, "div",{className:"lg:hidden",children:_jsxruntime.jsxs.call(void 0, _chunk6IACFTJRjs.q,{variant:"outline",size:"sm","aria-expanded":y,onClick:()=>f(m=>!m),children:[_jsxruntime.jsx.call(void 0, _lucidereact.List,{"aria-hidden":!0}),a.contents]})}),!l&&_jsxruntime.jsx.call(void 0, "aside",{className:_chunkERL4Z7M7js.d.call(void 0, z,"p-3 sm:p-3 lg:sticky lg:top-4 lg:block lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto",y?"block":"hidden"),children:M}),_jsxruntime.jsx.call(void 0, "div",{className:"min-w-0","data-course-content":!0,children:E}),l&&_jsxruntime.jsx.call(void 0, "aside",{className:_chunkERL4Z7M7js.d.call(void 0, z,"p-3 sm:p-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto"),children:M})]})]})}function Qs({accessId:e,accessToken:t,dataSource:s,preview:o=!1,itemId:n,onItemChange:i,onBack:a,initialCourse:r,texts:c,locale:b,theme:y,className:f,analytics:d}){let p=_chunkDGUIG334js.j.call(void 0, ),A=_chunkDF2IK2GNjs.n.call(void 0, p.projectID,o?!1:d,"course"),C=_react.useMemo.call(void 0, ()=>Te(c,b),[c,b]),I=_react.useMemo.call(void 0, ()=>{if(s)return s;if(e==null||e==="")throw new Error("PanelCourse needs an accessId or a dataSource");return Ce({accessId:e,accessToken:t})},[s,e,t]),g=j(()=>I.getCourse(),[I],r),[k,M]=_react.useState.call(void 0, ),E=i?n:_nullishCoalesce(n, () => (k)),l=_react.useCallback.call(void 0, B=>{M(B),_optionalChain([i, 'optionalCall', _54 => _54(B)])},[i]),R=De(_nullishCoalesce(_optionalChain([y, 'optionalAccess', _55 => _55.colors]), () => (_optionalChain([p, 'access', _56 => _56.theme, 'optionalAccess', _57 => _57.colors])))),m=g.status==="success"?g.data.id:void 0,T=_react.useMemo.call(void 0, ()=>({dataSource:I,texts:C,preview:o,analytics:{courseId:m,instrument:A}}),[I,C,o,m,A]);return _jsxruntime.jsx.call(void 0, _chunkDF2IK2GNjs.l,{value:A,children:_jsxruntime.jsx.call(void 0, qe,{value:T,children:_jsxruntime.jsxs.call(void 0, "div",{"data-bitsnap-course":!0,"data-course-type":g.status==="success"?g.data.type:void 0,style:R,className:_chunkERL4Z7M7js.d.call(void 0, "font-inter w-full max-w-7xl",f),children:[g.status==="loading"&&_jsxruntime.jsxs.call(void 0, "div",{className:"space-y-4",children:[a&&_jsxruntime.jsxs.call(void 0, _chunk6IACFTJRjs.q,{variant:"ghost",size:"sm",onClick:a,children:[_jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft,{"aria-hidden":!0}),C.back]}),_jsxruntime.jsx.call(void 0, "p",{className:"sr-only",role:"status",children:C.loading}),_jsxruntime.jsx.call(void 0, ue,{})]}),g.status==="error"&&_jsxruntime.jsxs.call(void 0, "div",{className:"space-y-4",children:[a&&_jsxruntime.jsxs.call(void 0, _chunk6IACFTJRjs.q,{variant:"ghost",size:"sm",onClick:a,children:[_jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft,{"aria-hidden":!0}),C.back]}),_jsxruntime.jsx.call(void 0, ee,{error:g.error,onRetry:g.reload})]}),g.status==="success"&&_jsxruntime.jsx.call(void 0, zs,{course:g.data,selectedId:E,onSelect:l,onBack:a,onCourseChange:g.mutate,onCourseRefresh:g.refresh})]})})})}exports.COURSE_TEXT_TABLES = It; exports.CourseError = Z; exports.PanelCourse = Qs; exports.createCourseDataSource = ve; exports.createRpcCourseDataSource = Ce; exports.getCourseTexts = Te; exports.sanitizeLessonHtml = Pe;
1
+ "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }var _chunkEWLOGFF2js = require('./chunk-EWLOGFF2.js');var _chunkUFQVOGBSjs = require('./chunk-UFQVOGBS.js');var _chunkM4KEUK6Ijs = require('./chunk-M4KEUK6I.js');var _chunkTSJCVFOXjs = require('./chunk-TSJCVFOX.js');require('./chunk-XIHGWNTS.js');require('./chunk-JSDGZ4NY.js');var _lucidereact = require('lucide-react');var _react = require('react');function De(e){let t={},s=_optionalChain([e, 'optionalAccess', _2 => _2.brand, 'optionalAccess', _3 => _3.trim, 'call', _4 => _4()]),o=_optionalChain([e, 'optionalAccess', _5 => _5.brandDark, 'optionalAccess', _6 => _6.trim, 'call', _7 => _7()])||s;if(s){t["--course-accent"]=s;let n=_chunkUFQVOGBSjs.s.call(void 0, s);n&&(t["--course-accent-fg"]=n)}if(o){t["--course-accent-dark"]=o;let n=_chunkUFQVOGBSjs.s.call(void 0, o);n&&(t["--course-accent-dark-fg"]=n)}return t}var He=_react.createContext.call(void 0, void 0),qe=He.Provider;function h(){let e=_react.useContext.call(void 0, He);if(e==null)throw new Error("Course components have to be rendered inside PanelCourse");return e}function Q(){let{analytics:e,preview:t}=h();return _react.useCallback.call(void 0, (s,o)=>{t||_optionalChain([e, 'optionalAccess', _8 => _8.courseId])==null||e.instrument.track(s,{...o,course_id:e.courseId})},[e,t])}function ce(e){return[...e.sections.flatMap(t=>t.items.map(s=>({...s,sectionId:t.id}))),...e.finalItems.map(t=>({...t,sectionId:""}))]}function le(e,t){if(!(t==null||t===""))return ce(e).find(s=>s.id===t)}function ye(e,t){let s=ce(e),o=s.findIndex(n=>n.id===t);return o<0?{}:{prev:s[o-1],next:s[o+1]}}function Oe(e,t){let{next:s}=ye(e,t);return s!=null&&s.kind==="lesson"&&!s.locked?s:void 0}function zt(e){let t=0,s=0;for(let o of e.sections)for(let n of o.items)n.kind==="lesson"&&(t+=1,n.completed&&(s+=1));return{total:t,completed:s,percent:t===0?0:Math.round(s/t*100)}}function $e(e,t){let s=e.sections.map(n=>({...n,items:n.items.map(i=>i.id===t&&i.kind==="lesson"?{...i,completed:!0}:i)})),o=zt({sections:s});return{...e,sections:s,totalLessons:o.total,completedLessons:o.completed,progressPercent:o.percent}}function et(e,t){return le(e,t)!=null?t:_optionalChain([le, 'call', _9 => _9(e,e.resumeItemId), 'optionalAccess', _10 => _10.locked])===!1?e.resumeItemId:_optionalChain([ce, 'call', _11 => _11(e), 'access', _12 => _12.find, 'call', _13 => _13(s=>!s.locked), 'optionalAccess', _14 => _14.id])}function w(e,t){return e.replace(/\{(\w+)\}/g,(s,o)=>o in t?String(t[o]):s)}function tt(e,t){return t>0&&e/t>=.8}function st(e,t){return e<5||t>0&&e>=t-5?0:Math.floor(e)}function Se(e){let t=Math.max(0,Math.floor(e)),s=Math.floor(t/3600),o=Math.floor(t%3600/60),n=String(t%60).padStart(2,"0");return s>0?`${s}:${String(o).padStart(2,"0")}:${n}`:`${o}:${n}`}function nt(e){if(!(e>0))return"0 B";let t=["B","KB","MB","GB"],s=Math.min(Math.floor(Math.log(e)/Math.log(1024)),t.length-1),o=e/1024**s;return`${s===0||o>=10?Math.round(o):o.toFixed(1)} ${t[s]}`}function ot(e){switch(e.type){case"mcq":return{type:"mcq",selectedIds:[]};case"todolist":return{type:"todolist",checkedIds:[]};case"fill_gap":return{type:"fill_gap",values:{}};case"sort_order":return{type:"sort_order",orderedIds:e.items.map(t=>t.id)}}}function Qt(e,t){if(t==null||t.type!==e.config.type)return!1;switch(t.type){case"mcq":return t.selectedIds.length>0;case"todolist":return t.checkedIds.length>0;case"fill_gap":return Object.values(t.values).some(s=>s.trim()!=="");case"sort_order":return t.orderedIds.length>0}}function rt(e,t){return e.filter(s=>!Qt(s,t[s.id])).length}function at(e,t,s){if(t===s||t<0||s<0||t>=e.length||s>=e.length)return[...e];let o=[...e],[n]=o.splice(t,1);return o.splice(s,0,n),o}function he(e,t){let s=[],o=/\{\{\s*([^{}\s]+)\s*\}\}/g,n=0;for(let i of e.matchAll(o)){let a=i[1];t.includes(a)&&(i.index>n&&s.push({type:"text",text:e.slice(n,i.index)}),s.push({type:"blank",id:a}),n=i.index+i[0].length)}return n<e.length&&s.push({type:"text",text:e.slice(n)}),s}function it(e,t){let{config:s}=e;if(t==null||t.type!==s.type)return"";let o=(n,i)=>_nullishCoalesce(_optionalChain([n, 'access', _15 => _15.find, 'call', _16 => _16(a=>a.id===i), 'optionalAccess', _17 => _17.label]), () => (i));return s.type==="mcq"&&t.type==="mcq"?t.selectedIds.map(n=>o(s.choices,n)).join(", "):s.type==="todolist"&&t.type==="todolist"?t.checkedIds.map(n=>o(s.steps,n)).join(", "):s.type==="sort_order"&&t.type==="sort_order"?t.orderedIds.map(n=>o(s.items,n)).join(" \u2192 "):s.type==="fill_gap"&&t.type==="fill_gap"?he(s.template,s.blanks).map(n=>n.type==="text"?n.text:`[${_nullishCoalesce(t.values[n.id], () => (""))}]`).join(""):""}var _jsxruntime = require('react/jsx-runtime');function lt({item:e}){return e.locked?_jsxruntime.jsx.call(void 0, _lucidereact.Lock,{className:"size-4 text-neutral-500","aria-hidden":!0}):e.completed?_jsxruntime.jsx.call(void 0, _lucidereact.CheckCircle2,{className:"size-4 text-emerald-600 dark:text-emerald-400","aria-hidden":!0}):e.kind==="test"?_jsxruntime.jsx.call(void 0, _lucidereact.ClipboardCheck,{className:"size-4 text-neutral-500","aria-hidden":!0}):e.durationSeconds>0?_jsxruntime.jsx.call(void 0, _lucidereact.PlayCircle,{className:"size-4 text-neutral-500","aria-hidden":!0}):_jsxruntime.jsx.call(void 0, _lucidereact.FileText,{className:"size-4 text-neutral-500","aria-hidden":!0})}function Jt({item:e,selected:t,variant:s,onSelect:o}){let{texts:n}=h(),i=e.lockReason?w(n.lockedByTest,{test:e.lockReason.gatingTestTitle}):n.locked,a=e.locked?i:e.completed?n.completed:void 0;return _jsxruntime.jsx.call(void 0, "li",{children:_jsxruntime.jsxs.call(void 0, "button",{type:"button","data-item-id":e.id,"data-locked":e.locked||void 0,"aria-current":t?"true":void 0,"aria-disabled":e.locked||void 0,title:e.locked?i:void 0,onClick:()=>{e.locked||o(e)},className:_chunkM4KEUK6Ijs.d.call(void 0, "flex w-full items-start gap-3 rounded-lg p-2 text-left text-sm outline-none transition focus-visible:ring-[3px] focus-visible:ring-ring/50",t?"bg-neutral-200 dark:bg-neutral-700":"hover:bg-neutral-100 dark:hover:bg-neutral-800",e.locked&&"cursor-not-allowed opacity-60 hover:bg-transparent dark:hover:bg-transparent"),children:[s==="playlist"&&e.kind==="lesson"&&_jsxruntime.jsxs.call(void 0, "span",{className:"relative aspect-video w-24 shrink-0 overflow-hidden rounded bg-neutral-200 dark:bg-neutral-800",children:[e.thumbnailUrl&&_jsxruntime.jsx.call(void 0, "img",{src:e.thumbnailUrl,alt:"",loading:"lazy",draggable:!1,className:"size-full object-cover"}),e.durationSeconds>0&&_jsxruntime.jsx.call(void 0, "span",{className:"absolute right-1 bottom-1 rounded bg-black/75 px-1 text-[10px] text-white",children:Se(e.durationSeconds)})]}),(s==="modules"||e.kind==="test")&&_jsxruntime.jsx.call(void 0, "span",{className:"mt-0.5 shrink-0",children:_jsxruntime.jsx.call(void 0, lt,{item:e})}),_jsxruntime.jsxs.call(void 0, "span",{className:"min-w-0 flex-1",children:[_jsxruntime.jsx.call(void 0, "span",{className:"block font-medium break-words",children:e.title}),_jsxruntime.jsxs.call(void 0, "span",{className:"mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-neutral-500 dark:text-neutral-400",children:[e.kind==="test"&&_jsxruntime.jsx.call(void 0, "span",{children:n.testBadge}),e.isFree&&!e.locked&&_jsxruntime.jsx.call(void 0, "span",{children:n.free}),s==="modules"&&e.durationSeconds>0&&_jsxruntime.jsx.call(void 0, "span",{children:Se(e.durationSeconds)}),a&&_jsxruntime.jsx.call(void 0, "span",{children:a})]})]}),s==="playlist"&&e.kind==="lesson"&&_jsxruntime.jsx.call(void 0, "span",{className:"mt-0.5 shrink-0",children:_jsxruntime.jsx.call(void 0, lt,{item:e})})]})})}function ct({course:e,selectedId:t,onSelect:s,variant:o,className:n}){let{texts:i}=h(),a=r=>_jsxruntime.jsx.call(void 0, "ul",{className:"flex flex-col gap-1",children:r.map(c=>_jsxruntime.jsx.call(void 0, Jt,{item:c,selected:c.id===t,variant:o,onSelect:s},c.id))});return _jsxruntime.jsxs.call(void 0, "nav",{"aria-label":i.contents,className:_chunkM4KEUK6Ijs.d.call(void 0, "space-y-4",n),children:[e.sections.map(r=>{let c=r.items.filter(y=>y.kind==="lesson"),b=c.filter(y=>y.completed).length;return _jsxruntime.jsxs.call(void 0, "section",{"data-section-id":r.id,children:[_jsxruntime.jsxs.call(void 0, "h3",{className:"mb-1 flex items-baseline justify-between gap-2 px-2 text-sm font-semibold",children:[_jsxruntime.jsx.call(void 0, "span",{className:"break-words",children:r.title}),c.length>0&&_jsxruntime.jsxs.call(void 0, "span",{className:"shrink-0 text-xs font-normal text-neutral-500 dark:text-neutral-400",children:[b,"/",c.length]})]}),a(r.items)]},r.id)}),e.finalItems.length>0&&_jsxruntime.jsx.call(void 0, "section",{"data-section-id":"final",children:a(e.finalItems)})]})}var _connect = require('@connectrpc/connect');var _codegenv2 = require('@bufbuild/protobuf/codegenv2');var _wkt = require('@bufbuild/protobuf/wkt');var qt=_codegenv2.fileDesc.call(void 0, "CiFwdWJsaWMvdjEvdXNlcl9wYW5lbF9jb3Vyc2UucHJvdG8SDXB1YmxpY19hcGkudjEiRAoPUGFuZWxMb2NrUmVhc29uEhYKDmdhdGluZ190ZXN0X2lkGAEgASgJEhkKEWdhdGluZ190ZXN0X3RpdGxlGAIgASgJIrQCCg9QYW5lbENvdXJzZUl0ZW0SCgoCaWQYASABKAkSMAoEa2luZBgCIAEoDjIiLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VJdGVtS2luZBINCgV0aXRsZRgDIAEoCRIPCgdpc19mcmVlGAQgASgIEg4KBmxvY2tlZBgFIAEoCBI4Cgtsb2NrX3JlYXNvbhgGIAEoCzIeLnB1YmxpY19hcGkudjEuUGFuZWxMb2NrUmVhc29uSACIAQESEQoJY29tcGxldGVkGAcgASgIEhgKEGR1cmF0aW9uX3NlY29uZHMYCCABKAUSGgoNdGh1bWJuYWlsX3VybBgJIAEoCUgBiAEBEg4KBmdhdGluZxgKIAEoCEIOCgxfbG9ja19yZWFzb25CEAoOX3RodW1ibmFpbF91cmwicwoSUGFuZWxDb3Vyc2VTZWN0aW9uEgoKAmlkGAEgASgJEg0KBXRpdGxlGAIgASgJEhMKC2Rlc2NyaXB0aW9uGAMgASgJEi0KBWl0ZW1zGAQgAygLMh4ucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUl0ZW0i3wIKC1BhbmVsQ291cnNlEgoKAmlkGAEgASgJEiwKBHR5cGUYAiABKA4yHi5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlVHlwZRINCgV0aXRsZRgDIAEoCRITCgtkZXNjcmlwdGlvbhgEIAEoCRIWCgljb3Zlcl91cmwYBSABKAlIAIgBARIVCg10b3RhbF9sZXNzb25zGAYgASgFEhkKEWNvbXBsZXRlZF9sZXNzb25zGAcgASgFEhgKEHByb2dyZXNzX3BlcmNlbnQYCCABKAUSMwoIc2VjdGlvbnMYCSADKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlU2VjdGlvbhIzCgtmaW5hbF9pdGVtcxgKIAMoCzIeLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VJdGVtEhYKDnJlc3VtZV9pdGVtX2lkGAsgASgJQgwKCl9jb3Zlcl91cmwiRAoZVXNlclBhbmVsR2V0Q291cnNlUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJIosBChpVc2VyUGFuZWxHZXRDb3Vyc2VSZXNwb25zZRItCgdzdWNjZXNzGAEgASgLMhoucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAQggKBnJlc3VsdCJfCgpQYW5lbFZpZGVvEg8KB2hsc191cmwYASABKAkSFwoKcG9zdGVyX3VybBgCIAEoCUgAiAEBEhgKEGR1cmF0aW9uX3NlY29uZHMYAyABKAVCDQoLX3Bvc3Rlcl91cmwiRgoPUGFuZWxBdHRhY2htZW50EgoKAmlkGAEgASgJEgwKBG5hbWUYAiABKAkSDAoEc2l6ZRgDIAEoAxILCgN1cmwYBCABKAkiTAoMUGFuZWxJdGVtUmVmEgoKAmlkGAEgASgJEjAKBGtpbmQYAiABKA4yIi5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlSXRlbUtpbmQigQQKC1BhbmVsTGVzc29uEgoKAmlkGAEgASgJEhIKCnNlY3Rpb25faWQYAiABKAkSDQoFdGl0bGUYAyABKAkSEwoLZGVzY3JpcHRpb24YBCABKAkSFAoMY29udGVudF9odG1sGAUgASgJEi0KBXZpZGVvGAYgASgLMhkucHVibGljX2FwaS52MS5QYW5lbFZpZGVvSACIAQESMwoLYXR0YWNobWVudHMYByADKAsyHi5wdWJsaWNfYXBpLnYxLlBhbmVsQXR0YWNobWVudBIuCgRwcmV2GAggASgLMhsucHVibGljX2FwaS52MS5QYW5lbEl0ZW1SZWZIAYgBARIuCgRuZXh0GAkgASgLMhsucHVibGljX2FwaS52MS5QYW5lbEl0ZW1SZWZIAogBARIRCgljb21wbGV0ZWQYCiABKAgSGAoQcG9zaXRpb25fc2Vjb25kcxgLIAEoBRIRCglteV9yYXRpbmcYDCABKAUSFgoOYXZlcmFnZV9yYXRpbmcYDSABKAISFAoMcmF0aW5nX2NvdW50GA4gASgFEjcKDnVybHNfZXhwaXJlX2F0GA8gASgLMhouZ29vZ2xlLnByb3RvYnVmLlRpbWVzdGFtcEgDiAEBQggKBl92aWRlb0IHCgVfcHJldkIHCgVfbmV4dEIRCg9fdXJsc19leHBpcmVfYXQiVwoZVXNlclBhbmVsR2V0TGVzc29uUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEhEKCWxlc3Nvbl9pZBgDIAEoCSKLAQoaVXNlclBhbmVsR2V0TGVzc29uUmVzcG9uc2USLQoHc3VjY2VzcxgBIAEoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25IABI0CgdmYWlsdXJlGAIgASgOMiEucHVibGljX2FwaS52MS5QYW5lbENvdXJzZUZhaWx1cmVIAEIICgZyZXN1bHQipwEKIlVzZXJQYW5lbFNhdmVMZXNzb25Qcm9ncmVzc1JlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIRCglsZXNzb25faWQYAyABKAkSEQoJY29tcGxldGVkGAQgASgIEhgKEHBvc2l0aW9uX3NlY29uZHMYBSABKAUSGAoQZHVyYXRpb25fc2Vjb25kcxgGIAEoBSKFAgojVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2USTAoHc3VjY2VzcxgBIAEoCzI5LnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaUAoGUmVzdWx0EhEKCWNvbXBsZXRlZBgBIAEoCBIZChFjb21wbGV0ZWRfbGVzc29ucxgCIAEoBRIYChBwcm9ncmVzc19wZXJjZW50GAMgASgFQggKBnJlc3VsdCIoCgtQYW5lbENob2ljZRIKCgJpZBgBIAEoCRINCgVsYWJlbBgCIAEoCSJPCg5QYW5lbE1jcUNvbmZpZxIrCgdjaG9pY2VzGAEgAygLMhoucHVibGljX2FwaS52MS5QYW5lbENob2ljZRIQCghtdWx0aXBsZRgCIAEoCCKCAQoTUGFuZWxUb2RvbGlzdENvbmZpZxI2CgVzdGVwcxgBIAMoCzInLnB1YmxpY19hcGkudjEuUGFuZWxUb2RvbGlzdENvbmZpZy5TdGVwGjMKBFN0ZXASCgoCaWQYASABKAkSDQoFbGFiZWwYAiABKAkSEAoIcmVxdWlyZWQYAyABKAgiNgoSUGFuZWxGaWxsR2FwQ29uZmlnEhAKCHRlbXBsYXRlGAEgASgJEg4KBmJsYW5rcxgCIAMoCSJBChRQYW5lbFNvcnRPcmRlckNvbmZpZxIpCgVpdGVtcxgBIAMoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxDaG9pY2UioQIKEVBhbmVsVGVzdFF1ZXN0aW9uEgoKAmlkGAEgASgJEg4KBnByb21wdBgCIAEoCRIOCgZwb2ludHMYAyABKAUSLAoDbWNxGAogASgLMh0ucHVibGljX2FwaS52MS5QYW5lbE1jcUNvbmZpZ0gAEjYKCHRvZG9saXN0GAsgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRvZG9saXN0Q29uZmlnSAASNQoIZmlsbF9nYXAYDCABKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsRmlsbEdhcENvbmZpZ0gAEjkKCnNvcnRfb3JkZXIYDSABKAsyIy5wdWJsaWNfYXBpLnYxLlBhbmVsU29ydE9yZGVyQ29uZmlnSABCCAoGY29uZmlnIqsCCglQYW5lbFRlc3QSCgoCaWQYASABKAkSDQoFdGl0bGUYAiABKAkSEwoLZGVzY3JpcHRpb24YAyABKAkSDgoGZ2F0aW5nGAQgASgIEh4KFnBhc3NfdGhyZXNob2xkX3BlcmNlbnQYBSABKAUSGAoQYXR0ZW1wdHNfYWxsb3dlZBgGIAEoBRIaCg1hdHRlbXB0c19sZWZ0GAcgASgFSACIAQESFQoNYXR0ZW1wdF9jb3VudBgIIAEoBRIaChJiZXN0X3Njb3JlX3BlcmNlbnQYCSABKAUSDgoGcGFzc2VkGAogASgIEjMKCXF1ZXN0aW9ucxgLIAMoCzIgLnB1YmxpY19hcGkudjEuUGFuZWxUZXN0UXVlc3Rpb25CEAoOX2F0dGVtcHRzX2xlZnQiUwoXVXNlclBhbmVsR2V0VGVzdFJlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIPCgd0ZXN0X2lkGAMgASgJIocBChhVc2VyUGFuZWxHZXRUZXN0UmVzcG9uc2USKwoHc3VjY2VzcxgBIAEoCzIYLnB1YmxpY19hcGkudjEuUGFuZWxUZXN0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSABCCAoGcmVzdWx0IiYKDlBhbmVsTWNxQW5zd2VyEhQKDHNlbGVjdGVkX2lkcxgBIAMoCSIqChNQYW5lbFRvZG9saXN0QW5zd2VyEhMKC2NoZWNrZWRfaWRzGAEgAygJIoIBChJQYW5lbEZpbGxHYXBBbnN3ZXISPQoGdmFsdWVzGAEgAygLMi0ucHVibGljX2FwaS52MS5QYW5lbEZpbGxHYXBBbnN3ZXIuVmFsdWVzRW50cnkaLQoLVmFsdWVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASIrChRQYW5lbFNvcnRPcmRlckFuc3dlchITCgtvcmRlcmVkX2lkcxgBIAMoCSKDAgoLUGFuZWxBbnN3ZXISEwoLcXVlc3Rpb25faWQYASABKAkSLAoDbWNxGAogASgLMh0ucHVibGljX2FwaS52MS5QYW5lbE1jcUFuc3dlckgAEjYKCHRvZG9saXN0GAsgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRvZG9saXN0QW5zd2VySAASNQoIZmlsbF9nYXAYDCABKAsyIS5wdWJsaWNfYXBpLnYxLlBhbmVsRmlsbEdhcEFuc3dlckgAEjkKCnNvcnRfb3JkZXIYDSABKAsyIy5wdWJsaWNfYXBpLnYxLlBhbmVsU29ydE9yZGVyQW5zd2VySABCBwoFdmFsdWUijQEKE1BhbmVsVGVzdFN1Ym1pc3Npb24SCgoCaWQYASABKAkSFQoNc2NvcmVfcGVyY2VudBgCIAEoBRIOCgZwYXNzZWQYAyABKAgSEwoLZHVyYXRpb25fbXMYBCABKAMSLgoKY3JlYXRlZF9hdBgFIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAilwEKE1BhbmVsUXVlc3Rpb25SZXN1bHQSEwoLcXVlc3Rpb25faWQYASABKAkSDwoHY29ycmVjdBgCIAEoCBIOCgZwb2ludHMYAyABKAUSNwoOY29ycmVjdF9hbnN3ZXIYBCABKAsyGi5wdWJsaWNfYXBpLnYxLlBhbmVsQW5zd2VySACIAQFCEQoPX2NvcnJlY3RfYW5zd2VyIpgBChpVc2VyUGFuZWxTdWJtaXRUZXN0UmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEg8KB3Rlc3RfaWQYAyABKAkSKwoHYW5zd2VycxgEIAMoCzIaLnB1YmxpY19hcGkudjEuUGFuZWxBbnN3ZXISEwoLZHVyYXRpb25fbXMYBSABKAMiyQIKG1VzZXJQYW5lbFN1Ym1pdFRlc3RSZXNwb25zZRJECgdzdWNjZXNzGAEgASgLMjEucHVibGljX2FwaS52MS5Vc2VyUGFuZWxTdWJtaXRUZXN0UmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaowEKBlJlc3VsdBI2CgpzdWJtaXNzaW9uGAEgASgLMiIucHVibGljX2FwaS52MS5QYW5lbFRlc3RTdWJtaXNzaW9uEjMKB3Jlc3VsdHMYAiADKAsyIi5wdWJsaWNfYXBpLnYxLlBhbmVsUXVlc3Rpb25SZXN1bHQSGgoNYXR0ZW1wdHNfbGVmdBgDIAEoBUgAiAEBQhAKDl9hdHRlbXB0c19sZWZ0QggKBnJlc3VsdCJfCiNVc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEg8KB3Rlc3RfaWQYAyABKAkiqwIKJFVzZXJQYW5lbExpc3RUZXN0U3VibWlzc2lvbnNSZXNwb25zZRJNCgdzdWNjZXNzGAEgASgLMjoucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAadAoGUmVzdWx0EjcKC3N1Ym1pc3Npb25zGAEgAygLMiIucHVibGljX2FwaS52MS5QYW5lbFRlc3RTdWJtaXNzaW9uEhoKEmJlc3Rfc2NvcmVfcGVyY2VudBgCIAEoBRIVCg1hdHRlbXB0X2NvdW50GAMgASgFQggKBnJlc3VsdCJWChNQYW5lbFF1ZXN0aW9uQW5zd2VyEg8KB2NvbnRlbnQYASABKAkSLgoKY3JlYXRlZF9hdBgCIAEoCzIaLmdvb2dsZS5wcm90b2J1Zi5UaW1lc3RhbXAizAEKE1BhbmVsTGVzc29uUXVlc3Rpb24SCgoCaWQYASABKAkSDwoHY29udGVudBgCIAEoCRITCgthdXRob3JfbmFtZRgDIAEoCRIPCgdpc19taW5lGAQgASgIEi4KCmNyZWF0ZWRfYXQYBSABKAsyGi5nb29nbGUucHJvdG9idWYuVGltZXN0YW1wEjcKBmFuc3dlchgGIAEoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxRdWVzdGlvbkFuc3dlckgAiAEBQgkKB19hbnN3ZXIiYQojVXNlclBhbmVsTGlzdExlc3NvblF1ZXN0aW9uc1JlcXVlc3QSFAoMYWNjZXNzX3Rva2VuGAEgASgJEhEKCWFjY2Vzc19pZBgCIAEoCRIRCglsZXNzb25faWQYAyABKAki9gEKJFVzZXJQYW5lbExpc3RMZXNzb25RdWVzdGlvbnNSZXNwb25zZRJNCgdzdWNjZXNzGAEgASgLMjoucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0TGVzc29uUXVlc3Rpb25zUmVzcG9uc2UuUmVzdWx0SAASNAoHZmFpbHVyZRgCIAEoDjIhLnB1YmxpY19hcGkudjEuUGFuZWxDb3Vyc2VGYWlsdXJlSAAaPwoGUmVzdWx0EjUKCXF1ZXN0aW9ucxgBIAMoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25RdWVzdGlvbkIICgZyZXN1bHQicAohVXNlclBhbmVsQXNrTGVzc29uUXVlc3Rpb25SZXF1ZXN0EhQKDGFjY2Vzc190b2tlbhgBIAEoCRIRCglhY2Nlc3NfaWQYAiABKAkSEQoJbGVzc29uX2lkGAMgASgJEg8KB2NvbnRlbnQYBCABKAkimwEKIlVzZXJQYW5lbEFza0xlc3NvblF1ZXN0aW9uUmVzcG9uc2USNQoHc3VjY2VzcxgBIAEoCzIiLnB1YmxpY19hcGkudjEuUGFuZWxMZXNzb25RdWVzdGlvbkgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAQggKBnJlc3VsdCJ4ChpVc2VyUGFuZWxSYXRlTGVzc29uUmVxdWVzdBIUCgxhY2Nlc3NfdG9rZW4YASABKAkSEQoJYWNjZXNzX2lkGAIgASgJEhEKCWxlc3Nvbl9pZBgDIAEoCRINCgVzY29yZRgEIAEoBRIPCgdjb21tZW50GAUgASgJIu4BChtVc2VyUGFuZWxSYXRlTGVzc29uUmVzcG9uc2USRAoHc3VjY2VzcxgBIAEoCzIxLnB1YmxpY19hcGkudjEuVXNlclBhbmVsUmF0ZUxlc3NvblJlc3BvbnNlLlJlc3VsdEgAEjQKB2ZhaWx1cmUYAiABKA4yIS5wdWJsaWNfYXBpLnYxLlBhbmVsQ291cnNlRmFpbHVyZUgAGkkKBlJlc3VsdBIRCglteV9yYXRpbmcYASABKAUSFgoOYXZlcmFnZV9yYXRpbmcYAiABKAISFAoMcmF0aW5nX2NvdW50GAMgASgFQggKBnJlc3VsdCr6AgoSUGFuZWxDb3Vyc2VGYWlsdXJlEiAKHFBBTkVMX0NPVVJTRV9GQUlMVVJFX1VOS05PV04QABIlCiFQQU5FTF9DT1VSU0VfRkFJTFVSRV9VTkFVVEhPUklaRUQQARIpCiVQQU5FTF9DT1VSU0VfRkFJTFVSRV9DT1VSU0VfTk9UX0ZPVU5EEAISJwojUEFORUxfQ09VUlNFX0ZBSUxVUkVfSVRFTV9OT1RfRk9VTkQQAxIkCiBQQU5FTF9DT1VSU0VfRkFJTFVSRV9JVEVNX0xPQ0tFRBAEEikKJVBBTkVMX0NPVVJTRV9GQUlMVVJFX05PX0FUVEVNUFRTX0xFRlQQBRIpCiVQQU5FTF9DT1VSU0VfRkFJTFVSRV9JTlZBTElEX0FSR1VNRU5UEAYSJQohUEFORUxfQ09VUlNFX0ZBSUxVUkVfUkFURV9MSU1JVEVEEAcSJAogUEFORUxfQ09VUlNFX0ZBSUxVUkVfVU5BVkFJTEFCTEUQCCptCg9QYW5lbENvdXJzZVR5cGUSHQoZUEFORUxfQ09VUlNFX1RZUEVfVU5LTk9XThAAEh4KGlBBTkVMX0NPVVJTRV9UWVBFX1NUQU5EQVJEEAESGwoXUEFORUxfQ09VUlNFX1RZUEVfVklERU8QAip9ChNQYW5lbENvdXJzZUl0ZW1LaW5kEiIKHlBBTkVMX0NPVVJTRV9JVEVNX0tJTkRfVU5LTk9XThAAEiEKHVBBTkVMX0NPVVJTRV9JVEVNX0tJTkRfTEVTU09OEAESHwobUEFORUxfQ09VUlNFX0lURU1fS0lORF9URVNUEAIy+QcKFlVzZXJQYW5lbENvdXJzZVNlcnZpY2USYAoJR2V0Q291cnNlEigucHVibGljX2FwaS52MS5Vc2VyUGFuZWxHZXRDb3Vyc2VSZXF1ZXN0GikucHVibGljX2FwaS52MS5Vc2VyUGFuZWxHZXRDb3Vyc2VSZXNwb25zZRJgCglHZXRMZXNzb24SKC5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldExlc3NvblJlcXVlc3QaKS5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldExlc3NvblJlc3BvbnNlEnsKElNhdmVMZXNzb25Qcm9ncmVzcxIxLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVxdWVzdBoyLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU2F2ZUxlc3NvblByb2dyZXNzUmVzcG9uc2USWgoHR2V0VGVzdBImLnB1YmxpY19hcGkudjEuVXNlclBhbmVsR2V0VGVzdFJlcXVlc3QaJy5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbEdldFRlc3RSZXNwb25zZRJjCgpTdWJtaXRUZXN0EikucHVibGljX2FwaS52MS5Vc2VyUGFuZWxTdWJtaXRUZXN0UmVxdWVzdBoqLnB1YmxpY19hcGkudjEuVXNlclBhbmVsU3VibWl0VGVzdFJlc3BvbnNlEn4KE0xpc3RUZXN0U3VibWlzc2lvbnMSMi5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbExpc3RUZXN0U3VibWlzc2lvbnNSZXF1ZXN0GjMucHVibGljX2FwaS52MS5Vc2VyUGFuZWxMaXN0VGVzdFN1Ym1pc3Npb25zUmVzcG9uc2USfgoTTGlzdExlc3NvblF1ZXN0aW9ucxIyLnB1YmxpY19hcGkudjEuVXNlclBhbmVsTGlzdExlc3NvblF1ZXN0aW9uc1JlcXVlc3QaMy5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbExpc3RMZXNzb25RdWVzdGlvbnNSZXNwb25zZRJ4ChFBc2tMZXNzb25RdWVzdGlvbhIwLnB1YmxpY19hcGkudjEuVXNlclBhbmVsQXNrTGVzc29uUXVlc3Rpb25SZXF1ZXN0GjEucHVibGljX2FwaS52MS5Vc2VyUGFuZWxBc2tMZXNzb25RdWVzdGlvblJlc3BvbnNlEmMKClJhdGVMZXNzb24SKS5wdWJsaWNfYXBpLnYxLlVzZXJQYW5lbFJhdGVMZXNzb25SZXF1ZXN0GioucHVibGljX2FwaS52MS5Vc2VyUGFuZWxSYXRlTGVzc29uUmVzcG9uc2ViBnByb3RvMw",[_wkt.file_google_protobuf_timestamp]);var ut=_codegenv2.serviceDesc.call(void 0, qt,0);var Z=class extends Error{constructor(t,s){super(_nullishCoalesce(s, () => (t))),this.name="CourseError",this.code=t}};var $t={0:"unknown",1:"unauthorized",2:"course_not_found",3:"item_not_found",4:"item_locked",5:"no_attempts_left",6:"invalid_argument",7:"rate_limited",8:"unavailable"};function es(e){return _nullishCoalesce($t[e], () => ("unknown"))}function ts(e){if(e instanceof Z)return e;let t=_connect.ConnectError.from(e);switch(t.code){case _connect.Code.Unauthenticated:case _connect.Code.PermissionDenied:return new Z("unauthorized",t.rawMessage);case _connect.Code.NotFound:return new Z("course_not_found",t.rawMessage);case _connect.Code.ResourceExhausted:return new Z("rate_limited",t.rawMessage);case _connect.Code.Unavailable:case _connect.Code.DeadlineExceeded:return new Z("unavailable",t.rawMessage);default:return e}}function F(e){let{result:t}=e;if(t.case==="success")return t.value;throw t.case==="failure"?new Z(es(t.value)):new Z("unknown","empty response")}function dt(e){return{id:e.id,kind:e.kind===2?"test":"lesson",title:e.title,isFree:e.isFree,locked:e.locked,lockReason:e.locked&&e.lockReason!=null?{gatingTestId:e.lockReason.gatingTestId,gatingTestTitle:e.lockReason.gatingTestTitle}:void 0,completed:e.completed,durationSeconds:e.durationSeconds,thumbnailUrl:e.thumbnailUrl||void 0,gating:e.gating}}function ss(e){return{id:e.id,type:e.type===2?"video":"standard",title:e.title,description:e.description,coverUrl:e.coverUrl||void 0,totalLessons:e.totalLessons,completedLessons:e.completedLessons,progressPercent:e.progressPercent,sections:e.sections.map(t=>({id:t.id,title:t.title,description:t.description,items:t.items.map(dt)})),finalItems:e.finalItems.map(dt),resumeItemId:e.resumeItemId}}function pt(e){return e==null?void 0:{id:e.id,kind:e.kind===2?"test":"lesson"}}function ns(e){return{id:e.id,sectionId:e.sectionId,title:e.title,description:e.description,contentHtml:e.contentHtml,video:e.video!=null&&e.video.hlsUrl!==""?{hlsUrl:e.video.hlsUrl,posterUrl:e.video.posterUrl||void 0,durationSeconds:e.video.durationSeconds}:void 0,attachments:e.attachments.map(t=>({id:t.id,name:t.name,size:Number(t.size),url:t.url})),prev:pt(e.prev),next:pt(e.next),completed:e.completed,positionSeconds:e.positionSeconds,myRating:e.myRating,averageRating:e.averageRating,ratingCount:e.ratingCount,urlsExpireAt:e.urlsExpireAt!=null?_wkt.timestampDate.call(void 0, e.urlsExpireAt):void 0}}function os(e){let t={id:e.id,prompt:e.prompt,points:e.points},{config:s}=e;switch(s.case){case"mcq":return{...t,config:{type:"mcq",choices:s.value.choices.map(o=>({id:o.id,label:o.label})),multiple:s.value.multiple}};case"todolist":return{...t,config:{type:"todolist",steps:s.value.steps.map(o=>({id:o.id,label:o.label,required:o.required}))}};case"fillGap":return{...t,config:{type:"fill_gap",template:s.value.template,blanks:[...s.value.blanks]}};case"sortOrder":return{...t,config:{type:"sort_order",items:s.value.items.map(o=>({id:o.id,label:o.label}))}};default:return}}function rs(e){return{id:e.id,title:e.title,description:e.description,gating:e.gating,passThresholdPercent:e.passThresholdPercent,attemptsAllowed:e.attemptsAllowed,attemptsLeft:e.attemptsLeft,attemptCount:e.attemptCount,bestScorePercent:e.bestScorePercent,passed:e.passed,questions:e.questions.map(os).filter(t=>t!=null)}}function mt(e){return{id:e.id,scorePercent:e.scorePercent,passed:e.passed,durationMs:Number(e.durationMs),createdAt:e.createdAt!=null?_wkt.timestampDate.call(void 0, e.createdAt):void 0}}function as(e){let{value:t}=e;switch(t.case){case"mcq":return{type:"mcq",selectedIds:[...t.value.selectedIds]};case"todolist":return{type:"todolist",checkedIds:[...t.value.checkedIds]};case"fillGap":return{type:"fill_gap",values:{...t.value.values}};case"sortOrder":return{type:"sort_order",orderedIds:[...t.value.orderedIds]};default:return}}function is(e,t){switch(t.type){case"mcq":return{questionId:e,value:{case:"mcq",value:{selectedIds:t.selectedIds}}};case"todolist":return{questionId:e,value:{case:"todolist",value:{checkedIds:t.checkedIds}}};case"fill_gap":return{questionId:e,value:{case:"fillGap",value:{values:t.values}}};case"sort_order":return{questionId:e,value:{case:"sortOrder",value:{orderedIds:t.orderedIds}}}}}function gt(e){return{id:e.id,content:e.content,authorName:e.authorName,isMine:e.isMine,createdAt:e.createdAt!=null?_wkt.timestampDate.call(void 0, e.createdAt):void 0,answer:e.answer!=null?{content:e.answer.content,createdAt:e.answer.createdAt!=null?_wkt.timestampDate.call(void 0, e.answer.createdAt):void 0}:void 0}}function ve(e,t){let s=()=>({accessId:t.accessId,accessToken:typeof t.accessToken=="function"?_nullishCoalesce(t.accessToken(), () => ("")):t.accessToken});async function o(n){try{return await n()}catch(i){throw ts(i)}}return{getCourse:()=>o(async()=>ss(F(await e.getCourse(s())))),getLesson:n=>o(async()=>ns(F(await e.getLesson({...s(),lessonId:n})))),saveProgress:n=>o(async()=>{let i=F(await e.saveLessonProgress({...s(),...n}));return{completed:i.completed,completedLessons:i.completedLessons,progressPercent:i.progressPercent}}),getTest:n=>o(async()=>rs(F(await e.getTest({...s(),testId:n})))),submitTest:(n,i,a)=>o(async()=>{let r=F(await e.submitTest({...s(),testId:n,durationMs:BigInt(Math.max(0,Math.round(a))),answers:Object.entries(i).map(([c,b])=>is(c,b))}));if(r.submission==null)throw new Z("unknown","missing submission");return{submission:mt(r.submission),results:r.results.map(c=>({questionId:c.questionId,correct:c.correct,points:c.points,correctAnswer:c.correctAnswer!=null?as(c.correctAnswer):void 0})),attemptsLeft:r.attemptsLeft}}),listSubmissions:n=>o(async()=>{let i=F(await e.listTestSubmissions({...s(),testId:n}));return{submissions:i.submissions.map(mt),bestScorePercent:i.bestScorePercent,attemptCount:i.attemptCount}}),listQuestions:n=>o(async()=>F(await e.listLessonQuestions({...s(),lessonId:n})).questions.map(gt)),askQuestion:(n,i)=>o(async()=>gt(F(await e.askLessonQuestion({...s(),lessonId:n,content:i})))),rate:(n,i,a="")=>o(async()=>{let r=F(await e.rateLesson({...s(),lessonId:n,score:i,comment:a}));return{myRating:r.myRating,averageRating:r.averageRating,ratingCount:r.ratingCount}})}}function Ce(e){let t=_nullishCoalesce(e.host, () => (_chunkUFQVOGBSjs.m.call(void 0, ))),s=_connect.createClient.call(void 0, ut,_chunkUFQVOGBSjs.n.call(void 0, t));return ve(s,{accessId:e.accessId,accessToken:_nullishCoalesce(e.accessToken, () => ((()=>{let{projectID:o,host:n}=_chunkEWLOGFF2js.b.call(void 0, );return _chunkEWLOGFF2js.k.call(void 0, o,n)})))})}function bt({value:e,onSelect:t,disabled:s}){let{texts:o}=h(),[n,i]=_react.useState.call(void 0, 0),a=n||e;return _jsxruntime.jsx.call(void 0, "div",{className:"flex items-center gap-1",role:t?"radiogroup":"img","aria-label":w(o.rateStar,{n:Math.round(e)}),children:[1,2,3,4,5].map(r=>{let c=r<=Math.round(a),b=_jsxruntime.jsx.call(void 0, _lucidereact.Star,{className:_chunkM4KEUK6Ijs.d.call(void 0, "size-5",c?"fill-amber-400 text-amber-400":"text-neutral-300 dark:text-neutral-600"),"aria-hidden":!0});return t?_jsxruntime.jsx.call(void 0, "button",{type:"button",role:"radio","aria-checked":r===e,"aria-label":w(o.rateStar,{n:r}),disabled:s,className:"rounded p-0.5 outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:opacity-50",onMouseEnter:()=>i(r),onMouseLeave:()=>i(0),onClick:()=>t(r),children:b},r):_jsxruntime.jsx.call(void 0, "span",{children:b},r)})})}var ft="bg-[var(--course-accent,var(--primary))] text-[var(--course-accent-fg,var(--primary-foreground))] dark:bg-[var(--course-accent-dark,var(--course-accent,var(--primary)))] dark:text-[var(--course-accent-dark-fg,var(--course-accent-fg,var(--primary-foreground)))]",$=_chunkM4KEUK6Ijs.d.call(void 0, ft,"hover:bg-[var(--course-accent,var(--primary))] hover:opacity-90 dark:hover:bg-[var(--course-accent-dark,var(--course-accent,var(--primary)))]"),z="rounded-xl bg-white p-4 drop-shadow-xl sm:p-6 dark:border dark:border-neutral-700 dark:bg-neutral-900 dark:drop-shadow-none";function J(e,t){if(e instanceof Z)switch(e.code){case"unauthorized":return t.errorUnauthorized;case"course_not_found":case"item_not_found":return t.errorNotFound;case"unavailable":return t.errorUnavailable}return t.errorGeneric}function ee({error:e,onRetry:t,className:s}){let{texts:o}=h();return _jsxruntime.jsxs.call(void 0, "div",{role:"alert",className:_chunkM4KEUK6Ijs.d.call(void 0, "flex flex-col items-start gap-3 rounded-xl border border-red-200 bg-red-50 p-4 text-sm text-red-700 dark:border-red-900 dark:bg-red-950 dark:text-red-300",s),children:[_jsxruntime.jsxs.call(void 0, "p",{className:"flex items-center gap-2 font-medium",children:[_jsxruntime.jsx.call(void 0, _lucidereact.AlertTriangle,{className:"size-4 shrink-0","aria-hidden":!0}),J(e,o)]}),t&&_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{variant:"outline",size:"sm",onClick:t,children:o.retry})]})}function ue({label:e}){let{texts:t}=h();return _jsxruntime.jsxs.call(void 0, "div",{role:"status","aria-label":_nullishCoalesce(e, () => (t.loading)),className:"space-y-3",children:[_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.u,{className:"aspect-video w-full"}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.u,{className:"h-6 w-2/3"}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.u,{className:"h-4 w-full"}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.u,{className:"h-4 w-5/6"})]})}function At({percent:e,label:t}){let s=Math.max(0,Math.min(100,Math.round(e)));return _jsxruntime.jsx.call(void 0, "div",{role:"progressbar","aria-valuemin":0,"aria-valuemax":100,"aria-valuenow":s,"aria-label":t,className:"h-2 w-full overflow-hidden rounded-full bg-neutral-200 dark:bg-neutral-700",children:_jsxruntime.jsx.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, "h-full rounded-full transition-all duration-300",ft),style:{width:`${s}%`}})})}function j(e,t,s){let[o,n]=_react.useState.call(void 0, s!==void 0?{status:"success",data:s}:{status:"loading"}),[i,a]=_react.useState.call(void 0, 0),r=_react.useRef.call(void 0, 0),c=_react.useRef.call(void 0, e);c.current=e;let b=_react.useRef.call(void 0, s!==void 0);_react.useEffect.call(void 0, ()=>{if(b.current){b.current=!1;return}let p=++r.current;return n({status:"loading"}),c.current().then(A=>{p===r.current&&n({status:"success",data:A})}).catch(A=>{p===r.current&&n({status:"error",error:A})}),()=>{r.current+=1}},[...t,i]);let y=_react.useCallback.call(void 0, ()=>a(p=>p+1),[]),f=_react.useCallback.call(void 0, ()=>{let p=++r.current;c.current().then(A=>{p===r.current&&n({status:"success",data:A})}).catch(()=>{})},[]),d=_react.useCallback.call(void 0, p=>{n(A=>A.status==="success"?{status:"success",data:p(A.data)}:A)},[]);return{...o,reload:y,refresh:f,mutate:d}}function St({lessonId:e,initial:t}){let{dataSource:s,texts:o,preview:n}=h(),i=Q(),[a,r]=_react.useState.call(void 0, t),[c,b]=_react.useState.call(void 0, t.myRating),[y,f]=_react.useState.call(void 0, ""),[d,p]=_react.useState.call(void 0, "idle"),[A,C]=_react.useState.call(void 0, );async function I(){if(!(c<1)){p("saving");try{r(await s.rate(e,c,y.trim())),p("saved"),i("lesson_rated",{lesson_id:e,rating:c})}catch(g){C(g),p("error")}}}return _jsxruntime.jsxs.call(void 0, "section",{"aria-labelledby":`rate-${e}`,className:"space-y-3",children:[_jsxruntime.jsx.call(void 0, "h3",{id:`rate-${e}`,className:"text-lg font-semibold",children:o.rateTitle}),_jsxruntime.jsx.call(void 0, bt,{value:c,onSelect:g=>{b(g),p("idle")},disabled:n||d==="saving"}),a.ratingCount>0&&_jsxruntime.jsx.call(void 0, "p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:w(o.rateAverage,{average:a.averageRating.toFixed(1),count:a.ratingCount})}),!n&&c>0&&_jsxruntime.jsxs.call(void 0, "div",{className:"space-y-2",children:[_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.v,{value:y,maxLength:1e3,placeholder:o.ratePlaceholder,onChange:g=>{f(g.target.value),p("idle")}}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{size:"sm",className:$,disabled:d==="saving",onClick:I,children:o.rateSubmit})]}),d==="saved"&&_jsxruntime.jsx.call(void 0, "p",{role:"status",className:"text-sm text-emerald-700 dark:text-emerald-400",children:o.rateSaved}),d==="error"&&_jsxruntime.jsx.call(void 0, "p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:J(A,o)})]})}function yt(e){return e==null?"":e.toLocaleDateString()}function ht({lessonId:e}){let{dataSource:t,texts:s,preview:o}=h(),n=j(()=>t.listQuestions(e),[t,e]),[i,a]=_react.useState.call(void 0, ""),[r,c]=_react.useState.call(void 0, "idle"),[b,y]=_react.useState.call(void 0, );async function f(){let d=i.trim();if(d!==""){c("sending");try{let p=await t.askQuestion(e,d);n.mutate(A=>[p,...A]),a(""),c("sent")}catch(p){y(p),c("error")}}}return _jsxruntime.jsxs.call(void 0, "section",{"aria-labelledby":`qa-${e}`,className:"space-y-4",children:[_jsxruntime.jsx.call(void 0, "h3",{id:`qa-${e}`,className:"text-lg font-semibold",children:s.questionsTitle}),_jsxruntime.jsxs.call(void 0, "form",{className:"space-y-2",onSubmit:d=>{d.preventDefault(),f()},children:[_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.v,{value:i,maxLength:2e3,placeholder:s.questionPlaceholder,"aria-label":s.questionPlaceholder,onChange:d=>{a(d.target.value),c("idle")}}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{type:"submit",size:"sm",className:$,disabled:r==="sending"||i.trim()==="",children:s.questionSubmit}),r==="sent"&&_jsxruntime.jsx.call(void 0, "p",{role:"status",className:"text-sm text-emerald-700 dark:text-emerald-400",children:s.questionSent}),r==="error"&&_jsxruntime.jsx.call(void 0, "p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:J(b,s)})]}),n.status==="loading"&&_jsxruntime.jsx.call(void 0, "p",{role:"status",className:"text-sm text-neutral-500",children:s.loading}),n.status==="error"&&_jsxruntime.jsx.call(void 0, ee,{error:n.error,onRetry:n.reload}),n.status==="success"&&(n.data.length===0?_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:s.questionsEmpty}):_jsxruntime.jsx.call(void 0, "ul",{className:"space-y-3",children:n.data.map(d=>_jsxruntime.jsxs.call(void 0, "li",{className:"rounded-lg bg-neutral-100 p-3 text-sm dark:bg-neutral-800",children:[_jsxruntime.jsxs.call(void 0, "p",{className:"mb-1 text-xs text-neutral-500 dark:text-neutral-400",children:[d.isMine?s.questionMine:d.authorName,d.createdAt&&` \xB7 ${yt(d.createdAt)}`]}),_jsxruntime.jsx.call(void 0, "p",{className:"break-words whitespace-pre-wrap",children:d.content}),d.answer&&_jsxruntime.jsxs.call(void 0, "div",{className:"mt-2 border-l-2 border-neutral-400 pl-3 dark:border-neutral-500",children:[_jsxruntime.jsxs.call(void 0, "p",{className:"mb-1 text-xs font-medium text-neutral-500 dark:text-neutral-400",children:[s.questionTeamAnswer,d.answer.createdAt&&` \xB7 ${yt(d.answer.createdAt)}`]}),_jsxruntime.jsx.call(void 0, "p",{className:"break-words whitespace-pre-wrap",children:d.answer.content})]})]},d.id))}))]})}var _dompurify = require('dompurify'); var _dompurify2 = _interopRequireDefault(_dompurify);var gs={USE_PROFILES:{html:!0},FORBID_TAGS:["style","form","input","button","textarea","select","iframe","frame","object","embed","base","meta","link"],FORBID_ATTR:["style","srcdoc","formaction"],ALLOW_DATA_ATTR:!1,RETURN_TRUSTED_TYPE:!1};function bs(e){if(e.isSupported===!1||e.sanitize==null)return()=>"";let t=e.sanitize.bind(e);return _optionalChain([e, 'access', _18 => _18.addHook, 'optionalCall', _19 => _19("afterSanitizeAttributes",s=>{s.tagName==="A"&&s.hasAttribute("href")&&(s.setAttribute("target","_blank"),s.setAttribute("rel","noopener noreferrer nofollow")),s.tagName==="IMG"&&(s.setAttribute("loading","lazy"),s.setAttribute("referrerpolicy","no-referrer"))})]),s=>s==null||s===""?"":String(t(s,gs))}var vt;function Pe(e){return typeof window>"u"?"":(vt??=bs(_dompurify2.default),vt(e))}var _hlsjs = require('hls.js'); var _hlsjs2 = _interopRequireDefault(_hlsjs);function xs(e){return e.canPlayType("application/vnd.apple.mpegurl")!==""}function kt({src:e,poster:t,startAt:s=0,durationHint:o=0,autoPlay:n=!1,lessonId:i,onReport:a,onEnded:r,onExpired:c,className:b}){let{texts:y}=h(),f=Q(),d=_react.useRef.call(void 0, f);d.current=f;let p=_react.useRef.call(void 0, null),[A,C]=_react.useState.call(void 0, !1),[I,g]=_react.useState.call(void 0, 0),k=_react.useRef.call(void 0, {onReport:a,onEnded:r,onExpired:c,durationHint:o});k.current={onReport:a,onEnded:r,onExpired:c,durationHint:o};let M=_react.useRef.call(void 0, void 0),E=_react.useCallback.call(void 0, (l,R=p.current)=>{if(R==null||k.current.onReport==null)return;let m=Math.floor(R.currentTime);if(m<=0&&l!=="ended")return;let T=Number.isFinite(R.duration)?R.duration:k.current.durationHint;M.current=m,k.current.onReport({positionSeconds:m,durationSeconds:Math.floor(T||0),reason:l})},[]);return _react.useEffect.call(void 0, ()=>{let l=p.current;if(l==null)return;C(!1);let R,m=!1,T=!1,B,q=()=>{s>0&&(l.currentTime=s),n&&l.play().catch(()=>{})};xs(l)?(l.src=e,l.addEventListener("loadedmetadata",q,{once:!0})):_hlsjs2.default.isSupported()?(R=new _hlsjs2.default,R.attachMedia(l),R.loadSource(e),R.on(_hlsjs2.default.Events.MANIFEST_PARSED,q),R.on(_hlsjs2.default.Events.ERROR,(O,Y)=>{if(!Y.fatal)return;let Be=_optionalChain([Y, 'access', _20 => _20.response, 'optionalAccess', _21 => _21.code]);Be===401||Be===403?(_optionalChain([k, 'access', _22 => _22.current, 'access', _23 => _23.onExpired, 'optionalCall', _24 => _24()]),C(!0)):Y.type===_hlsjs2.default.ErrorTypes.NETWORK_ERROR&&!m?(m=!0,_optionalChain([R, 'optionalAccess', _25 => _25.startLoad, 'call', _26 => _26()])):Y.type===_hlsjs2.default.ErrorTypes.MEDIA_ERROR&&!T?(T=!0,_optionalChain([R, 'optionalAccess', _27 => _27.recoverMediaError, 'call', _28 => _28()])):C(!0)})):l.src=e;let pe=()=>{clearInterval(B),B=setInterval(()=>E("periodic"),15e3)},te=()=>{clearInterval(B),l.ended||E("pause")},oe=new Set,S=l.currentTime,W=0,re=O=>{if(i!=null)for(let Y of _chunkTSJCVFOXjs.s.call(void 0, O,oe))d.current("video_progress",{lesson_id:i,milestone:Y})},ae=()=>{let O=l.currentTime,Y=Number.isFinite(l.duration)?l.duration:k.current.durationHint;Y>0&&!l.seeking&&O>=S&&O-S<=2&&(W=Math.max(W,O),re(W/Y*100)),S=O},Ue=()=>{clearInterval(B),i!=null&&!oe.has(100)&&(oe.add(100),d.current("video_progress",{lesson_id:i,milestone:100})),E("ended"),_optionalChain([k, 'access', _29 => _29.current, 'access', _30 => _30.onEnded, 'optionalCall', _31 => _31()])},Le=()=>{R==null&&C(!0)};return l.addEventListener("play",pe),l.addEventListener("pause",te),l.addEventListener("ended",Ue),l.addEventListener("error",Le),l.addEventListener("timeupdate",ae),()=>{clearInterval(B),l.removeEventListener("play",pe),l.removeEventListener("pause",te),l.removeEventListener("ended",Ue),l.removeEventListener("error",Le),l.removeEventListener("timeupdate",ae),l.removeEventListener("loadedmetadata",q),!l.ended&&l.currentTime>0&&E("leave",l),_optionalChain([R, 'optionalAccess', _32 => _32.destroy, 'call', _33 => _33()]),l.removeAttribute("src"),l.load()}},[e,I]),_jsxruntime.jsxs.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, "relative overflow-hidden rounded-xl bg-black",b),children:[_jsxruntime.jsx.call(void 0, "video",{ref:p,className:"aspect-video w-full",poster:t,controls:!0,playsInline:!0,preload:"metadata",controlsList:"nodownload"}),A&&_jsxruntime.jsxs.call(void 0, "div",{role:"alert",className:"absolute inset-0 flex flex-col items-center justify-center gap-3 bg-black/80 p-4 text-center text-sm text-white",children:[_jsxruntime.jsx.call(void 0, "p",{children:y.videoError}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{variant:"secondary",size:"sm",onClick:()=>{_optionalChain([k, 'access', _34 => _34.current, 'access', _35 => _35.onExpired, 'optionalCall', _36 => _36()]),g(l=>l+1)},children:y.videoRetry})]})]})}function Vt({lesson:e,position:t=0,courseType:s,prev:o,next:n,autoplay:i,onAutoplayChange:a,startPlaying:r,onNavigate:c,onProgressSaved:b,onEnded:y,onUrlsExpired:f}){let{dataSource:d,texts:p,preview:A}=h(),C=Q(),[I,g]=_react.useState.call(void 0, e.completed),k=_react.useRef.call(void 0, e.completed),M=_react.useRef.call(void 0, void 0),E=_react.useRef.call(void 0, e.positionSeconds),[l,R]=_react.useState.call(void 0, !1);_react.useEffect.call(void 0, ()=>{C("lesson_started",{lesson_id:e.id,position:t})},[e.id]);let m=_optionalChain([e, 'access', _37 => _37.urlsExpireAt, 'optionalAccess', _38 => _38.getTime, 'call', _39 => _39()]);_react.useEffect.call(void 0, ()=>{if(m==null)return;let S=m-Date.now()-6e4;if(S<=0||S>2**31-1)return;let W=setTimeout(f,S);return()=>clearTimeout(W)},[m]);let T=_react.useMemo.call(void 0, ()=>Pe(e.contentHtml),[e.contentHtml]);async function B(S,W,re){if(A)return;let ae=await d.saveProgress({lessonId:e.id,completed:re,positionSeconds:S,durationSeconds:W});M.current=S,ae.completed&&!k.current&&(k.current=!0,g(!0),C("lesson_completed",{lesson_id:e.id,position:t})),b(e.id,ae)}function q(S){if(E.current=S.positionSeconds,A||S.reason==="periodic"&&S.positionSeconds===M.current)return;let W=S.reason==="ended"||tt(S.positionSeconds,S.durationSeconds);B(S.positionSeconds,S.durationSeconds,W&&!k.current).catch(re=>{console.warn("Saving the progress of the lesson failed",re)})}async function pe(){R(!0);try{await B(E.current,_nullishCoalesce(_optionalChain([e, 'access', _40 => _40.video, 'optionalAccess', _41 => _41.durationSeconds]), () => (0)),!0)}catch(S){console.warn("Marking the lesson as completed failed",S)}finally{R(!1)}}let te=e.description.trim(),oe=T!==""||te!=="";return _jsxruntime.jsxs.call(void 0, "article",{"data-lesson-id":e.id,className:"min-w-0 space-y-5","aria-labelledby":`lesson-${e.id}`,children:[e.video&&_jsxruntime.jsx.call(void 0, kt,{src:e.video.hlsUrl,poster:e.video.posterUrl,lessonId:e.id,startAt:st(E.current,e.video.durationSeconds),durationHint:e.video.durationSeconds,autoPlay:r,onReport:q,onEnded:y,onExpired:f}),_jsxruntime.jsxs.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, z,"space-y-5"),children:[_jsxruntime.jsxs.call(void 0, "header",{className:"flex flex-wrap items-start justify-between gap-3",children:[_jsxruntime.jsx.call(void 0, "h2",{id:`lesson-${e.id}`,className:"text-2xl font-semibold break-words",children:e.title}),!A&&_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{size:"sm",variant:I?"outline":"default",className:_chunkM4KEUK6Ijs.d.call(void 0, !I&&$),disabled:I||l,onClick:pe,children:I?p.markedComplete:p.markComplete})]}),s==="video"&&e.video&&_jsxruntime.jsxs.call(void 0, "label",{className:"flex w-fit cursor-pointer items-center gap-2 text-sm text-neutral-600 dark:text-neutral-300",children:[_jsxruntime.jsx.call(void 0, "input",{type:"checkbox",checked:i,onChange:S=>a(S.target.checked)}),p.autoplay]}),te!==""&&_jsxruntime.jsx.call(void 0, "p",{className:"text-sm break-words whitespace-pre-wrap text-neutral-700 dark:text-neutral-300",children:te}),T!==""&&_jsxruntime.jsx.call(void 0, "div",{className:"bitsnap-course-html break-words [&_a]:underline [&_blockquote]:border-l-4 [&_blockquote]:pl-4 [&_h1]:text-2xl [&_h1]:font-semibold [&_h2]:text-xl [&_h2]:font-semibold [&_h3]:text-lg [&_h3]:font-semibold [&_img]:h-auto [&_img]:max-w-full [&_ol]:list-decimal [&_ol]:pl-6 [&_p]:my-3 [&_pre]:overflow-x-auto [&_table]:block [&_table]:max-w-full [&_table]:overflow-x-auto [&_ul]:list-disc [&_ul]:pl-6",dangerouslySetInnerHTML:{__html:T}}),!oe&&!e.video&&_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:p.noContent}),e.attachments.length>0&&_jsxruntime.jsxs.call(void 0, "section",{"aria-labelledby":`files-${e.id}`,className:"space-y-2",children:[_jsxruntime.jsxs.call(void 0, "h3",{id:`files-${e.id}`,className:"flex items-center gap-2 text-lg font-semibold",children:[_jsxruntime.jsx.call(void 0, _lucidereact.Paperclip,{className:"size-4","aria-hidden":!0}),p.attachments]}),_jsxruntime.jsx.call(void 0, "ul",{className:"space-y-2",children:e.attachments.map(S=>_jsxruntime.jsx.call(void 0, "li",{children:_jsxruntime.jsxs.call(void 0, "a",{href:S.url,target:"_blank",rel:"noopener noreferrer",download:S.name,className:"flex items-center justify-between gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm transition hover:bg-neutral-200 dark:bg-neutral-800 dark:hover:bg-neutral-700",children:[_jsxruntime.jsxs.call(void 0, "span",{className:"min-w-0 break-words",children:[S.name,_jsxruntime.jsx.call(void 0, "span",{className:"ml-2 text-xs text-neutral-500 dark:text-neutral-400",children:nt(S.size)})]}),_jsxruntime.jsxs.call(void 0, "span",{className:"flex shrink-0 items-center gap-1 text-xs font-medium",children:[p.download,_jsxruntime.jsx.call(void 0, _lucidereact.Download,{className:"size-3.5","aria-hidden":!0})]})]})},S.id))})]}),_jsxruntime.jsxs.call(void 0, "nav",{className:"flex items-center justify-between gap-3 border-t pt-4",children:[_jsxruntime.jsxs.call(void 0, _chunkUFQVOGBSjs.q,{variant:"outline",size:"sm",disabled:o==null,onClick:()=>o&&c(o),children:[_jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft,{"aria-hidden":!0}),p.prevItem]}),_jsxruntime.jsxs.call(void 0, _chunkUFQVOGBSjs.q,{variant:"outline",size:"sm",disabled:n==null,onClick:()=>n&&c(n),children:[p.nextItem,_jsxruntime.jsx.call(void 0, _lucidereact.ChevronRight,{"aria-hidden":!0})]})]})]}),_jsxruntime.jsx.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, z),children:_jsxruntime.jsx.call(void 0, St,{lessonId:e.id,initial:{myRating:e.myRating,averageRating:e.averageRating,ratingCount:e.ratingCount}})}),_jsxruntime.jsx.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, z),children:_jsxruntime.jsx.call(void 0, ht,{lessonId:e.id})})]})}var It={pl:{back:"Wr\xF3\u0107",loading:"\u0141adowanie kursu...",retry:"Spr\xF3buj ponownie",errorGeneric:"Nie uda\u0142o si\u0119 za\u0142adowa\u0107 danych. Spr\xF3buj ponownie za chwil\u0119.",errorUnauthorized:"Nie masz dost\u0119pu do tego kursu. Zaloguj si\u0119 ponownie lub skontaktuj si\u0119 z pomoc\u0105.",errorNotFound:"Nie znaleziono tego kursu lub lekcji.",errorUnavailable:"Kurs jest chwilowo niedost\u0119pny. Spr\xF3buj ponownie za chwil\u0119.",previewBadge:"Podgl\u0105d",progressLessons:"{done} z {total} lekcji",progressPercent:"{percent}% uko\u0144czono",contents:"Spis tre\u015Bci",free:"Darmowa",locked:"Zablokowane",lockedByTest:"Odblokuje je zaliczenie testu: {test}",lockedPage:"Ta lekcja jest zablokowana. Zalicz test \u201E{test}\u201D, aby j\u0105 odblokowa\u0107.",testBadge:"Test",completed:"Uko\u0144czono",continueCourse:"Kontynuuj kurs",noItem:"Wybierz lekcj\u0119 z listy.",markComplete:"Oznacz jako uko\u0144czon\u0105",markedComplete:"Lekcja uko\u0144czona",prevItem:"Poprzednia",nextItem:"Nast\u0119pna",attachments:"Materia\u0142y do pobrania",download:"Pobierz",autoplay:"Autoodtwarzanie nast\u0119pnej lekcji",videoError:"Nie uda\u0142o si\u0119 odtworzy\u0107 wideo.",videoRetry:"Od\u015Bwie\u017C wideo",noContent:"Ta lekcja nie ma jeszcze tre\u015Bci.",testGating:"Zaliczenie tego testu odblokowuje kolejne lekcje.",testAttemptsLeft:"Pozosta\u0142o pr\xF3b: {count}",testAttemptsUnlimited:"Liczba pr\xF3b nie jest ograniczona.",testNoAttemptsLeft:"Wykorzystano wszystkie pr\xF3by.",testPassThreshold:"Pr\xF3g zaliczenia: {percent}%",testBestScore:"Najlepszy wynik: {percent}%",testSubmit:"Sprawd\u017A odpowiedzi",testSubmitting:"Sprawdzanie...",testUnanswered:"Pytania bez odpowiedzi: {count}",testPassed:"Test zaliczony",testFailed:"Test niezaliczony",testScore:"Wynik: {percent}%",testCorrect:"Poprawnie",testIncorrect:"Niepoprawnie",testCorrectAnswer:"Poprawna odpowied\u017A",testTryAgain:"Spr\xF3buj ponownie",testPreviousAttempts:"Poprzednie podej\u015Bcia",testAttemptRow:"{percent}% \xB7 {date}",testNoQuestions:"Ten test nie ma jeszcze pyta\u0144.",mcqSingle:"Wybierz jedn\u0105 odpowied\u017A",mcqMultiple:"Zaznacz wszystkie poprawne odpowiedzi",todoHint:"Zaznacz wykonane kroki",todoRequired:"wymagany",gapPlaceholder:"uzupe\u0142nij",sortHint:"U\u0142\xF3\u017C elementy w poprawnej kolejno\u015Bci",moveUp:"Przesu\u0144 w g\xF3r\u0119",moveDown:"Przesu\u0144 w d\xF3\u0142",questionsTitle:"Pytania do lekcji",questionsEmpty:"Nikt jeszcze nie zada\u0142 pytania.",questionPlaceholder:"Zadaj pytanie autorowi kursu",questionSubmit:"Wy\u015Blij pytanie",questionSent:"Pytanie wys\u0142ane.",questionMine:"Ty",questionTeamAnswer:"Odpowied\u017A autora",rateTitle:"Oce\u0144 lekcj\u0119",rateStar:"{n} z 5",ratePlaceholder:"Komentarz (opcjonalnie)",rateSubmit:"Zapisz ocen\u0119",rateSaved:"Dzi\u0119kujemy za ocen\u0119.",rateAverage:"\u015Arednia ocena {average} ({count})"},en:{back:"Back",loading:"Loading the course...",retry:"Try again",errorGeneric:"Could not load the data. Please try again in a moment.",errorUnauthorized:"You do not have access to this course. Sign in again or contact support.",errorNotFound:"This course or lesson was not found.",errorUnavailable:"The course is temporarily unavailable. Please try again in a moment.",previewBadge:"Preview",progressLessons:"{done} of {total} lessons",progressPercent:"{percent}% complete",contents:"Contents",free:"Free",locked:"Locked",lockedByTest:"Pass the test to unlock it: {test}",lockedPage:"This lesson is locked. Pass the test \u201C{test}\u201D to unlock it.",testBadge:"Test",completed:"Completed",continueCourse:"Continue the course",noItem:"Pick a lesson from the list.",markComplete:"Mark as completed",markedComplete:"Lesson completed",prevItem:"Previous",nextItem:"Next",attachments:"Downloads",download:"Download",autoplay:"Autoplay the next lesson",videoError:"The video could not be played.",videoRetry:"Reload the video",noContent:"This lesson has no content yet.",testGating:"Passing this test unlocks the following lessons.",testAttemptsLeft:"Attempts left: {count}",testAttemptsUnlimited:"The number of attempts is unlimited.",testNoAttemptsLeft:"You have used all attempts.",testPassThreshold:"Passing score: {percent}%",testBestScore:"Best score: {percent}%",testSubmit:"Check answers",testSubmitting:"Checking...",testUnanswered:"Unanswered questions: {count}",testPassed:"Test passed",testFailed:"Test not passed",testScore:"Score: {percent}%",testCorrect:"Correct",testIncorrect:"Incorrect",testCorrectAnswer:"Correct answer",testTryAgain:"Try again",testPreviousAttempts:"Previous attempts",testAttemptRow:"{percent}% \xB7 {date}",testNoQuestions:"This test has no questions yet.",mcqSingle:"Choose one answer",mcqMultiple:"Select all correct answers",todoHint:"Check the steps you have done",todoRequired:"required",gapPlaceholder:"fill in",sortHint:"Put the items in the correct order",moveUp:"Move up",moveDown:"Move down",questionsTitle:"Questions about this lesson",questionsEmpty:"Nobody has asked a question yet.",questionPlaceholder:"Ask the course author a question",questionSubmit:"Send question",questionSent:"Question sent.",questionMine:"You",questionTeamAnswer:"Answer from the author",rateTitle:"Rate this lesson",rateStar:"{n} of 5",ratePlaceholder:"Comment (optional)",rateSubmit:"Save rating",rateSaved:"Thanks for your rating.",rateAverage:"Average rating {average} ({count})"}};function Te(e,t=_chunkM4KEUK6Ijs.m.call(void 0, )){return _chunkM4KEUK6Ijs.i.call(void 0, It,t,e)}function Ns({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=h();if(e.config.type!=="mcq")return null;let{choices:i,multiple:a}=e.config;return _jsxruntime.jsxs.call(void 0, "fieldset",{className:"space-y-2",disabled:s,children:[_jsxruntime.jsx.call(void 0, "legend",{className:"mb-2 text-xs text-neutral-500 dark:text-neutral-400",children:a?n.mcqMultiple:n.mcqSingle}),i.map(r=>{let c=t.selectedIds.includes(r.id);return _jsxruntime.jsxs.call(void 0, "label",{className:"flex cursor-pointer items-start gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[_jsxruntime.jsx.call(void 0, "input",{className:"mt-0.5",type:a?"checkbox":"radio",name:`q-${e.id}`,checked:c,onChange:()=>o({type:"mcq",selectedIds:a?c?t.selectedIds.filter(b=>b!==r.id):[...t.selectedIds,r.id]:[r.id]})}),_jsxruntime.jsx.call(void 0, "span",{className:"break-words",children:r.label})]},r.id)})]})}function Ps({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=h();return e.config.type!=="todolist"?null:_jsxruntime.jsxs.call(void 0, "fieldset",{className:"space-y-2",disabled:s,children:[_jsxruntime.jsx.call(void 0, "legend",{className:"mb-2 text-xs text-neutral-500 dark:text-neutral-400",children:n.todoHint}),e.config.steps.map(i=>{let a=t.checkedIds.includes(i.id);return _jsxruntime.jsxs.call(void 0, "label",{className:"flex cursor-pointer items-start gap-3 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[_jsxruntime.jsx.call(void 0, "input",{className:"mt-0.5",type:"checkbox",checked:a,onChange:()=>o({type:"todolist",checkedIds:a?t.checkedIds.filter(r=>r!==i.id):[...t.checkedIds,i.id]})}),_jsxruntime.jsxs.call(void 0, "span",{className:"break-words",children:[i.label,i.required&&_jsxruntime.jsxs.call(void 0, "span",{className:"ml-2 text-xs text-neutral-500 dark:text-neutral-400",children:["(",n.todoRequired,")"]})]})]},i.id)})]})}function Vs({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=h();if(e.config.type!=="fill_gap")return null;let i=he(e.config.template,e.config.blanks);return _jsxruntime.jsx.call(void 0, "p",{className:"text-sm leading-10 break-words",children:i.map((a,r)=>a.type==="text"?_jsxruntime.jsx.call(void 0, "span",{className:"whitespace-pre-wrap",children:a.text},r):_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.r,{className:"mx-1 inline-block h-8 w-36 align-middle","aria-label":`${n.gapPlaceholder} ${a.id}`,placeholder:n.gapPlaceholder,disabled:s,value:_nullishCoalesce(t.values[a.id], () => ("")),onChange:c=>o({type:"fill_gap",values:{...t.values,[a.id]:c.target.value}})},r))})}function Is({question:e,answer:t,disabled:s,onChange:o}){let{texts:n}=h();if(e.config.type!=="sort_order")return null;let i=new Map(e.config.items.map(r=>[r.id,r.label])),a=(r,c)=>o({type:"sort_order",orderedIds:at(t.orderedIds,r,c)});return _jsxruntime.jsxs.call(void 0, "div",{className:"space-y-2",children:[_jsxruntime.jsx.call(void 0, "p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:n.sortHint}),_jsxruntime.jsx.call(void 0, "ol",{className:"space-y-2",children:t.orderedIds.map((r,c)=>_jsxruntime.jsxs.call(void 0, "li",{className:"flex items-center gap-2 rounded-lg bg-neutral-100 px-3 py-2 text-sm dark:bg-neutral-800",children:[_jsxruntime.jsxs.call(void 0, "span",{className:"w-5 text-xs text-neutral-500",children:[c+1,"."]}),_jsxruntime.jsx.call(void 0, "span",{className:"min-w-0 flex-1 break-words",children:_nullishCoalesce(i.get(r), () => (r))}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{type:"button",variant:"ghost",size:"icon-xs","aria-label":n.moveUp,disabled:s||c===0,onClick:()=>a(c,c-1),children:_jsxruntime.jsx.call(void 0, _lucidereact.ArrowUp,{"aria-hidden":!0})}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{type:"button",variant:"ghost",size:"icon-xs","aria-label":n.moveDown,disabled:s||c===t.orderedIds.length-1,onClick:()=>a(c,c+1),children:_jsxruntime.jsx.call(void 0, _lucidereact.ArrowDown,{"aria-hidden":!0})})]},r))})]})}function Ts({question:e,answer:t,disabled:s,onChange:o}){let n={question:e,disabled:s,onChange:o};switch(t.type){case"mcq":return _jsxruntime.jsx.call(void 0, Ns,{...n,answer:t});case"todolist":return _jsxruntime.jsx.call(void 0, Ps,{...n,answer:t});case"fill_gap":return _jsxruntime.jsx.call(void 0, Vs,{...n,answer:t});case"sort_order":return _jsxruntime.jsx.call(void 0, Is,{...n,answer:t})}}function ws({list:e}){let{texts:t}=h();return e.submissions.length===0?null:_jsxruntime.jsxs.call(void 0, "section",{className:"space-y-2",children:[_jsxruntime.jsx.call(void 0, "h3",{className:"text-sm font-semibold",children:t.testPreviousAttempts}),_jsxruntime.jsx.call(void 0, "ul",{className:"space-y-1 text-sm",children:e.submissions.map(s=>_jsxruntime.jsxs.call(void 0, "li",{className:"flex items-center gap-2 text-neutral-700 dark:text-neutral-300",children:[s.passed?_jsxruntime.jsx.call(void 0, _lucidereact.Check,{className:"size-4 text-emerald-600","aria-hidden":!0}):_jsxruntime.jsx.call(void 0, _lucidereact.X,{className:"size-4 text-red-600","aria-hidden":!0}),w(t.testAttemptRow,{percent:s.scorePercent,date:_nullishCoalesce(_optionalChain([s, 'access', _42 => _42.createdAt, 'optionalAccess', _43 => _43.toLocaleString, 'call', _44 => _44()]), () => (""))})]},s.id))})]})}function Es({test:e,onSubmitted:t,onTryAgain:s}){let{dataSource:o,texts:n}=h(),[i,a]=_react.useState.call(void 0, {}),[r,c]=_react.useState.call(void 0, ),[b,y]=_react.useState.call(void 0, !1),[f,d]=_react.useState.call(void 0, ),p=_react.useRef.call(void 0, Date.now()),A=_react.useRef.call(void 0, e.questions).current,C=e.attemptsLeft!=null&&e.attemptsLeft<=0,I=r?r.attemptsLeft:e.attemptsLeft,g=rt(A,i),k=l=>_nullishCoalesce(i[l.id], () => (ot(l.config)));async function M(){y(!0),d(void 0);try{let l={};for(let m of A)l[m.id]=k(m);let R=await o.submitTest(e.id,l,Date.now()-p.current);c(R),t(R)}catch(l){d(l)}finally{y(!1)}}let E=new Map(_optionalChain([r, 'optionalAccess', _45 => _45.results, 'access', _46 => _46.map, 'call', _47 => _47(l=>[l.questionId,l])]));return A.length===0?_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-500 dark:text-neutral-400",children:n.testNoQuestions}):_jsxruntime.jsxs.call(void 0, "form",{className:"space-y-6",onSubmit:l=>{l.preventDefault(),r==null&&!C&&M()},children:[r&&_jsxruntime.jsxs.call(void 0, "div",{role:"status",className:_chunkM4KEUK6Ijs.d.call(void 0, "rounded-xl p-4 text-sm",r.submission.passed?"bg-emerald-50 text-emerald-800 dark:bg-emerald-950 dark:text-emerald-300":"bg-red-50 text-red-800 dark:bg-red-950 dark:text-red-300"),children:[_jsxruntime.jsx.call(void 0, "p",{className:"text-base font-semibold",children:r.submission.passed?n.testPassed:n.testFailed}),_jsxruntime.jsx.call(void 0, "p",{children:w(n.testScore,{percent:r.submission.scorePercent})})]}),_jsxruntime.jsx.call(void 0, "ol",{className:"space-y-6",children:A.map((l,R)=>{let m=E.get(l.id),T=_optionalChain([m, 'optionalAccess', _48 => _48.correctAnswer])!=null?it(l,m.correctAnswer):"";return _jsxruntime.jsxs.call(void 0, "li",{className:"space-y-3","data-question-id":l.id,children:[_jsxruntime.jsxs.call(void 0, "p",{className:"font-medium break-words",children:[R+1,". ",l.prompt]}),_jsxruntime.jsx.call(void 0, Ts,{question:l,answer:k(l),disabled:r!=null||b||C,onChange:B=>a(q=>({...q,[l.id]:B}))}),m&&_jsxruntime.jsxs.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, "flex items-start gap-2 text-sm",m.correct?"text-emerald-700 dark:text-emerald-400":"text-red-700 dark:text-red-400"),children:[m.correct?_jsxruntime.jsx.call(void 0, _lucidereact.Check,{className:"mt-0.5 size-4 shrink-0","aria-hidden":!0}):_jsxruntime.jsx.call(void 0, _lucidereact.X,{className:"mt-0.5 size-4 shrink-0","aria-hidden":!0}),_jsxruntime.jsxs.call(void 0, "span",{children:[m.correct?n.testCorrect:n.testIncorrect,T!==""&&!m.correct&&_jsxruntime.jsxs.call(void 0, "span",{className:"block text-neutral-700 dark:text-neutral-300",children:[n.testCorrectAnswer,": ",T]})]})]})]},l.id)})}),f!=null&&_jsxruntime.jsx.call(void 0, "p",{role:"alert",className:"text-sm text-red-600 dark:text-red-400",children:J(f,n)}),r==null?_jsxruntime.jsxs.call(void 0, "div",{className:"space-y-2",children:[g>0&&!C&&_jsxruntime.jsx.call(void 0, "p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:w(n.testUnanswered,{count:g})}),_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{type:"submit",className:$,disabled:b||C,children:b?n.testSubmitting:n.testSubmit})]}):I!==0&&!r.submission.passed&&_jsxruntime.jsx.call(void 0, _chunkUFQVOGBSjs.q,{type:"button",variant:"outline",onClick:s,children:n.testTryAgain})]})}function Gt({testId:e,title:t,onSubmitted:s}){let{dataSource:o,texts:n}=h(),i=Q(),a=j(()=>o.getTest(e),[o,e]),r=j(()=>o.listSubmissions(e).catch(()=>{}),[o,e]),[c,b]=_react.useState.call(void 0, 0);return _jsxruntime.jsxs.call(void 0, "article",{"data-test-id":e,className:_chunkM4KEUK6Ijs.d.call(void 0, z,"min-w-0 space-y-5"),children:[_jsxruntime.jsx.call(void 0, "h2",{className:"text-2xl font-semibold break-words",children:a.status==="success"?a.data.title:t}),a.status==="loading"&&_jsxruntime.jsx.call(void 0, ue,{}),a.status==="error"&&_jsxruntime.jsx.call(void 0, ee,{error:a.error,onRetry:a.reload}),a.status==="success"&&_jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment,{children:[a.data.description.trim()!==""&&_jsxruntime.jsx.call(void 0, "p",{className:"text-sm break-words whitespace-pre-wrap text-neutral-700 dark:text-neutral-300",children:a.data.description}),_jsxruntime.jsxs.call(void 0, "ul",{className:"space-y-1 text-sm text-neutral-600 dark:text-neutral-400",children:[a.data.gating&&_jsxruntime.jsx.call(void 0, "li",{children:n.testGating}),_jsxruntime.jsx.call(void 0, "li",{children:w(n.testPassThreshold,{percent:a.data.passThresholdPercent})}),_jsxruntime.jsx.call(void 0, "li",{children:a.data.attemptsLeft==null?n.testAttemptsUnlimited:w(n.testAttemptsLeft,{count:a.data.attemptsLeft})}),a.data.attemptCount>0&&_jsxruntime.jsx.call(void 0, "li",{children:w(n.testBestScore,{percent:a.data.bestScorePercent})})]}),a.data.attemptsLeft!=null&&a.data.attemptsLeft<=0&&_jsxruntime.jsx.call(void 0, "p",{role:"alert",className:"text-sm font-medium text-red-700 dark:text-red-400",children:n.testNoAttemptsLeft}),_jsxruntime.jsx.call(void 0, Es,{test:a.data,onSubmitted:y=>{i("test_submitted",{lesson_id:e,score_bucket:_chunkTSJCVFOXjs.r.call(void 0, y.submission.scorePercent)}),s(y),r.refresh(),a.refresh()},onTryAgain:()=>b(y=>y+1)},c),r.status==="success"&&r.data&&_jsxruntime.jsx.call(void 0, ws,{list:r.data})]})]})}var Ut="bitsnap-course-autoplay";function Zs(){let[e,t]=_react.useState.call(void 0, !1);_react.useEffect.call(void 0, ()=>{try{t(window.localStorage.getItem(Ut)==="1")}catch (e2){}},[]);let s=_react.useCallback.call(void 0, o=>{t(o);try{window.localStorage.setItem(Ut,o?"1":"0")}catch (e3){}},[]);return[e,s]}function Lt(e){return e==null?void 0:{id:e.id,kind:e.kind}}function Xs({item:e}){let{texts:t}=h();return _jsxruntime.jsxs.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, z,"space-y-2"),role:"status",children:[_jsxruntime.jsx.call(void 0, "h2",{className:"text-2xl font-semibold break-words",children:e.title}),_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-600 dark:text-neutral-300",children:e.lockReason?w(t.lockedPage,{test:e.lockReason.gatingTestTitle}):t.locked})]})}function Ms({course:e,item:t,autoplay:s,onAutoplayChange:o,startPlaying:n,onNavigate:i,onProgressSaved:a,onEnded:r,onLocked:c}){let{dataSource:b,texts:y}=h(),f=j(()=>b.getLesson(t.id),[b,t.id]),d=f.status==="error"&&f.error instanceof Z&&f.error.code==="item_locked";if(_react.useEffect.call(void 0, ()=>{d&&c()},[d]),f.status==="loading")return _jsxruntime.jsx.call(void 0, ue,{});if(f.status==="error")return d?_jsxruntime.jsxs.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, z,"space-y-2"),role:"alert",children:[_jsxruntime.jsx.call(void 0, "h2",{className:"text-2xl font-semibold break-words",children:t.title}),_jsxruntime.jsx.call(void 0, "p",{className:"text-sm",children:J(f.error,y)})]}):_jsxruntime.jsx.call(void 0, ee,{error:f.error,onRetry:f.reload});let p=ye(e,t.id),A=ce(e).filter(C=>C.kind!=="test").findIndex(C=>C.id===t.id)+1;return _jsxruntime.jsx.call(void 0, Vt,{lesson:f.data,position:A,courseType:e.type,prev:_nullishCoalesce(f.data.prev, () => (Lt(p.prev))),next:_nullishCoalesce(f.data.next, () => (Lt(p.next))),autoplay:s,onAutoplayChange:o,startPlaying:n,onNavigate:i,onProgressSaved:a,onEnded:r,onUrlsExpired:f.refresh},f.data.id)}function zs({course:e,selectedId:t,onSelect:s,onBack:o,onCourseChange:n,onCourseRefresh:i}){let{texts:a,preview:r}=h(),c=Q(),b=_react.useRef.call(void 0, null),[y,f]=_react.useState.call(void 0, !1),[d,p]=Zs(),[A,C]=_react.useState.call(void 0, ),I=et(e,t),g=le(e,I);_react.useEffect.call(void 0, ()=>{c("course_opened",{})},[e.id]);let k=(m,T=!1)=>{C(T?m:void 0),f(!1),s(m),_optionalChain([b, 'access', _49 => _49.current, 'optionalAccess', _50 => _50.scrollIntoView, 'call', _51 => _51({block:"start",behavior:"smooth"})])},M=_jsxruntime.jsx.call(void 0, ct,{course:e,selectedId:I,variant:e.type==="video"?"playlist":"modules",onSelect:m=>k(m.id)}),E;g==null?E=_jsxruntime.jsx.call(void 0, "p",{className:"text-sm text-neutral-500",children:a.noItem}):g.locked?E=_jsxruntime.jsx.call(void 0, Xs,{item:g}):g.kind==="test"?E=_jsxruntime.jsx.call(void 0, Gt,{testId:g.id,title:g.title,onSubmitted:()=>i()},g.id):E=_jsxruntime.jsx.call(void 0, Ms,{course:e,item:g,autoplay:d,onAutoplayChange:p,startPlaying:A===g.id,onNavigate:m=>k(m.id),onProgressSaved:(m,T)=>n(B=>({...T.completed?$e(B,m):B,completedLessons:T.completedLessons,progressPercent:T.progressPercent})),onEnded:()=>{if(!d)return;let m=Oe(e,g.id);m&&k(m.id,!0)},onLocked:i},g.id);let l=e.type==="video",R=e.resumeItemId!==""&&e.resumeItemId!==I&&_optionalChain([le, 'call', _52 => _52(e,e.resumeItemId), 'optionalAccess', _53 => _53.locked])===!1;return _jsxruntime.jsxs.call(void 0, "div",{ref:b,className:"space-y-4 scroll-mt-4",children:[_jsxruntime.jsxs.call(void 0, "header",{className:"space-y-3",children:[_jsxruntime.jsxs.call(void 0, "div",{className:"flex flex-wrap items-center gap-2",children:[o&&_jsxruntime.jsxs.call(void 0, _chunkUFQVOGBSjs.q,{variant:"ghost",size:"sm",onClick:o,children:[_jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft,{"aria-hidden":!0}),a.back]}),r&&_jsxruntime.jsx.call(void 0, "span",{"data-course-preview":!0,className:"rounded-full bg-amber-100 px-3 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900/40 dark:text-amber-200",children:a.previewBadge})]}),_jsxruntime.jsxs.call(void 0, "div",{className:"flex flex-wrap items-end justify-between gap-4",children:[_jsxruntime.jsx.call(void 0, "div",{className:"min-w-0 space-y-1",children:_jsxruntime.jsx.call(void 0, "h1",{className:"text-2xl font-semibold break-words sm:text-3xl",children:e.title})}),R&&_jsxruntime.jsxs.call(void 0, _chunkUFQVOGBSjs.q,{size:"sm",variant:"outline",onClick:()=>k(e.resumeItemId),children:[_jsxruntime.jsx.call(void 0, _lucidereact.Play,{"aria-hidden":!0}),a.continueCourse]})]}),_jsxruntime.jsxs.call(void 0, "div",{className:"max-w-xl space-y-1",children:[_jsxruntime.jsx.call(void 0, At,{percent:e.progressPercent,label:w(a.progressPercent,{percent:e.progressPercent})}),_jsxruntime.jsxs.call(void 0, "p",{className:"text-xs text-neutral-500 dark:text-neutral-400",children:[w(a.progressLessons,{done:e.completedLessons,total:e.totalLessons})," \xB7 ",w(a.progressPercent,{percent:e.progressPercent})]})]})]}),_jsxruntime.jsxs.call(void 0, "div",{className:_chunkM4KEUK6Ijs.d.call(void 0, "grid gap-6",l?"lg:grid-cols-[minmax(0,1fr)_380px]":"lg:grid-cols-[320px_minmax(0,1fr)]"),children:[!l&&_jsxruntime.jsx.call(void 0, "div",{className:"lg:hidden",children:_jsxruntime.jsxs.call(void 0, _chunkUFQVOGBSjs.q,{variant:"outline",size:"sm","aria-expanded":y,onClick:()=>f(m=>!m),children:[_jsxruntime.jsx.call(void 0, _lucidereact.List,{"aria-hidden":!0}),a.contents]})}),!l&&_jsxruntime.jsx.call(void 0, "aside",{className:_chunkM4KEUK6Ijs.d.call(void 0, z,"p-3 sm:p-3 lg:sticky lg:top-4 lg:block lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto",y?"block":"hidden"),children:M}),_jsxruntime.jsx.call(void 0, "div",{className:"min-w-0","data-course-content":!0,children:E}),l&&_jsxruntime.jsx.call(void 0, "aside",{className:_chunkM4KEUK6Ijs.d.call(void 0, z,"p-3 sm:p-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto"),children:M})]})]})}function Qs({accessId:e,accessToken:t,dataSource:s,preview:o=!1,itemId:n,onItemChange:i,onBack:a,initialCourse:r,texts:c,locale:b,theme:y,className:f,analytics:d}){let p=_chunkEWLOGFF2js.j.call(void 0, ),A=_chunkTSJCVFOXjs.n.call(void 0, p.projectID,o?!1:d,"course"),C=_react.useMemo.call(void 0, ()=>Te(c,b),[c,b]),I=_react.useMemo.call(void 0, ()=>{if(s)return s;if(e==null||e==="")throw new Error("PanelCourse needs an accessId or a dataSource");return Ce({accessId:e,accessToken:t})},[s,e,t]),g=j(()=>I.getCourse(),[I],r),[k,M]=_react.useState.call(void 0, ),E=i?n:_nullishCoalesce(n, () => (k)),l=_react.useCallback.call(void 0, B=>{M(B),_optionalChain([i, 'optionalCall', _54 => _54(B)])},[i]),R=De(_nullishCoalesce(_optionalChain([y, 'optionalAccess', _55 => _55.colors]), () => (_optionalChain([p, 'access', _56 => _56.theme, 'optionalAccess', _57 => _57.colors])))),m=g.status==="success"?g.data.id:void 0,T=_react.useMemo.call(void 0, ()=>({dataSource:I,texts:C,preview:o,analytics:{courseId:m,instrument:A}}),[I,C,o,m,A]);return _jsxruntime.jsx.call(void 0, _chunkTSJCVFOXjs.l,{value:A,children:_jsxruntime.jsx.call(void 0, qe,{value:T,children:_jsxruntime.jsxs.call(void 0, "div",{"data-bitsnap-course":!0,"data-course-type":g.status==="success"?g.data.type:void 0,style:R,className:_chunkM4KEUK6Ijs.d.call(void 0, "font-inter w-full max-w-7xl",f),children:[g.status==="loading"&&_jsxruntime.jsxs.call(void 0, "div",{className:"space-y-4",children:[a&&_jsxruntime.jsxs.call(void 0, _chunkUFQVOGBSjs.q,{variant:"ghost",size:"sm",onClick:a,children:[_jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft,{"aria-hidden":!0}),C.back]}),_jsxruntime.jsx.call(void 0, "p",{className:"sr-only",role:"status",children:C.loading}),_jsxruntime.jsx.call(void 0, ue,{})]}),g.status==="error"&&_jsxruntime.jsxs.call(void 0, "div",{className:"space-y-4",children:[a&&_jsxruntime.jsxs.call(void 0, _chunkUFQVOGBSjs.q,{variant:"ghost",size:"sm",onClick:a,children:[_jsxruntime.jsx.call(void 0, _lucidereact.ChevronLeft,{"aria-hidden":!0}),C.back]}),_jsxruntime.jsx.call(void 0, ee,{error:g.error,onRetry:g.reload})]}),g.status==="success"&&_jsxruntime.jsx.call(void 0, zs,{course:g.data,selectedId:E,onSelect:l,onBack:a,onCourseChange:g.mutate,onCourseRefresh:g.refresh})]})})})}exports.COURSE_TEXT_TABLES = It; exports.CourseError = Z; exports.PanelCourse = Qs; exports.createCourseDataSource = ve; exports.createRpcCourseDataSource = Ce; exports.getCourseTexts = Te; exports.sanitizeLessonHtml = Pe;
2
2
  //# sourceMappingURL=course.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/course.js","../src/course/PanelCourse.tsx","../src/course/accent.ts","../src/course/analytics.ts","../src/course/context.tsx","../src/course/CourseOutline.tsx","../src/course/logic.ts"],"names":["accentVars","colors","vars","brand","brandDark","foreground","readableForeground","CourseViewContext","createContext","CourseViewProvider","useCourseView","value","useContext","useCourseTrack","analytics","preview","useCallback","event","props","flattenItems","course","section","item","findItem","itemId","getAdjacentItems","items","index","getAutoplayTarget","next","computeProgress","total","completed","markLessonCompleted","lessonId","sections","progress","resolveInitialItemId","requestedId","fillTemplate","template","values","match","key","isPlaybackComplete","positionSeconds","durationSeconds","resumePosition","formatDuration","totalSeconds","seconds","h","m","s"],"mappings":"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","file":"/home/emems/web/bitsnap/bitsnap/bitsnap-react/dist/course.js","sourcesContent":[null,"import { ChevronLeft, List, Play } from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n InstrumentProvider,\n useInstrument,\n type AnalyticsOption,\n} from \"../analytics/instrument\";\nimport { usePanelConfig } from \"../panel/PanelProvider\";\nimport type { GetThemeOutput } from \"../panel/types\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../ui/button\";\nimport { accentVars } from \"./accent\";\nimport { useCourseTrack } from \"./analytics\";\nimport { CourseViewProvider, useCourseView } from \"./context\";\nimport { CourseOutline } from \"./CourseOutline\";\nimport { createRpcCourseDataSource } from \"./rpc\";\nimport { LessonPane } from \"./LessonPane\";\nimport {\n fillTemplate,\n findItem,\n flattenItems,\n getAdjacentItems,\n getAutoplayTarget,\n markLessonCompleted,\n resolveInitialItemId,\n type FlatItem,\n} from \"./logic\";\nimport { getCourseTexts, type CourseTexts } from \"./texts\";\nimport { TestPane } from \"./TestPane\";\nimport {\n CourseError,\n type Course,\n type CourseDataSource,\n type ItemRef,\n type Lesson,\n type ProgressSaved,\n} from \"./types\";\nimport { CARD, ErrorBox, LoadingBlock, ProgressBar, errorMessage } from \"./ui\";\nimport { useAsyncResource } from \"./useAsyncResource\";\n\nexport type PanelCourseProps = {\n /** `UserProduct.access_id` of the purchased course. Not needed with `dataSource`. */\n accessId?: string;\n /** Panel session token; read from the panel session when omitted. */\n accessToken?: string;\n /**\n * Replaces the default data source (`UserPanelCourseService` through the\n * panel's transport). The creator preview of the dashboard passes its own.\n */\n dataSource?: CourseDataSource;\n /** Creator preview: shows a badge and never saves progress. */\n preview?: boolean;\n /** The open lesson or test. Controlled together with `onItemChange`. */\n itemId?: string;\n onItemChange?: (itemId: string) => void;\n /** Shows a back button. */\n onBack?: () => void;\n /** Course structure to render right away (server rendering, preview). */\n initialCourse?: Course;\n /** Text overrides; Polish and English defaults by `locale`. */\n texts?: Partial<CourseTexts>;\n /** Defaults to the locale of the library (`setLocale`). */\n locale?: string;\n /** Brand colors; the panel theme (`setTheme`) by default. */\n theme?: GetThemeOutput;\n className?: string;\n /**\n * Usage statistics for your Bitsnap dashboard (course opened, lessons\n * started and completed, video progress, tests, ratings). On by default;\n * `false` turns it off, and the creator `preview` never reports. Inside\n * `<Panel>` the panel's setting applies unless this is given. An object\n * lets you forward every event: `{ onEvent }`. Nothing is stored on the\n * visitor's device.\n */\n analytics?: AnalyticsOption;\n};\n\nconst AUTOPLAY_KEY = \"bitsnap-course-autoplay\";\n\nfunction useAutoplayPreference(): [boolean, (value: boolean) => void] {\n const [value, setValue] = useState(false);\n useEffect(() => {\n try {\n setValue(window.localStorage.getItem(AUTOPLAY_KEY) === \"1\");\n } catch {\n // Storage is not available (private mode); keep the default.\n }\n }, []);\n const update = useCallback((next: boolean) => {\n setValue(next);\n try {\n window.localStorage.setItem(AUTOPLAY_KEY, next ? \"1\" : \"0\");\n } catch {\n // Not persisted.\n }\n }, []);\n return [value, update];\n}\n\nfunction toRef(item: FlatItem | undefined): ItemRef | undefined {\n return item == null ? undefined : { id: item.id, kind: item.kind };\n}\n\nfunction LockedPane({ item }: { item: FlatItem }) {\n const { texts } = useCourseView();\n return (\n <div className={cn(CARD, \"space-y-2\")} role=\"status\">\n <h2 className=\"text-2xl font-semibold break-words\">{item.title}</h2>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-300\">\n {item.lockReason\n ? fillTemplate(texts.lockedPage, { test: item.lockReason.gatingTestTitle })\n : texts.locked}\n </p>\n </div>\n );\n}\n\ntype LessonContainerProps = {\n course: Course;\n item: FlatItem;\n autoplay: boolean;\n onAutoplayChange: (value: boolean) => void;\n startPlaying: boolean;\n onNavigate: (ref: ItemRef) => void;\n onProgressSaved: (lessonId: string, saved: ProgressSaved) => void;\n onEnded: () => void;\n onLocked: () => void;\n};\n\nfunction LessonContainer({\n course,\n item,\n autoplay,\n onAutoplayChange,\n startPlaying,\n onNavigate,\n onProgressSaved,\n onEnded,\n onLocked,\n}: LessonContainerProps) {\n const { dataSource, texts } = useCourseView();\n const lesson = useAsyncResource<Lesson>(\n () => dataSource.getLesson(item.id),\n [dataSource, item.id],\n );\n\n const lockedByServer =\n lesson.status === \"error\" &&\n lesson.error instanceof CourseError &&\n lesson.error.code === \"item_locked\";\n useEffect(() => {\n // The structure on screen is stale: take the locks from the server again.\n if (lockedByServer) onLocked();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [lockedByServer]);\n\n if (lesson.status === \"loading\") {\n return <LoadingBlock />;\n }\n if (lesson.status === \"error\") {\n return lockedByServer ? (\n <div className={cn(CARD, \"space-y-2\")} role=\"alert\">\n <h2 className=\"text-2xl font-semibold break-words\">{item.title}</h2>\n <p className=\"text-sm\">{errorMessage(lesson.error, texts)}</p>\n </div>\n ) : (\n <ErrorBox error={lesson.error} onRetry={lesson.reload} />\n );\n }\n\n const adjacent = getAdjacentItems(course, item.id);\n const position =\n flattenItems(course)\n .filter((entry) => entry.kind !== \"test\")\n .findIndex((entry) => entry.id === item.id) + 1;\n return (\n <LessonPane\n key={lesson.data.id}\n lesson={lesson.data}\n position={position}\n courseType={course.type}\n prev={lesson.data.prev ?? toRef(adjacent.prev)}\n next={lesson.data.next ?? toRef(adjacent.next)}\n autoplay={autoplay}\n onAutoplayChange={onAutoplayChange}\n startPlaying={startPlaying}\n onNavigate={onNavigate}\n onProgressSaved={onProgressSaved}\n onEnded={onEnded}\n onUrlsExpired={lesson.refresh}\n />\n );\n}\n\nfunction CourseView({\n course,\n selectedId,\n onSelect,\n onBack,\n onCourseChange,\n onCourseRefresh,\n}: {\n course: Course;\n selectedId?: string;\n onSelect: (itemId: string) => void;\n onBack?: () => void;\n onCourseChange: (update: (course: Course) => Course) => void;\n onCourseRefresh: () => void;\n}) {\n const { texts, preview } = useCourseView();\n const track = useCourseTrack();\n const rootRef = useRef<HTMLDivElement>(null);\n const [contentsOpen, setContentsOpen] = useState(false);\n const [autoplay, setAutoplay] = useAutoplayPreference();\n const [playOnOpen, setPlayOnOpen] = useState<string>();\n\n const currentId = resolveInitialItemId(course, selectedId);\n const item = findItem(course, currentId);\n\n // Once per opening of the course.\n useEffect(() => {\n track(\"course_opened\", {});\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [course.id]);\n\n const open = (itemId: string, play = false) => {\n setPlayOnOpen(play ? itemId : undefined);\n setContentsOpen(false);\n onSelect(itemId);\n rootRef.current?.scrollIntoView({ block: \"start\", behavior: \"smooth\" });\n };\n\n const outline = (\n <CourseOutline\n course={course}\n selectedId={currentId}\n variant={course.type === \"video\" ? \"playlist\" : \"modules\"}\n onSelect={(entry) => open(entry.id)}\n />\n );\n\n let content;\n if (item == null) {\n content = <p className=\"text-sm text-neutral-500\">{texts.noItem}</p>;\n } else if (item.locked) {\n content = <LockedPane item={item} />;\n } else if (item.kind === \"test\") {\n content = (\n <TestPane\n key={item.id}\n testId={item.id}\n title={item.title}\n onSubmitted={() => onCourseRefresh()}\n />\n );\n } else {\n content = (\n <LessonContainer\n key={item.id}\n course={course}\n item={item}\n autoplay={autoplay}\n onAutoplayChange={setAutoplay}\n startPlaying={playOnOpen === item.id}\n onNavigate={(ref) => open(ref.id)}\n onProgressSaved={(lessonId, saved) =>\n onCourseChange((current) => ({\n ...(saved.completed ? markLessonCompleted(current, lessonId) : current),\n completedLessons: saved.completedLessons,\n progressPercent: saved.progressPercent,\n }))\n }\n onEnded={() => {\n if (!autoplay) return;\n const target = getAutoplayTarget(course, item.id);\n if (target) open(target.id, true);\n }}\n onLocked={onCourseRefresh}\n />\n );\n }\n\n const isVideo = course.type === \"video\";\n const showResume =\n course.resumeItemId !== \"\" &&\n course.resumeItemId !== currentId &&\n findItem(course, course.resumeItemId)?.locked === false;\n\n return (\n <div ref={rootRef} className=\"space-y-4 scroll-mt-4\">\n <header className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n {preview && (\n <span\n data-course-preview\n className=\"rounded-full bg-amber-100 px-3 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900/40 dark:text-amber-200\"\n >\n {texts.previewBadge}\n </span>\n )}\n </div>\n <div className=\"flex flex-wrap items-end justify-between gap-4\">\n <div className=\"min-w-0 space-y-1\">\n <h1 className=\"text-2xl font-semibold break-words sm:text-3xl\">\n {course.title}\n </h1>\n </div>\n {showResume && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => open(course.resumeItemId)}>\n <Play aria-hidden />\n {texts.continueCourse}\n </Button>\n )}\n </div>\n <div className=\"max-w-xl space-y-1\">\n <ProgressBar\n percent={course.progressPercent}\n label={fillTemplate(texts.progressPercent, {\n percent: course.progressPercent,\n })}\n />\n <p className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {fillTemplate(texts.progressLessons, {\n done: course.completedLessons,\n total: course.totalLessons,\n })}\n {\" · \"}\n {fillTemplate(texts.progressPercent, { percent: course.progressPercent })}\n </p>\n </div>\n </header>\n\n <div\n className={cn(\n \"grid gap-6\",\n isVideo\n ? \"lg:grid-cols-[minmax(0,1fr)_380px]\"\n : \"lg:grid-cols-[320px_minmax(0,1fr)]\",\n )}\n >\n {!isVideo && (\n <div className=\"lg:hidden\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n aria-expanded={contentsOpen}\n onClick={() => setContentsOpen((value) => !value)}\n >\n <List aria-hidden />\n {texts.contents}\n </Button>\n </div>\n )}\n\n {!isVideo && (\n <aside\n className={cn(\n CARD,\n \"p-3 sm:p-3 lg:sticky lg:top-4 lg:block lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto\",\n contentsOpen ? \"block\" : \"hidden\",\n )}\n >\n {outline}\n </aside>\n )}\n\n <div className=\"min-w-0\" data-course-content>\n {content}\n </div>\n\n {isVideo && (\n <aside\n className={cn(\n CARD,\n \"p-3 sm:p-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto\",\n )}\n >\n {outline}\n </aside>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The course of a customer inside the panel: structure with progress and\n * locks, lessons with video, text and attachments, tests, questions and\n * ratings. Standard courses get a sidebar of modules, video courses a\n * playlist next to the player.\n *\n * Load it lazily from `bitsnap-react/course`. Inside the panel it needs only\n * `accessId` of the purchased course.\n */\nexport function PanelCourse({\n accessId,\n accessToken,\n dataSource: customDataSource,\n preview = false,\n itemId,\n onItemChange,\n onBack,\n initialCourse,\n texts: textOverrides,\n locale,\n theme,\n className,\n analytics,\n}: PanelCourseProps) {\n const panel = usePanelConfig();\n // The creator preview reports nothing, not even the page view of the page it sits on.\n const instrument = useInstrument(panel.projectID, preview ? false : analytics, \"course\");\n const texts = useMemo(\n () => getCourseTexts(textOverrides, locale),\n [textOverrides, locale],\n );\n const dataSource = useMemo(() => {\n if (customDataSource) return customDataSource;\n if (accessId == null || accessId === \"\") {\n throw new Error(\"PanelCourse needs an accessId or a dataSource\");\n }\n return createRpcCourseDataSource({ accessId, accessToken });\n }, [customDataSource, accessId, accessToken]);\n\n const course = useAsyncResource<Course>(\n () => dataSource.getCourse(),\n [dataSource],\n initialCourse,\n );\n\n const [internalItemId, setInternalItemId] = useState<string>();\n // With `onItemChange` the owner navigates, so no `itemId` means the default\n // item, not the one picked last.\n const selectedId = onItemChange ? itemId : (itemId ?? internalItemId);\n const select = useCallback(\n (next: string) => {\n setInternalItemId(next);\n onItemChange?.(next);\n },\n [onItemChange],\n );\n\n const style = accentVars(theme?.colors ?? panel.theme?.colors);\n\n const courseId = course.status === \"success\" ? course.data.id : undefined;\n const viewValue = useMemo(\n () => ({\n dataSource,\n texts,\n preview,\n analytics: { courseId, instrument },\n }),\n [dataSource, texts, preview, courseId, instrument],\n );\n\n return (\n <InstrumentProvider value={instrument}>\n <CourseViewProvider value={viewValue}>\n <div\n data-bitsnap-course\n data-course-type={course.status === \"success\" ? course.data.type : undefined}\n style={style}\n className={cn(\"font-inter w-full max-w-7xl\", className)}\n >\n {course.status === \"loading\" && (\n <div className=\"space-y-4\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n <p className=\"sr-only\" role=\"status\">\n {texts.loading}\n </p>\n <LoadingBlock />\n </div>\n )}\n {course.status === \"error\" && (\n <div className=\"space-y-4\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n <ErrorBox error={course.error} onRetry={course.reload} />\n </div>\n )}\n {course.status === \"success\" && (\n <CourseView\n course={course.data}\n selectedId={selectedId}\n onSelect={select}\n onBack={onBack}\n onCourseChange={course.mutate}\n onCourseRefresh={course.refresh}\n />\n )}\n </div>\n </CourseViewProvider>\n </InstrumentProvider>\n );\n}\n\nexport default PanelCourse;\n","import type { CSSProperties } from \"react\";\nimport { readableForeground } from \"../components/returns/color\";\n\n/**\n * CSS variables of the accent (brand) color of the course view. Without a\n * brand color nothing is set and the library's `--primary` applies.\n */\nexport function accentVars(colors?: {\n brand?: string;\n brandDark?: string;\n}): CSSProperties {\n const vars: Record<string, string> = {};\n const brand = colors?.brand?.trim();\n const brandDark = colors?.brandDark?.trim() || brand;\n if (brand) {\n vars[\"--course-accent\"] = brand;\n const foreground = readableForeground(brand);\n if (foreground) vars[\"--course-accent-fg\"] = foreground;\n }\n if (brandDark) {\n vars[\"--course-accent-dark\"] = brandDark;\n const foreground = readableForeground(brandDark);\n if (foreground) vars[\"--course-accent-dark-fg\"] = foreground;\n }\n return vars as CSSProperties;\n}\n","import { useCallback } from \"react\";\nimport type { EventProps } from \"@/src/analytics/contract\";\nimport { useCourseView } from \"./context\";\n\ntype CourseEventName =\n | \"course_opened\"\n | \"lesson_started\"\n | \"lesson_completed\"\n | \"video_progress\"\n | \"test_submitted\"\n | \"lesson_rated\";\n\n/**\n * Reports a course event; `course_id` is added. Nothing is reported in the\n * creator preview, before the course has loaded, or without analytics.\n */\nexport function useCourseTrack() {\n const { analytics, preview } = useCourseView();\n return useCallback(\n <E extends CourseEventName>(\n event: E,\n props: Omit<EventProps<E>, \"course_id\">,\n ) => {\n if (preview || analytics?.courseId == null) {\n return;\n }\n analytics.instrument.track(event, {\n ...props,\n course_id: analytics.courseId,\n } as unknown as EventProps<E>);\n },\n [analytics, preview],\n );\n}\n","import { createContext, useContext } from \"react\";\nimport type { Instrument } from \"@/src/analytics/instrument\";\nimport type { CourseTexts } from \"./texts\";\nimport type { CourseDataSource } from \"./types\";\n\nexport type CourseAnalytics = {\n /** `undefined` until the course has loaded. */\n courseId?: string;\n instrument: Instrument;\n};\n\nexport type CourseViewContextValue = {\n dataSource: CourseDataSource;\n texts: CourseTexts;\n /** Creator preview: nothing the viewer does is saved as progress. */\n preview: boolean;\n /** Usage statistics; none without it. */\n analytics?: CourseAnalytics;\n};\n\nconst CourseViewContext = createContext<CourseViewContextValue | undefined>(\n undefined,\n);\n\nexport const CourseViewProvider = CourseViewContext.Provider;\n\nexport function useCourseView(): CourseViewContextValue {\n const value = useContext(CourseViewContext);\n if (value == null) {\n throw new Error(\"Course components have to be rendered inside PanelCourse\");\n }\n return value;\n}\n","import {\n CheckCircle2,\n ClipboardCheck,\n Lock,\n PlayCircle,\n FileText,\n} from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\nimport { useCourseView } from \"./context\";\nimport { fillTemplate, formatDuration } from \"./logic\";\nimport type { Course, CourseItem } from \"./types\";\n\n\ntype CourseOutlineProps = {\n course: Course;\n selectedId?: string;\n onSelect: (item: CourseItem) => void;\n /**\n * `playlist` (video courses) shows the thumbnail and duration of every\n * video; `modules` (standard courses) is a plain list of lessons.\n */\n variant: \"modules\" | \"playlist\";\n className?: string;\n};\n\nfunction ItemIcon({ item }: { item: CourseItem }) {\n if (item.locked) {\n return <Lock className=\"size-4 text-neutral-500\" aria-hidden />;\n }\n if (item.completed) {\n return (\n <CheckCircle2\n className=\"size-4 text-emerald-600 dark:text-emerald-400\"\n aria-hidden\n />\n );\n }\n if (item.kind === \"test\") {\n return <ClipboardCheck className=\"size-4 text-neutral-500\" aria-hidden />;\n }\n return item.durationSeconds > 0 ? (\n <PlayCircle className=\"size-4 text-neutral-500\" aria-hidden />\n ) : (\n <FileText className=\"size-4 text-neutral-500\" aria-hidden />\n );\n}\n\nfunction OutlineItem({\n item,\n selected,\n variant,\n onSelect,\n}: {\n item: CourseItem;\n selected: boolean;\n variant: \"modules\" | \"playlist\";\n onSelect: (item: CourseItem) => void;\n}) {\n const { texts } = useCourseView();\n const lockText = item.lockReason\n ? fillTemplate(texts.lockedByTest, { test: item.lockReason.gatingTestTitle })\n : texts.locked;\n const state = item.locked\n ? lockText\n : item.completed\n ? texts.completed\n : undefined;\n\n return (\n <li>\n <button\n type=\"button\"\n data-item-id={item.id}\n data-locked={item.locked || undefined}\n aria-current={selected ? \"true\" : undefined}\n aria-disabled={item.locked || undefined}\n title={item.locked ? lockText : undefined}\n onClick={() => {\n if (!item.locked) onSelect(item);\n }}\n className={cn(\n \"flex w-full items-start gap-3 rounded-lg p-2 text-left text-sm outline-none transition focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n selected\n ? \"bg-neutral-200 dark:bg-neutral-700\"\n : \"hover:bg-neutral-100 dark:hover:bg-neutral-800\",\n item.locked && \"cursor-not-allowed opacity-60 hover:bg-transparent dark:hover:bg-transparent\",\n )}\n >\n {variant === \"playlist\" && item.kind === \"lesson\" && (\n <span className=\"relative aspect-video w-24 shrink-0 overflow-hidden rounded bg-neutral-200 dark:bg-neutral-800\">\n {item.thumbnailUrl && (\n <img\n src={item.thumbnailUrl}\n alt=\"\"\n loading=\"lazy\"\n draggable={false}\n className=\"size-full object-cover\"\n />\n )}\n {item.durationSeconds > 0 && (\n <span className=\"absolute right-1 bottom-1 rounded bg-black/75 px-1 text-[10px] text-white\">\n {formatDuration(item.durationSeconds)}\n </span>\n )}\n </span>\n )}\n {(variant === \"modules\" || item.kind === \"test\") && (\n <span className=\"mt-0.5 shrink-0\">\n <ItemIcon item={item} />\n </span>\n )}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block font-medium break-words\">{item.title}</span>\n <span className=\"mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-neutral-500 dark:text-neutral-400\">\n {item.kind === \"test\" && <span>{texts.testBadge}</span>}\n {item.isFree && !item.locked && <span>{texts.free}</span>}\n {variant === \"modules\" && item.durationSeconds > 0 && (\n <span>{formatDuration(item.durationSeconds)}</span>\n )}\n {state && <span>{state}</span>}\n </span>\n </span>\n {variant === \"playlist\" && item.kind === \"lesson\" && (\n <span className=\"mt-0.5 shrink-0\">\n <ItemIcon item={item} />\n </span>\n )}\n </button>\n </li>\n );\n}\n\n/** Modules / sections with their lessons and tests. */\nexport function CourseOutline({\n course,\n selectedId,\n onSelect,\n variant,\n className,\n}: CourseOutlineProps) {\n const { texts } = useCourseView();\n\n const renderItems = (items: CourseItem[]) => (\n <ul className=\"flex flex-col gap-1\">\n {items.map((item) => (\n <OutlineItem\n key={item.id}\n item={item}\n selected={item.id === selectedId}\n variant={variant}\n onSelect={onSelect}\n />\n ))}\n </ul>\n );\n\n return (\n <nav aria-label={texts.contents} className={cn(\"space-y-4\", className)}>\n {course.sections.map((section) => {\n const lessons = section.items.filter((i) => i.kind === \"lesson\");\n const done = lessons.filter((i) => i.completed).length;\n return (\n <section key={section.id} data-section-id={section.id}>\n <h3 className=\"mb-1 flex items-baseline justify-between gap-2 px-2 text-sm font-semibold\">\n <span className=\"break-words\">{section.title}</span>\n {lessons.length > 0 && (\n <span className=\"shrink-0 text-xs font-normal text-neutral-500 dark:text-neutral-400\">\n {done}/{lessons.length}\n </span>\n )}\n </h3>\n {renderItems(section.items)}\n </section>\n );\n })}\n {course.finalItems.length > 0 && (\n <section data-section-id=\"final\">{renderItems(course.finalItems)}</section>\n )}\n </nav>\n );\n}\n\n","import type {\n Course,\n CourseItem,\n QuestionAnswer,\n TestAnswers,\n TestQuestion,\n TestQuestionConfig,\n} from \"./types\";\n\n/** Playback share from which a lesson counts as watched. */\nexport const COMPLETE_AT_FRACTION = 0.8;\n\n/** How often the player reports the position while it plays. */\nexport const PROGRESS_SAVE_INTERVAL_MS = 15_000;\n\n/** An item together with the section it sits in (empty for final tests). */\nexport type FlatItem = CourseItem & { sectionId: string };\n\n/** All items of the course in the order they are taken. */\nexport function flattenItems(course: Course): FlatItem[] {\n return [\n ...course.sections.flatMap((section) =>\n section.items.map((item) => ({ ...item, sectionId: section.id })),\n ),\n ...course.finalItems.map((item) => ({ ...item, sectionId: \"\" })),\n ];\n}\n\nexport function findItem(\n course: Course,\n itemId: string | undefined,\n): FlatItem | undefined {\n if (itemId == null || itemId === \"\") {\n return undefined;\n }\n return flattenItems(course).find((item) => item.id === itemId);\n}\n\nexport function getAdjacentItems(\n course: Course,\n itemId: string,\n): { prev?: FlatItem; next?: FlatItem } {\n const items = flattenItems(course);\n const index = items.findIndex((item) => item.id === itemId);\n if (index < 0) {\n return {};\n }\n return { prev: items[index - 1], next: items[index + 1] };\n}\n\n/**\n * The item to start autoplay with after `itemId` ended: the next item when it\n * is an unlocked lesson. A test or a locked lesson stops the playlist.\n */\nexport function getAutoplayTarget(\n course: Course,\n itemId: string,\n): FlatItem | undefined {\n const { next } = getAdjacentItems(course, itemId);\n return next != null && next.kind === \"lesson\" && !next.locked\n ? next\n : undefined;\n}\n\n/** Progress counted from the lessons of the structure (tests do not count). */\nexport function computeProgress(course: Pick<Course, \"sections\">): {\n total: number;\n completed: number;\n percent: number;\n} {\n let total = 0;\n let completed = 0;\n for (const section of course.sections) {\n for (const item of section.items) {\n if (item.kind !== \"lesson\") {\n continue;\n }\n total += 1;\n if (item.completed) {\n completed += 1;\n }\n }\n }\n return {\n total,\n completed,\n percent: total === 0 ? 0 : Math.round((completed / total) * 100),\n };\n}\n\n/** The course after a lesson was completed, progress recomputed. */\nexport function markLessonCompleted(course: Course, lessonId: string): Course {\n const sections = course.sections.map((section) => ({\n ...section,\n items: section.items.map((item) =>\n item.id === lessonId && item.kind === \"lesson\"\n ? { ...item, completed: true }\n : item,\n ),\n }));\n const progress = computeProgress({ sections });\n return {\n ...course,\n sections,\n totalLessons: progress.total,\n completedLessons: progress.completed,\n progressPercent: progress.percent,\n };\n}\n\n/**\n * Item to show first: the requested one when it exists, else the one to\n * resume with, else the first unlocked item.\n */\nexport function resolveInitialItemId(\n course: Course,\n requestedId?: string,\n): string | undefined {\n if (findItem(course, requestedId) != null) {\n return requestedId;\n }\n if (findItem(course, course.resumeItemId)?.locked === false) {\n return course.resumeItemId;\n }\n return flattenItems(course).find((item) => !item.locked)?.id;\n}\n\n/** `{name}` placeholders of a text. Unknown placeholders stay untouched. */\nexport function fillTemplate(\n template: string,\n values: Record<string, string | number>,\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) =>\n key in values ? String(values[key]) : match,\n );\n}\n\nexport function isPlaybackComplete(\n positionSeconds: number,\n durationSeconds: number,\n): boolean {\n return (\n durationSeconds > 0 &&\n positionSeconds / durationSeconds >= COMPLETE_AT_FRACTION\n );\n}\n\n/** Whether a periodic save is due. */\nexport function isSaveDue(\n lastSavedAt: number | undefined,\n now: number,\n intervalMs: number = PROGRESS_SAVE_INTERVAL_MS,\n): boolean {\n return lastSavedAt == null || now - lastSavedAt >= intervalMs;\n}\n\n/** Where to resume: nothing when the video was (nearly) watched to the end. */\nexport function resumePosition(\n positionSeconds: number,\n durationSeconds: number,\n): number {\n if (positionSeconds < 5) {\n return 0;\n }\n if (durationSeconds > 0 && positionSeconds >= durationSeconds - 5) {\n return 0;\n }\n return Math.floor(positionSeconds);\n}\n\n/** `m:ss`, or `h:mm:ss` from an hour on. */\nexport function formatDuration(totalSeconds: number): string {\n const seconds = Math.max(0, Math.floor(totalSeconds));\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = String(seconds % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${s}` : `${m}:${s}`;\n}\n\nexport function formatBytes(bytes: number): string {\n if (!(bytes > 0)) {\n return \"0 B\";\n }\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\n const exponent = Math.min(\n Math.floor(Math.log(bytes) / Math.log(1024)),\n units.length - 1,\n );\n const value = bytes / 1024 ** exponent;\n return `${exponent === 0 || value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[exponent]}`;\n}\n\n// Tests\n\n/** The empty answer of a question, with the order of sort items as given. */\nexport function emptyAnswer(config: TestQuestionConfig): QuestionAnswer {\n switch (config.type) {\n case \"mcq\":\n return { type: \"mcq\", selectedIds: [] };\n case \"todolist\":\n return { type: \"todolist\", checkedIds: [] };\n case \"fill_gap\":\n return { type: \"fill_gap\", values: {} };\n case \"sort_order\":\n return {\n type: \"sort_order\",\n orderedIds: config.items.map((item) => item.id),\n };\n }\n}\n\n/** Whether the customer did anything with the question. */\nexport function isAnswered(\n question: TestQuestion,\n answer: QuestionAnswer | undefined,\n): boolean {\n if (answer == null || answer.type !== question.config.type) {\n return false;\n }\n switch (answer.type) {\n case \"mcq\":\n return answer.selectedIds.length > 0;\n case \"todolist\":\n return answer.checkedIds.length > 0;\n case \"fill_gap\":\n return Object.values(answer.values).some((value) => value.trim() !== \"\");\n case \"sort_order\":\n // The view stores a sort answer once the customer moved an item.\n return answer.orderedIds.length > 0;\n }\n}\n\nexport function countUnanswered(\n questions: TestQuestion[],\n answers: TestAnswers,\n): number {\n return questions.filter((q) => !isAnswered(q, answers[q.id])).length;\n}\n\n/** Moves the element at `from` to `to`; out of range moves change nothing. */\nexport function moveItem<T>(list: readonly T[], from: number, to: number): T[] {\n if (\n from === to ||\n from < 0 ||\n to < 0 ||\n from >= list.length ||\n to >= list.length\n ) {\n return [...list];\n }\n const copy = [...list];\n const [item] = copy.splice(from, 1);\n copy.splice(to, 0, item!);\n return copy;\n}\n\nexport type GapSegment =\n | { type: \"text\"; text: string }\n | { type: \"blank\"; id: string };\n\n/**\n * Splits a fill-in-the-gap template (`Paris is in {{g1}}`) into text and\n * blanks. Placeholders that are not in `blanks` stay text.\n */\nexport function splitGapTemplate(\n template: string,\n blanks: readonly string[],\n): GapSegment[] {\n const segments: GapSegment[] = [];\n const pattern = /\\{\\{\\s*([^{}\\s]+)\\s*\\}\\}/g;\n let last = 0;\n for (const match of template.matchAll(pattern)) {\n const id = match[1]!;\n if (!blanks.includes(id)) {\n continue;\n }\n if (match.index > last) {\n segments.push({ type: \"text\", text: template.slice(last, match.index) });\n }\n segments.push({ type: \"blank\", id });\n last = match.index + match[0].length;\n }\n if (last < template.length) {\n segments.push({ type: \"text\", text: template.slice(last) });\n }\n return segments;\n}\n\n/** A readable form of an answer, used on the result screen. */\nexport function describeAnswer(\n question: TestQuestion,\n answer: QuestionAnswer | undefined,\n): string {\n const { config } = question;\n if (answer == null || answer.type !== config.type) {\n return \"\";\n }\n const label = (list: { id: string; label: string }[], id: string) =>\n list.find((entry) => entry.id === id)?.label ?? id;\n\n if (config.type === \"mcq\" && answer.type === \"mcq\") {\n return answer.selectedIds.map((id) => label(config.choices, id)).join(\", \");\n }\n if (config.type === \"todolist\" && answer.type === \"todolist\") {\n return answer.checkedIds.map((id) => label(config.steps, id)).join(\", \");\n }\n if (config.type === \"sort_order\" && answer.type === \"sort_order\") {\n return answer.orderedIds.map((id) => label(config.items, id)).join(\" → \");\n }\n if (config.type === \"fill_gap\" && answer.type === \"fill_gap\") {\n return splitGapTemplate(config.template, config.blanks)\n .map((segment) =>\n segment.type === \"text\"\n ? segment.text\n : `[${answer.values[segment.id] ?? \"\"}]`,\n )\n .join(\"\");\n }\n return \"\";\n}\n"]}
1
+ {"version":3,"sources":["/home/emems/.t3/worktrees/bitsnap-react/cart-payment-method/dist/course.js","../src/course/PanelCourse.tsx","../src/course/accent.ts","../src/course/analytics.ts","../src/course/context.tsx","../src/course/CourseOutline.tsx","../src/course/logic.ts"],"names":["accentVars","colors","vars","brand","brandDark","foreground","readableForeground","CourseViewContext","createContext","CourseViewProvider","useCourseView","value","useContext","useCourseTrack","analytics","preview","useCallback","event","props","flattenItems","course","section","item","findItem","itemId","getAdjacentItems","items","index","getAutoplayTarget","next","computeProgress","total","completed","markLessonCompleted","lessonId","sections","progress","resolveInitialItemId","requestedId","fillTemplate","template","values","match","key","isPlaybackComplete","positionSeconds","durationSeconds","resumePosition","formatDuration","totalSeconds","seconds","h","m","s"],"mappings":"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","file":"/home/emems/.t3/worktrees/bitsnap-react/cart-payment-method/dist/course.js","sourcesContent":[null,"import { ChevronLeft, List, Play } from \"lucide-react\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport {\n InstrumentProvider,\n useInstrument,\n type AnalyticsOption,\n} from \"../analytics/instrument\";\nimport { usePanelConfig } from \"../panel/PanelProvider\";\nimport type { GetThemeOutput } from \"../panel/types\";\nimport { cn } from \"../lib/utils\";\nimport { Button } from \"../ui/button\";\nimport { accentVars } from \"./accent\";\nimport { useCourseTrack } from \"./analytics\";\nimport { CourseViewProvider, useCourseView } from \"./context\";\nimport { CourseOutline } from \"./CourseOutline\";\nimport { createRpcCourseDataSource } from \"./rpc\";\nimport { LessonPane } from \"./LessonPane\";\nimport {\n fillTemplate,\n findItem,\n flattenItems,\n getAdjacentItems,\n getAutoplayTarget,\n markLessonCompleted,\n resolveInitialItemId,\n type FlatItem,\n} from \"./logic\";\nimport { getCourseTexts, type CourseTexts } from \"./texts\";\nimport { TestPane } from \"./TestPane\";\nimport {\n CourseError,\n type Course,\n type CourseDataSource,\n type ItemRef,\n type Lesson,\n type ProgressSaved,\n} from \"./types\";\nimport { CARD, ErrorBox, LoadingBlock, ProgressBar, errorMessage } from \"./ui\";\nimport { useAsyncResource } from \"./useAsyncResource\";\n\nexport type PanelCourseProps = {\n /** `UserProduct.access_id` of the purchased course. Not needed with `dataSource`. */\n accessId?: string;\n /** Panel session token; read from the panel session when omitted. */\n accessToken?: string;\n /**\n * Replaces the default data source (`UserPanelCourseService` through the\n * panel's transport). The creator preview of the dashboard passes its own.\n */\n dataSource?: CourseDataSource;\n /** Creator preview: shows a badge and never saves progress. */\n preview?: boolean;\n /** The open lesson or test. Controlled together with `onItemChange`. */\n itemId?: string;\n onItemChange?: (itemId: string) => void;\n /** Shows a back button. */\n onBack?: () => void;\n /** Course structure to render right away (server rendering, preview). */\n initialCourse?: Course;\n /** Text overrides; Polish and English defaults by `locale`. */\n texts?: Partial<CourseTexts>;\n /** Defaults to the locale of the library (`setLocale`). */\n locale?: string;\n /** Brand colors; the panel theme (`setTheme`) by default. */\n theme?: GetThemeOutput;\n className?: string;\n /**\n * Usage statistics for your Bitsnap dashboard (course opened, lessons\n * started and completed, video progress, tests, ratings). On by default;\n * `false` turns it off, and the creator `preview` never reports. Inside\n * `<Panel>` the panel's setting applies unless this is given. An object\n * lets you forward every event: `{ onEvent }`. Nothing is stored on the\n * visitor's device.\n */\n analytics?: AnalyticsOption;\n};\n\nconst AUTOPLAY_KEY = \"bitsnap-course-autoplay\";\n\nfunction useAutoplayPreference(): [boolean, (value: boolean) => void] {\n const [value, setValue] = useState(false);\n useEffect(() => {\n try {\n setValue(window.localStorage.getItem(AUTOPLAY_KEY) === \"1\");\n } catch {\n // Storage is not available (private mode); keep the default.\n }\n }, []);\n const update = useCallback((next: boolean) => {\n setValue(next);\n try {\n window.localStorage.setItem(AUTOPLAY_KEY, next ? \"1\" : \"0\");\n } catch {\n // Not persisted.\n }\n }, []);\n return [value, update];\n}\n\nfunction toRef(item: FlatItem | undefined): ItemRef | undefined {\n return item == null ? undefined : { id: item.id, kind: item.kind };\n}\n\nfunction LockedPane({ item }: { item: FlatItem }) {\n const { texts } = useCourseView();\n return (\n <div className={cn(CARD, \"space-y-2\")} role=\"status\">\n <h2 className=\"text-2xl font-semibold break-words\">{item.title}</h2>\n <p className=\"text-sm text-neutral-600 dark:text-neutral-300\">\n {item.lockReason\n ? fillTemplate(texts.lockedPage, { test: item.lockReason.gatingTestTitle })\n : texts.locked}\n </p>\n </div>\n );\n}\n\ntype LessonContainerProps = {\n course: Course;\n item: FlatItem;\n autoplay: boolean;\n onAutoplayChange: (value: boolean) => void;\n startPlaying: boolean;\n onNavigate: (ref: ItemRef) => void;\n onProgressSaved: (lessonId: string, saved: ProgressSaved) => void;\n onEnded: () => void;\n onLocked: () => void;\n};\n\nfunction LessonContainer({\n course,\n item,\n autoplay,\n onAutoplayChange,\n startPlaying,\n onNavigate,\n onProgressSaved,\n onEnded,\n onLocked,\n}: LessonContainerProps) {\n const { dataSource, texts } = useCourseView();\n const lesson = useAsyncResource<Lesson>(\n () => dataSource.getLesson(item.id),\n [dataSource, item.id],\n );\n\n const lockedByServer =\n lesson.status === \"error\" &&\n lesson.error instanceof CourseError &&\n lesson.error.code === \"item_locked\";\n useEffect(() => {\n // The structure on screen is stale: take the locks from the server again.\n if (lockedByServer) onLocked();\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [lockedByServer]);\n\n if (lesson.status === \"loading\") {\n return <LoadingBlock />;\n }\n if (lesson.status === \"error\") {\n return lockedByServer ? (\n <div className={cn(CARD, \"space-y-2\")} role=\"alert\">\n <h2 className=\"text-2xl font-semibold break-words\">{item.title}</h2>\n <p className=\"text-sm\">{errorMessage(lesson.error, texts)}</p>\n </div>\n ) : (\n <ErrorBox error={lesson.error} onRetry={lesson.reload} />\n );\n }\n\n const adjacent = getAdjacentItems(course, item.id);\n const position =\n flattenItems(course)\n .filter((entry) => entry.kind !== \"test\")\n .findIndex((entry) => entry.id === item.id) + 1;\n return (\n <LessonPane\n key={lesson.data.id}\n lesson={lesson.data}\n position={position}\n courseType={course.type}\n prev={lesson.data.prev ?? toRef(adjacent.prev)}\n next={lesson.data.next ?? toRef(adjacent.next)}\n autoplay={autoplay}\n onAutoplayChange={onAutoplayChange}\n startPlaying={startPlaying}\n onNavigate={onNavigate}\n onProgressSaved={onProgressSaved}\n onEnded={onEnded}\n onUrlsExpired={lesson.refresh}\n />\n );\n}\n\nfunction CourseView({\n course,\n selectedId,\n onSelect,\n onBack,\n onCourseChange,\n onCourseRefresh,\n}: {\n course: Course;\n selectedId?: string;\n onSelect: (itemId: string) => void;\n onBack?: () => void;\n onCourseChange: (update: (course: Course) => Course) => void;\n onCourseRefresh: () => void;\n}) {\n const { texts, preview } = useCourseView();\n const track = useCourseTrack();\n const rootRef = useRef<HTMLDivElement>(null);\n const [contentsOpen, setContentsOpen] = useState(false);\n const [autoplay, setAutoplay] = useAutoplayPreference();\n const [playOnOpen, setPlayOnOpen] = useState<string>();\n\n const currentId = resolveInitialItemId(course, selectedId);\n const item = findItem(course, currentId);\n\n // Once per opening of the course.\n useEffect(() => {\n track(\"course_opened\", {});\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [course.id]);\n\n const open = (itemId: string, play = false) => {\n setPlayOnOpen(play ? itemId : undefined);\n setContentsOpen(false);\n onSelect(itemId);\n rootRef.current?.scrollIntoView({ block: \"start\", behavior: \"smooth\" });\n };\n\n const outline = (\n <CourseOutline\n course={course}\n selectedId={currentId}\n variant={course.type === \"video\" ? \"playlist\" : \"modules\"}\n onSelect={(entry) => open(entry.id)}\n />\n );\n\n let content;\n if (item == null) {\n content = <p className=\"text-sm text-neutral-500\">{texts.noItem}</p>;\n } else if (item.locked) {\n content = <LockedPane item={item} />;\n } else if (item.kind === \"test\") {\n content = (\n <TestPane\n key={item.id}\n testId={item.id}\n title={item.title}\n onSubmitted={() => onCourseRefresh()}\n />\n );\n } else {\n content = (\n <LessonContainer\n key={item.id}\n course={course}\n item={item}\n autoplay={autoplay}\n onAutoplayChange={setAutoplay}\n startPlaying={playOnOpen === item.id}\n onNavigate={(ref) => open(ref.id)}\n onProgressSaved={(lessonId, saved) =>\n onCourseChange((current) => ({\n ...(saved.completed ? markLessonCompleted(current, lessonId) : current),\n completedLessons: saved.completedLessons,\n progressPercent: saved.progressPercent,\n }))\n }\n onEnded={() => {\n if (!autoplay) return;\n const target = getAutoplayTarget(course, item.id);\n if (target) open(target.id, true);\n }}\n onLocked={onCourseRefresh}\n />\n );\n }\n\n const isVideo = course.type === \"video\";\n const showResume =\n course.resumeItemId !== \"\" &&\n course.resumeItemId !== currentId &&\n findItem(course, course.resumeItemId)?.locked === false;\n\n return (\n <div ref={rootRef} className=\"space-y-4 scroll-mt-4\">\n <header className=\"space-y-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n {preview && (\n <span\n data-course-preview\n className=\"rounded-full bg-amber-100 px-3 py-0.5 text-xs font-semibold text-amber-900 dark:bg-amber-900/40 dark:text-amber-200\"\n >\n {texts.previewBadge}\n </span>\n )}\n </div>\n <div className=\"flex flex-wrap items-end justify-between gap-4\">\n <div className=\"min-w-0 space-y-1\">\n <h1 className=\"text-2xl font-semibold break-words sm:text-3xl\">\n {course.title}\n </h1>\n </div>\n {showResume && (\n <Button size=\"sm\" variant=\"outline\" onClick={() => open(course.resumeItemId)}>\n <Play aria-hidden />\n {texts.continueCourse}\n </Button>\n )}\n </div>\n <div className=\"max-w-xl space-y-1\">\n <ProgressBar\n percent={course.progressPercent}\n label={fillTemplate(texts.progressPercent, {\n percent: course.progressPercent,\n })}\n />\n <p className=\"text-xs text-neutral-500 dark:text-neutral-400\">\n {fillTemplate(texts.progressLessons, {\n done: course.completedLessons,\n total: course.totalLessons,\n })}\n {\" · \"}\n {fillTemplate(texts.progressPercent, { percent: course.progressPercent })}\n </p>\n </div>\n </header>\n\n <div\n className={cn(\n \"grid gap-6\",\n isVideo\n ? \"lg:grid-cols-[minmax(0,1fr)_380px]\"\n : \"lg:grid-cols-[320px_minmax(0,1fr)]\",\n )}\n >\n {!isVideo && (\n <div className=\"lg:hidden\">\n <Button\n variant=\"outline\"\n size=\"sm\"\n aria-expanded={contentsOpen}\n onClick={() => setContentsOpen((value) => !value)}\n >\n <List aria-hidden />\n {texts.contents}\n </Button>\n </div>\n )}\n\n {!isVideo && (\n <aside\n className={cn(\n CARD,\n \"p-3 sm:p-3 lg:sticky lg:top-4 lg:block lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto\",\n contentsOpen ? \"block\" : \"hidden\",\n )}\n >\n {outline}\n </aside>\n )}\n\n <div className=\"min-w-0\" data-course-content>\n {content}\n </div>\n\n {isVideo && (\n <aside\n className={cn(\n CARD,\n \"p-3 sm:p-3 lg:sticky lg:top-4 lg:max-h-[calc(100vh-2rem)] lg:self-start lg:overflow-y-auto\",\n )}\n >\n {outline}\n </aside>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * The course of a customer inside the panel: structure with progress and\n * locks, lessons with video, text and attachments, tests, questions and\n * ratings. Standard courses get a sidebar of modules, video courses a\n * playlist next to the player.\n *\n * Load it lazily from `bitsnap-react/course`. Inside the panel it needs only\n * `accessId` of the purchased course.\n */\nexport function PanelCourse({\n accessId,\n accessToken,\n dataSource: customDataSource,\n preview = false,\n itemId,\n onItemChange,\n onBack,\n initialCourse,\n texts: textOverrides,\n locale,\n theme,\n className,\n analytics,\n}: PanelCourseProps) {\n const panel = usePanelConfig();\n // The creator preview reports nothing, not even the page view of the page it sits on.\n const instrument = useInstrument(panel.projectID, preview ? false : analytics, \"course\");\n const texts = useMemo(\n () => getCourseTexts(textOverrides, locale),\n [textOverrides, locale],\n );\n const dataSource = useMemo(() => {\n if (customDataSource) return customDataSource;\n if (accessId == null || accessId === \"\") {\n throw new Error(\"PanelCourse needs an accessId or a dataSource\");\n }\n return createRpcCourseDataSource({ accessId, accessToken });\n }, [customDataSource, accessId, accessToken]);\n\n const course = useAsyncResource<Course>(\n () => dataSource.getCourse(),\n [dataSource],\n initialCourse,\n );\n\n const [internalItemId, setInternalItemId] = useState<string>();\n // With `onItemChange` the owner navigates, so no `itemId` means the default\n // item, not the one picked last.\n const selectedId = onItemChange ? itemId : (itemId ?? internalItemId);\n const select = useCallback(\n (next: string) => {\n setInternalItemId(next);\n onItemChange?.(next);\n },\n [onItemChange],\n );\n\n const style = accentVars(theme?.colors ?? panel.theme?.colors);\n\n const courseId = course.status === \"success\" ? course.data.id : undefined;\n const viewValue = useMemo(\n () => ({\n dataSource,\n texts,\n preview,\n analytics: { courseId, instrument },\n }),\n [dataSource, texts, preview, courseId, instrument],\n );\n\n return (\n <InstrumentProvider value={instrument}>\n <CourseViewProvider value={viewValue}>\n <div\n data-bitsnap-course\n data-course-type={course.status === \"success\" ? course.data.type : undefined}\n style={style}\n className={cn(\"font-inter w-full max-w-7xl\", className)}\n >\n {course.status === \"loading\" && (\n <div className=\"space-y-4\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n <p className=\"sr-only\" role=\"status\">\n {texts.loading}\n </p>\n <LoadingBlock />\n </div>\n )}\n {course.status === \"error\" && (\n <div className=\"space-y-4\">\n {onBack && (\n <Button variant=\"ghost\" size=\"sm\" onClick={onBack}>\n <ChevronLeft aria-hidden />\n {texts.back}\n </Button>\n )}\n <ErrorBox error={course.error} onRetry={course.reload} />\n </div>\n )}\n {course.status === \"success\" && (\n <CourseView\n course={course.data}\n selectedId={selectedId}\n onSelect={select}\n onBack={onBack}\n onCourseChange={course.mutate}\n onCourseRefresh={course.refresh}\n />\n )}\n </div>\n </CourseViewProvider>\n </InstrumentProvider>\n );\n}\n\nexport default PanelCourse;\n","import type { CSSProperties } from \"react\";\nimport { readableForeground } from \"../components/returns/color\";\n\n/**\n * CSS variables of the accent (brand) color of the course view. Without a\n * brand color nothing is set and the library's `--primary` applies.\n */\nexport function accentVars(colors?: {\n brand?: string;\n brandDark?: string;\n}): CSSProperties {\n const vars: Record<string, string> = {};\n const brand = colors?.brand?.trim();\n const brandDark = colors?.brandDark?.trim() || brand;\n if (brand) {\n vars[\"--course-accent\"] = brand;\n const foreground = readableForeground(brand);\n if (foreground) vars[\"--course-accent-fg\"] = foreground;\n }\n if (brandDark) {\n vars[\"--course-accent-dark\"] = brandDark;\n const foreground = readableForeground(brandDark);\n if (foreground) vars[\"--course-accent-dark-fg\"] = foreground;\n }\n return vars as CSSProperties;\n}\n","import { useCallback } from \"react\";\nimport type { EventProps } from \"@/src/analytics/contract\";\nimport { useCourseView } from \"./context\";\n\ntype CourseEventName =\n | \"course_opened\"\n | \"lesson_started\"\n | \"lesson_completed\"\n | \"video_progress\"\n | \"test_submitted\"\n | \"lesson_rated\";\n\n/**\n * Reports a course event; `course_id` is added. Nothing is reported in the\n * creator preview, before the course has loaded, or without analytics.\n */\nexport function useCourseTrack() {\n const { analytics, preview } = useCourseView();\n return useCallback(\n <E extends CourseEventName>(\n event: E,\n props: Omit<EventProps<E>, \"course_id\">,\n ) => {\n if (preview || analytics?.courseId == null) {\n return;\n }\n analytics.instrument.track(event, {\n ...props,\n course_id: analytics.courseId,\n } as unknown as EventProps<E>);\n },\n [analytics, preview],\n );\n}\n","import { createContext, useContext } from \"react\";\nimport type { Instrument } from \"@/src/analytics/instrument\";\nimport type { CourseTexts } from \"./texts\";\nimport type { CourseDataSource } from \"./types\";\n\nexport type CourseAnalytics = {\n /** `undefined` until the course has loaded. */\n courseId?: string;\n instrument: Instrument;\n};\n\nexport type CourseViewContextValue = {\n dataSource: CourseDataSource;\n texts: CourseTexts;\n /** Creator preview: nothing the viewer does is saved as progress. */\n preview: boolean;\n /** Usage statistics; none without it. */\n analytics?: CourseAnalytics;\n};\n\nconst CourseViewContext = createContext<CourseViewContextValue | undefined>(\n undefined,\n);\n\nexport const CourseViewProvider = CourseViewContext.Provider;\n\nexport function useCourseView(): CourseViewContextValue {\n const value = useContext(CourseViewContext);\n if (value == null) {\n throw new Error(\"Course components have to be rendered inside PanelCourse\");\n }\n return value;\n}\n","import {\n CheckCircle2,\n ClipboardCheck,\n Lock,\n PlayCircle,\n FileText,\n} from \"lucide-react\";\nimport { cn } from \"../lib/utils\";\nimport { useCourseView } from \"./context\";\nimport { fillTemplate, formatDuration } from \"./logic\";\nimport type { Course, CourseItem } from \"./types\";\n\n\ntype CourseOutlineProps = {\n course: Course;\n selectedId?: string;\n onSelect: (item: CourseItem) => void;\n /**\n * `playlist` (video courses) shows the thumbnail and duration of every\n * video; `modules` (standard courses) is a plain list of lessons.\n */\n variant: \"modules\" | \"playlist\";\n className?: string;\n};\n\nfunction ItemIcon({ item }: { item: CourseItem }) {\n if (item.locked) {\n return <Lock className=\"size-4 text-neutral-500\" aria-hidden />;\n }\n if (item.completed) {\n return (\n <CheckCircle2\n className=\"size-4 text-emerald-600 dark:text-emerald-400\"\n aria-hidden\n />\n );\n }\n if (item.kind === \"test\") {\n return <ClipboardCheck className=\"size-4 text-neutral-500\" aria-hidden />;\n }\n return item.durationSeconds > 0 ? (\n <PlayCircle className=\"size-4 text-neutral-500\" aria-hidden />\n ) : (\n <FileText className=\"size-4 text-neutral-500\" aria-hidden />\n );\n}\n\nfunction OutlineItem({\n item,\n selected,\n variant,\n onSelect,\n}: {\n item: CourseItem;\n selected: boolean;\n variant: \"modules\" | \"playlist\";\n onSelect: (item: CourseItem) => void;\n}) {\n const { texts } = useCourseView();\n const lockText = item.lockReason\n ? fillTemplate(texts.lockedByTest, { test: item.lockReason.gatingTestTitle })\n : texts.locked;\n const state = item.locked\n ? lockText\n : item.completed\n ? texts.completed\n : undefined;\n\n return (\n <li>\n <button\n type=\"button\"\n data-item-id={item.id}\n data-locked={item.locked || undefined}\n aria-current={selected ? \"true\" : undefined}\n aria-disabled={item.locked || undefined}\n title={item.locked ? lockText : undefined}\n onClick={() => {\n if (!item.locked) onSelect(item);\n }}\n className={cn(\n \"flex w-full items-start gap-3 rounded-lg p-2 text-left text-sm outline-none transition focus-visible:ring-[3px] focus-visible:ring-ring/50\",\n selected\n ? \"bg-neutral-200 dark:bg-neutral-700\"\n : \"hover:bg-neutral-100 dark:hover:bg-neutral-800\",\n item.locked && \"cursor-not-allowed opacity-60 hover:bg-transparent dark:hover:bg-transparent\",\n )}\n >\n {variant === \"playlist\" && item.kind === \"lesson\" && (\n <span className=\"relative aspect-video w-24 shrink-0 overflow-hidden rounded bg-neutral-200 dark:bg-neutral-800\">\n {item.thumbnailUrl && (\n <img\n src={item.thumbnailUrl}\n alt=\"\"\n loading=\"lazy\"\n draggable={false}\n className=\"size-full object-cover\"\n />\n )}\n {item.durationSeconds > 0 && (\n <span className=\"absolute right-1 bottom-1 rounded bg-black/75 px-1 text-[10px] text-white\">\n {formatDuration(item.durationSeconds)}\n </span>\n )}\n </span>\n )}\n {(variant === \"modules\" || item.kind === \"test\") && (\n <span className=\"mt-0.5 shrink-0\">\n <ItemIcon item={item} />\n </span>\n )}\n <span className=\"min-w-0 flex-1\">\n <span className=\"block font-medium break-words\">{item.title}</span>\n <span className=\"mt-0.5 flex flex-wrap items-center gap-x-2 text-xs text-neutral-500 dark:text-neutral-400\">\n {item.kind === \"test\" && <span>{texts.testBadge}</span>}\n {item.isFree && !item.locked && <span>{texts.free}</span>}\n {variant === \"modules\" && item.durationSeconds > 0 && (\n <span>{formatDuration(item.durationSeconds)}</span>\n )}\n {state && <span>{state}</span>}\n </span>\n </span>\n {variant === \"playlist\" && item.kind === \"lesson\" && (\n <span className=\"mt-0.5 shrink-0\">\n <ItemIcon item={item} />\n </span>\n )}\n </button>\n </li>\n );\n}\n\n/** Modules / sections with their lessons and tests. */\nexport function CourseOutline({\n course,\n selectedId,\n onSelect,\n variant,\n className,\n}: CourseOutlineProps) {\n const { texts } = useCourseView();\n\n const renderItems = (items: CourseItem[]) => (\n <ul className=\"flex flex-col gap-1\">\n {items.map((item) => (\n <OutlineItem\n key={item.id}\n item={item}\n selected={item.id === selectedId}\n variant={variant}\n onSelect={onSelect}\n />\n ))}\n </ul>\n );\n\n return (\n <nav aria-label={texts.contents} className={cn(\"space-y-4\", className)}>\n {course.sections.map((section) => {\n const lessons = section.items.filter((i) => i.kind === \"lesson\");\n const done = lessons.filter((i) => i.completed).length;\n return (\n <section key={section.id} data-section-id={section.id}>\n <h3 className=\"mb-1 flex items-baseline justify-between gap-2 px-2 text-sm font-semibold\">\n <span className=\"break-words\">{section.title}</span>\n {lessons.length > 0 && (\n <span className=\"shrink-0 text-xs font-normal text-neutral-500 dark:text-neutral-400\">\n {done}/{lessons.length}\n </span>\n )}\n </h3>\n {renderItems(section.items)}\n </section>\n );\n })}\n {course.finalItems.length > 0 && (\n <section data-section-id=\"final\">{renderItems(course.finalItems)}</section>\n )}\n </nav>\n );\n}\n\n","import type {\n Course,\n CourseItem,\n QuestionAnswer,\n TestAnswers,\n TestQuestion,\n TestQuestionConfig,\n} from \"./types\";\n\n/** Playback share from which a lesson counts as watched. */\nexport const COMPLETE_AT_FRACTION = 0.8;\n\n/** How often the player reports the position while it plays. */\nexport const PROGRESS_SAVE_INTERVAL_MS = 15_000;\n\n/** An item together with the section it sits in (empty for final tests). */\nexport type FlatItem = CourseItem & { sectionId: string };\n\n/** All items of the course in the order they are taken. */\nexport function flattenItems(course: Course): FlatItem[] {\n return [\n ...course.sections.flatMap((section) =>\n section.items.map((item) => ({ ...item, sectionId: section.id })),\n ),\n ...course.finalItems.map((item) => ({ ...item, sectionId: \"\" })),\n ];\n}\n\nexport function findItem(\n course: Course,\n itemId: string | undefined,\n): FlatItem | undefined {\n if (itemId == null || itemId === \"\") {\n return undefined;\n }\n return flattenItems(course).find((item) => item.id === itemId);\n}\n\nexport function getAdjacentItems(\n course: Course,\n itemId: string,\n): { prev?: FlatItem; next?: FlatItem } {\n const items = flattenItems(course);\n const index = items.findIndex((item) => item.id === itemId);\n if (index < 0) {\n return {};\n }\n return { prev: items[index - 1], next: items[index + 1] };\n}\n\n/**\n * The item to start autoplay with after `itemId` ended: the next item when it\n * is an unlocked lesson. A test or a locked lesson stops the playlist.\n */\nexport function getAutoplayTarget(\n course: Course,\n itemId: string,\n): FlatItem | undefined {\n const { next } = getAdjacentItems(course, itemId);\n return next != null && next.kind === \"lesson\" && !next.locked\n ? next\n : undefined;\n}\n\n/** Progress counted from the lessons of the structure (tests do not count). */\nexport function computeProgress(course: Pick<Course, \"sections\">): {\n total: number;\n completed: number;\n percent: number;\n} {\n let total = 0;\n let completed = 0;\n for (const section of course.sections) {\n for (const item of section.items) {\n if (item.kind !== \"lesson\") {\n continue;\n }\n total += 1;\n if (item.completed) {\n completed += 1;\n }\n }\n }\n return {\n total,\n completed,\n percent: total === 0 ? 0 : Math.round((completed / total) * 100),\n };\n}\n\n/** The course after a lesson was completed, progress recomputed. */\nexport function markLessonCompleted(course: Course, lessonId: string): Course {\n const sections = course.sections.map((section) => ({\n ...section,\n items: section.items.map((item) =>\n item.id === lessonId && item.kind === \"lesson\"\n ? { ...item, completed: true }\n : item,\n ),\n }));\n const progress = computeProgress({ sections });\n return {\n ...course,\n sections,\n totalLessons: progress.total,\n completedLessons: progress.completed,\n progressPercent: progress.percent,\n };\n}\n\n/**\n * Item to show first: the requested one when it exists, else the one to\n * resume with, else the first unlocked item.\n */\nexport function resolveInitialItemId(\n course: Course,\n requestedId?: string,\n): string | undefined {\n if (findItem(course, requestedId) != null) {\n return requestedId;\n }\n if (findItem(course, course.resumeItemId)?.locked === false) {\n return course.resumeItemId;\n }\n return flattenItems(course).find((item) => !item.locked)?.id;\n}\n\n/** `{name}` placeholders of a text. Unknown placeholders stay untouched. */\nexport function fillTemplate(\n template: string,\n values: Record<string, string | number>,\n): string {\n return template.replace(/\\{(\\w+)\\}/g, (match, key: string) =>\n key in values ? String(values[key]) : match,\n );\n}\n\nexport function isPlaybackComplete(\n positionSeconds: number,\n durationSeconds: number,\n): boolean {\n return (\n durationSeconds > 0 &&\n positionSeconds / durationSeconds >= COMPLETE_AT_FRACTION\n );\n}\n\n/** Whether a periodic save is due. */\nexport function isSaveDue(\n lastSavedAt: number | undefined,\n now: number,\n intervalMs: number = PROGRESS_SAVE_INTERVAL_MS,\n): boolean {\n return lastSavedAt == null || now - lastSavedAt >= intervalMs;\n}\n\n/** Where to resume: nothing when the video was (nearly) watched to the end. */\nexport function resumePosition(\n positionSeconds: number,\n durationSeconds: number,\n): number {\n if (positionSeconds < 5) {\n return 0;\n }\n if (durationSeconds > 0 && positionSeconds >= durationSeconds - 5) {\n return 0;\n }\n return Math.floor(positionSeconds);\n}\n\n/** `m:ss`, or `h:mm:ss` from an hour on. */\nexport function formatDuration(totalSeconds: number): string {\n const seconds = Math.max(0, Math.floor(totalSeconds));\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = String(seconds % 60).padStart(2, \"0\");\n return h > 0 ? `${h}:${String(m).padStart(2, \"0\")}:${s}` : `${m}:${s}`;\n}\n\nexport function formatBytes(bytes: number): string {\n if (!(bytes > 0)) {\n return \"0 B\";\n }\n const units = [\"B\", \"KB\", \"MB\", \"GB\"];\n const exponent = Math.min(\n Math.floor(Math.log(bytes) / Math.log(1024)),\n units.length - 1,\n );\n const value = bytes / 1024 ** exponent;\n return `${exponent === 0 || value >= 10 ? Math.round(value) : value.toFixed(1)} ${units[exponent]}`;\n}\n\n// Tests\n\n/** The empty answer of a question, with the order of sort items as given. */\nexport function emptyAnswer(config: TestQuestionConfig): QuestionAnswer {\n switch (config.type) {\n case \"mcq\":\n return { type: \"mcq\", selectedIds: [] };\n case \"todolist\":\n return { type: \"todolist\", checkedIds: [] };\n case \"fill_gap\":\n return { type: \"fill_gap\", values: {} };\n case \"sort_order\":\n return {\n type: \"sort_order\",\n orderedIds: config.items.map((item) => item.id),\n };\n }\n}\n\n/** Whether the customer did anything with the question. */\nexport function isAnswered(\n question: TestQuestion,\n answer: QuestionAnswer | undefined,\n): boolean {\n if (answer == null || answer.type !== question.config.type) {\n return false;\n }\n switch (answer.type) {\n case \"mcq\":\n return answer.selectedIds.length > 0;\n case \"todolist\":\n return answer.checkedIds.length > 0;\n case \"fill_gap\":\n return Object.values(answer.values).some((value) => value.trim() !== \"\");\n case \"sort_order\":\n // The view stores a sort answer once the customer moved an item.\n return answer.orderedIds.length > 0;\n }\n}\n\nexport function countUnanswered(\n questions: TestQuestion[],\n answers: TestAnswers,\n): number {\n return questions.filter((q) => !isAnswered(q, answers[q.id])).length;\n}\n\n/** Moves the element at `from` to `to`; out of range moves change nothing. */\nexport function moveItem<T>(list: readonly T[], from: number, to: number): T[] {\n if (\n from === to ||\n from < 0 ||\n to < 0 ||\n from >= list.length ||\n to >= list.length\n ) {\n return [...list];\n }\n const copy = [...list];\n const [item] = copy.splice(from, 1);\n copy.splice(to, 0, item!);\n return copy;\n}\n\nexport type GapSegment =\n | { type: \"text\"; text: string }\n | { type: \"blank\"; id: string };\n\n/**\n * Splits a fill-in-the-gap template (`Paris is in {{g1}}`) into text and\n * blanks. Placeholders that are not in `blanks` stay text.\n */\nexport function splitGapTemplate(\n template: string,\n blanks: readonly string[],\n): GapSegment[] {\n const segments: GapSegment[] = [];\n const pattern = /\\{\\{\\s*([^{}\\s]+)\\s*\\}\\}/g;\n let last = 0;\n for (const match of template.matchAll(pattern)) {\n const id = match[1]!;\n if (!blanks.includes(id)) {\n continue;\n }\n if (match.index > last) {\n segments.push({ type: \"text\", text: template.slice(last, match.index) });\n }\n segments.push({ type: \"blank\", id });\n last = match.index + match[0].length;\n }\n if (last < template.length) {\n segments.push({ type: \"text\", text: template.slice(last) });\n }\n return segments;\n}\n\n/** A readable form of an answer, used on the result screen. */\nexport function describeAnswer(\n question: TestQuestion,\n answer: QuestionAnswer | undefined,\n): string {\n const { config } = question;\n if (answer == null || answer.type !== config.type) {\n return \"\";\n }\n const label = (list: { id: string; label: string }[], id: string) =>\n list.find((entry) => entry.id === id)?.label ?? id;\n\n if (config.type === \"mcq\" && answer.type === \"mcq\") {\n return answer.selectedIds.map((id) => label(config.choices, id)).join(\", \");\n }\n if (config.type === \"todolist\" && answer.type === \"todolist\") {\n return answer.checkedIds.map((id) => label(config.steps, id)).join(\", \");\n }\n if (config.type === \"sort_order\" && answer.type === \"sort_order\") {\n return answer.orderedIds.map((id) => label(config.items, id)).join(\" → \");\n }\n if (config.type === \"fill_gap\" && answer.type === \"fill_gap\") {\n return splitGapTemplate(config.template, config.blanks)\n .map((segment) =>\n segment.type === \"text\"\n ? segment.text\n : `[${answer.values[segment.id] ?? \"\"}]`,\n )\n .join(\"\");\n }\n return \"\";\n}\n"]}