@flamingo-stack/openframe-frontend-core 0.0.581 → 0.0.582-1960.5386.1
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.
- package/dist/{chunk-R5JVRFSW.js → chunk-4KWAHIYA.js} +3 -3
- package/dist/{chunk-KAEVE7W2.js → chunk-646JEAY4.js} +2 -2
- package/dist/{chunk-GAHKWML7.js → chunk-6EUYXDV3.js} +6 -6
- package/dist/{chunk-HVSZYUGP.js → chunk-7HHRJTIY.js} +5 -5
- package/dist/{chunk-JW53WPB5.js → chunk-A3SVEK7A.js} +3 -3
- package/dist/{chunk-6U45VX5J.cjs → chunk-A5VD6R7N.cjs} +43 -43
- package/dist/{chunk-6U45VX5J.cjs.map → chunk-A5VD6R7N.cjs.map} +1 -1
- package/dist/{chunk-BTKMTVN5.cjs → chunk-ALSLHGBH.cjs} +4 -4
- package/dist/{chunk-BTKMTVN5.cjs.map → chunk-ALSLHGBH.cjs.map} +1 -1
- package/dist/{chunk-SEZ25E22.cjs → chunk-BQPCCBGO.cjs} +10 -10
- package/dist/{chunk-SEZ25E22.cjs.map → chunk-BQPCCBGO.cjs.map} +1 -1
- package/dist/{chunk-LWDIDSCF.js → chunk-BUOYMIFJ.js} +3 -3
- package/dist/{chunk-3GXNCJZI.js → chunk-CR7GPMCM.js} +3 -3
- package/dist/{chunk-U3W2ZVN4.cjs → chunk-DGZPX2G3.cjs} +159 -159
- package/dist/{chunk-U3W2ZVN4.cjs.map → chunk-DGZPX2G3.cjs.map} +1 -1
- package/dist/{chunk-PBRXT7TD.cjs → chunk-DQVM4FTL.cjs} +10 -10
- package/dist/{chunk-PBRXT7TD.cjs.map → chunk-DQVM4FTL.cjs.map} +1 -1
- package/dist/{chunk-HJVKPG55.js → chunk-F227VMPL.js} +125 -15
- package/dist/{chunk-HJVKPG55.js.map → chunk-F227VMPL.js.map} +1 -1
- package/dist/{chunk-EZVABFEO.js → chunk-FRWQ67UM.js} +2 -2
- package/dist/{chunk-DSPQVZFE.cjs → chunk-FYVL6A4Z.cjs} +24 -24
- package/dist/{chunk-DSPQVZFE.cjs.map → chunk-FYVL6A4Z.cjs.map} +1 -1
- package/dist/{chunk-VXUUSLD5.js → chunk-GD3OAM4T.js} +2 -2
- package/dist/{chunk-ESWXXWCL.js → chunk-HQF7VE7B.js} +2 -2
- package/dist/{chunk-VDRCKHML.cjs → chunk-J5I3TKAH.cjs} +3 -3
- package/dist/{chunk-VDRCKHML.cjs.map → chunk-J5I3TKAH.cjs.map} +1 -1
- package/dist/{chunk-GUC4JK6O.cjs → chunk-K5SCFOD5.cjs} +9 -9
- package/dist/{chunk-GUC4JK6O.cjs.map → chunk-K5SCFOD5.cjs.map} +1 -1
- package/dist/{chunk-UKHM2S6I.cjs → chunk-KAOXOYRT.cjs} +15 -15
- package/dist/{chunk-UKHM2S6I.cjs.map → chunk-KAOXOYRT.cjs.map} +1 -1
- package/dist/{chunk-IHFQ5VVR.js → chunk-KBOBPKMU.js} +5 -5
- package/dist/{chunk-GMQEHDN2.cjs → chunk-L4E2TOSF.cjs} +5 -5
- package/dist/{chunk-GMQEHDN2.cjs.map → chunk-L4E2TOSF.cjs.map} +1 -1
- package/dist/{chunk-YU2UJPAT.cjs → chunk-LOJ5KQ6B.cjs} +466 -356
- package/dist/chunk-LOJ5KQ6B.cjs.map +1 -0
- package/dist/{chunk-M4KQWGEL.cjs → chunk-ORQQZQBB.cjs} +32 -32
- package/dist/{chunk-M4KQWGEL.cjs.map → chunk-ORQQZQBB.cjs.map} +1 -1
- package/dist/{chunk-ZEZMGXYV.js → chunk-QDSVTIO5.js} +5 -5
- package/dist/{chunk-KNBXZ22O.cjs → chunk-R3J2ERH2.cjs} +3 -3
- package/dist/{chunk-KNBXZ22O.cjs.map → chunk-R3J2ERH2.cjs.map} +1 -1
- package/dist/{chunk-WHFEG6UU.cjs → chunk-RMOUILHT.cjs} +22 -22
- package/dist/{chunk-WHFEG6UU.cjs.map → chunk-RMOUILHT.cjs.map} +1 -1
- package/dist/{chunk-OKA3DJJ3.js → chunk-SKGMU7TZ.js} +2 -2
- package/dist/{chunk-7CP5KY5W.js → chunk-SVQVAGZK.js} +3 -3
- package/dist/{chunk-W2TXSZTM.cjs → chunk-T32XVOFP.cjs} +21 -21
- package/dist/{chunk-W2TXSZTM.cjs.map → chunk-T32XVOFP.cjs.map} +1 -1
- package/dist/{chunk-GVM37TRU.js → chunk-U2ZRM3YB.js} +4 -4
- package/dist/{chunk-AIALG25H.cjs → chunk-WQ236GO3.cjs} +69 -69
- package/dist/{chunk-AIALG25H.cjs.map → chunk-WQ236GO3.cjs.map} +1 -1
- package/dist/components/case-studies/index.cjs +9 -9
- package/dist/components/case-studies/index.js +3 -3
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/contact/index.cjs +4 -4
- package/dist/components/contact/index.js +3 -3
- package/dist/components/docs/index.cjs +7 -7
- package/dist/components/docs/index.js +6 -6
- package/dist/components/embeds/index.cjs +4 -4
- package/dist/components/embeds/index.js +3 -3
- package/dist/components/faq/index.cjs +4 -4
- package/dist/components/faq/index.js +3 -3
- package/dist/components/features/index.cjs +5 -3
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +4 -2
- package/dist/components/features/video.d.ts +12 -0
- package/dist/components/features/video.d.ts.map +1 -1
- package/dist/components/help-center-pages/index.cjs +23 -23
- package/dist/components/help-center-pages/index.js +14 -14
- package/dist/components/index.cjs +123 -121
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +15 -13
- package/dist/components/index.js.map +1 -1
- package/dist/components/meeting-scheduler/index.cjs +21 -21
- package/dist/components/meeting-scheduler/index.js +2 -2
- package/dist/components/navigation/index.cjs +6 -6
- package/dist/components/navigation/index.js +5 -5
- package/dist/components/onboarding-guides/index.cjs +6 -6
- package/dist/components/onboarding-guides/index.js +5 -5
- package/dist/components/tickets/index.cjs +7 -7
- package/dist/components/tickets/index.js +6 -6
- package/dist/components/ui/index.cjs +3 -3
- package/dist/components/ui/index.js +2 -2
- package/dist/index.cjs +9 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +8 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/features/__tests__/video-playback-failure.test.tsx +250 -0
- package/src/components/features/video.tsx +198 -16
- package/dist/chunk-YU2UJPAT.cjs.map +0 -1
- /package/dist/{chunk-R5JVRFSW.js.map → chunk-4KWAHIYA.js.map} +0 -0
- /package/dist/{chunk-KAEVE7W2.js.map → chunk-646JEAY4.js.map} +0 -0
- /package/dist/{chunk-GAHKWML7.js.map → chunk-6EUYXDV3.js.map} +0 -0
- /package/dist/{chunk-HVSZYUGP.js.map → chunk-7HHRJTIY.js.map} +0 -0
- /package/dist/{chunk-JW53WPB5.js.map → chunk-A3SVEK7A.js.map} +0 -0
- /package/dist/{chunk-LWDIDSCF.js.map → chunk-BUOYMIFJ.js.map} +0 -0
- /package/dist/{chunk-3GXNCJZI.js.map → chunk-CR7GPMCM.js.map} +0 -0
- /package/dist/{chunk-EZVABFEO.js.map → chunk-FRWQ67UM.js.map} +0 -0
- /package/dist/{chunk-VXUUSLD5.js.map → chunk-GD3OAM4T.js.map} +0 -0
- /package/dist/{chunk-ESWXXWCL.js.map → chunk-HQF7VE7B.js.map} +0 -0
- /package/dist/{chunk-IHFQ5VVR.js.map → chunk-KBOBPKMU.js.map} +0 -0
- /package/dist/{chunk-ZEZMGXYV.js.map → chunk-QDSVTIO5.js.map} +0 -0
- /package/dist/{chunk-OKA3DJJ3.js.map → chunk-SKGMU7TZ.js.map} +0 -0
- /package/dist/{chunk-7CP5KY5W.js.map → chunk-SVQVAGZK.js.map} +0 -0
- /package/dist/{chunk-GVM37TRU.js.map → chunk-U2ZRM3YB.js.map} +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-M4KQWGEL.cjs","../src/components/docs/doc-viewer.tsx","../src/components/loading/unified-skeleton.tsx","../src/components/loading/card-skeleton.tsx","../src/components/loading/page-layout-skeleton.tsx","../src/components/persistent-filter-controls.tsx","../src/components/docs/doc-navigation-context.tsx","../src/components/docs/use-docs-resolve-link.ts","../src/components/docs/use-document-tree.ts","../src/components/docs/use-scroll-spy.ts","../src/components/docs/skeletons.tsx","../src/components/docs/docs-hub-page.tsx"],"names":["jsx","jsxs","Fragment","useCallback","readString","useRef","pathParts","currentPath","useState","useMemo"],"mappings":"AAAA,6xBAAY;AACZ;AACE;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;AC1CA,4EAAwB;AD4CxB;AACA;AEoBI,+CAAA;AAjCG,SAAS,eAAA,CAAgB;AAAA,EAC9B,SAAA;AAAA,EACA,QAAA,EAAU,SAAA;AAAA,EACV,KAAA;AAAA,EACA,MAAA;AAAA,EACA,QAAA,EAAU,IAAA;AAAA,EACV,YAAA,EAAc,SAAA;AAAA,EACd,GAAG;AACL,CAAA,EAAyB;AACvB,EAAA,MAAM,YAAA,EAAc,iBAAA;AAEpB,EAAA,MAAM,eAAA,EAAiB;AAAA,IACrB,OAAA,EAAS,YAAA;AAAA,IACT,IAAA,EAAM,YAAA;AAAA,IACN,QAAA,EAAU,cAAA;AAAA,IACV,WAAA,EAAa;AAAA,EACf,CAAA;AAEA,EAAA,MAAM,eAAA,EAAiB;AAAA,IACrB,OAAA,EAAS,KAAA;AAAA,IACT,IAAA,EAAM,KAAA;AAAA,IACN,QAAA,EAAU,WAAA;AAAA,IACV,WAAA,EAAa;AAAA,EACf,CAAA;AAEA,EAAA,MAAM,iBAAA,EAAmB,QAAA,EAAU,2CAAA,EAA6C,EAAA;AAEhF,EAAA,MAAM,MAAA,EAA6B;AAAA,IACjC,KAAA;AAAA,IACA;AAAA,EACF,CAAA;AAEA,EAAA,uBACE,6BAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,WAAA;AAAA,QACA,cAAA,CAAe,OAAO,CAAA;AAAA,QACtB,CAAC,OAAA,GAAU,CAAC,KAAA,CAAM,OAAA,GAAU,cAAA,CAAe,OAAO,CAAA;AAAA,QAClD,CAAC,MAAA,GAAS,CAAC,KAAA,CAAM,MAAA,GAAS,QAAA,IAAY,WAAA,GAAc,QAAA;AAAA,QACpD,gBAAA;AAAA,QACA;AAAA,MACF,CAAA;AAAA,MACA,KAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MACL,YAAA,EAAY,UAAA,GAAa,iBAAA;AAAA,MACxB,GAAG;AAAA,IAAA;AAAA,EACN,CAAA;AAEJ;AAKO,IAAM,aAAA,EAAe;AAAA;AAAA;AAAA;AAAA,EAI1B,OAAA,EAAS,CAAC,EAAE,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,mBAC9B,6BAAA;AAAA,IAAC,eAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,MAAA;AAAA,MACR,SAAA,EAAW,kCAAA,qBAAG,EAAuB,SAAS,CAAA;AAAA,MAC9C,YAAA,EAAW,iBAAA;AAAA,MACV,GAAG;AAAA,IAAA;AAAA,EACN,CAAA;AAAA;AAAA;AAAA;AAAA,EAMF,UAAA,EAAY,CAAC,EAAE,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,mBACjC,6BAAA;AAAA,IAAC,eAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,MAAA;AAAA,MACR,SAAA,EAAW,kCAAA,mBAAG,EAAqB,SAAS,CAAA;AAAA,MAC5C,YAAA,EAAW,oBAAA;AAAA,MACV,GAAG;AAAA,IAAA;AAAA,EACN,CAAA;AAAA;AAAA;AAAA;AAAA,EAMF,IAAA,EAAM,CAAC,EAAE,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,mBAC3B,6BAAA,eAAC,EAAA,EAAgB,OAAA,EAAQ,MAAA,EAAO,SAAA,EAAW,kCAAA,YAAG,EAAc,SAAS,CAAA,EAAG,YAAA,EAAW,cAAA,EAAgB,GAAG,MAAA,CAAO,CAAA;AAAA;AAAA;AAAA;AAAA,EAM/G,OAAA,EAAS,CAAC,EAAE,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,mBAC9B,6BAAA,eAAC,EAAA,EAAgB,OAAA,EAAQ,MAAA,EAAO,SAAA,EAAW,kCAAA,YAAG,EAAc,SAAS,CAAA,EAAG,YAAA,EAAW,iBAAA,EAAmB,GAAG,MAAA,CAAO;AAEpH,CAAA;AAKO,IAAM,oBAAA,EAAsB;AAAA;AAAA;AAAA;AAAA,EAIjC,MAAA,EAAQ,CAAC,EAAE,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,mBAC7B,6BAAA;AAAA,IAAC,eAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,SAAA;AAAA,MACR,SAAA,EAAW,kCAAA,2BAAG,EAA6B,SAAS,CAAA;AAAA,MACpD,YAAA,EAAW,gBAAA;AAAA,MACV,GAAG;AAAA,IAAA;AAAA,EACN,CAAA;AAAA;AAAA;AAAA;AAAA,EAMF,KAAA,EAAO,CAAC,EAAE,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,mBAC5B,6BAAA;AAAA,IAAC,eAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,SAAA;AAAA,MACR,SAAA,EAAW,kCAAA,qBAAG,EAAuB,SAAS,CAAA;AAAA,MAC9C,YAAA,EAAW,qBAAA;AAAA,MACV,GAAG;AAAA,IAAA;AAAA,EACN,CAAA;AAAA;AAAA;AAAA;AAAA,EAMF,IAAA,EAAM,CAAC,EAAE,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,mBAC3B,6BAAA;AAAA,IAAC,eAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,SAAA;AAAA,MACR,SAAA,EAAW,kCAAA,uBAAG,EAAyB,SAAS,CAAA;AAAA,MAChD,YAAA,EAAW,gBAAA;AAAA,MACV,GAAG;AAAA,IAAA;AAAA,EACN;AAEJ,CAAA;AAKO,IAAM,cAAA,EAAgB;AAAA;AAAA;AAAA;AAAA,EAI3B,MAAA,EAAQ,CAAC;AAAA,IACP,KAAA,EAAO,IAAA;AAAA,IACP,SAAA;AAAA,IACA,GAAG;AAAA,EACL,CAAA,EAAA,GAA6E;AAC3E,IAAA,MAAM,YAAA,EAAc;AAAA,MAClB,EAAA,EAAI,SAAA;AAAA,MACJ,EAAA,EAAI,WAAA;AAAA,MACJ,EAAA,EAAI;AAAA,IACN,CAAA;AAEA,IAAA,uBACE,6BAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QACC,OAAA,EAAQ,UAAA;AAAA,QACR,SAAA,EAAW,kCAAA,WAAG,CAAY,IAAI,CAAA,EAAG,SAAS,CAAA;AAAA,QAC1C,YAAA,EAAW,uBAAA;AAAA,QACV,GAAG;AAAA,MAAA;AAAA,IACN,CAAA;AAAA,EAEJ,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,SAAA,EAAW,CAAC,EAAE,SAAA,EAAW,GAAG,MAAM,CAAA,EAAA,mBAChC,6BAAA;AAAA,IAAC,eAAA;AAAA,IAAA;AAAA,MACC,OAAA,EAAQ,SAAA;AAAA,MACR,SAAA,EAAW,kCAAA,0BAAG,EAA4B,SAAS,CAAA;AAAA,MACnD,YAAA,EAAW,eAAA;AAAA,MACV,GAAG;AAAA,IAAA;AAAA,EACN,CAAA;AAAA;AAAA;AAAA;AAAA,EAMF,IAAA,EAAM,CAAC;AAAA,IACL,KAAA,EAAO,IAAA;AAAA,IACP,SAAA;AAAA,IACA,GAAG;AAAA,EACL,CAAA,EAAA,GAA6E;AAC3E,IAAA,MAAM,YAAA,EAAc;AAAA,MAClB,EAAA,EAAI,SAAA;AAAA,MACJ,EAAA,EAAI,SAAA;AAAA,MACJ,EAAA,EAAI;AAAA,IACN,CAAA;AAEA,IAAA,uBACE,6BAAA;AAAA,MAAC,eAAA;AAAA,MAAA;AAAA,QACC,OAAA,EAAQ,SAAA;AAAA,QACR,SAAA,EAAW,kCAAA,WAAG,CAAY,IAAI,CAAA,EAAG,SAAS,CAAA;AAAA,QAC1C,YAAA,EAAW,cAAA;AAAA,QACV,GAAG;AAAA,MAAA;AAAA,IACN,CAAA;AAAA,EAEJ;AACF,CAAA;AFLA;AACA;AG9LY;AATL,SAAS,YAAA,CAAa;AAAA,EAC3B,SAAA;AAAA,EACA,kBAAA;AAAA,EACA,QAAA,EAAU,QAAA;AAAA,EACV,YAAA,EAAc,IAAA;AAAA,EACd,aAAA,EAAe,IAAA;AAAA,EACf,GAAG;AACL,CAAA,EAAsB;AACpB,EAAA,MAAM,YAAA,EAAc;AAAA,IAClB,MAAA,kBAAQA,6BAAAA,iBAAC,EAAA,EAAkB,aAAA,CAA4B,CAAA;AAAA,IACvD,IAAA,kBAAMA,6BAAAA,eAAC,EAAA,EAAgB,WAAA,EAA0B,aAAA,CAA4B,CAAA;AAAA,IAC7E,QAAA,kBAAUA,6BAAAA,mBAAC,EAAA,CAAA,CAAoB,CAAA;AAAA,IAC/B,WAAA,kBAAaA,6BAAAA,sBAAC,EAAA,EAAuB,YAAA,CAA0B;AAAA,EACjE,CAAA;AAEA,EAAA,uBACEA,6BAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,mBAAA,GAAsB,sCAAA;AAAA,QACtB,4BAAA;AAAA;AAAA,QAEA,QAAA,IAAY,OAAA,GAAU,sBAAA;AAAA,QACtB,QAAA,IAAY,SAAA,GAAY,sBAAA;AAAA,QACxB;AAAA,MACF,CAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MACL,YAAA,EAAY,CAAA,QAAA,EAAW,OAAO,CAAA,KAAA,CAAA;AAAA,MAC7B,GAAG,KAAA;AAAA,MAEH,QAAA,EAAA,WAAA,CAAY,OAAO;AAAA,IAAA;AAAA,EACtB,CAAA;AAEJ;AAKA,SAAS,iBAAA,CAAkB,EAAE,aAAa,CAAA,EAA8B;AACtE,EAAA,uBACE,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yBAAA,EAEb,QAAA,EAAA;AAAA,oBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,+BAAA,EAEb,QAAA,EAAA;AAAA,sBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,oHAAA,EACb,QAAA,kBAAAA,6BAAAA,aAAC,CAAc,MAAA,EAAd,EAAqB,IAAA,EAAK,IAAA,EAAK,SAAA,EAAU,YAAA,CAAY,EAAA,CACxD,CAAA;AAAA,sBAGA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4DAAA,EAEb,QAAA,EAAA;AAAA,wBAAAA,6BAAAA,YAAC,CAAa,UAAA,EAAb,EAAwB,SAAA,EAAU,QAAA,CAAQ,CAAA;AAAA,wBAE3CA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,QAAA,CAAQ;AAAA,MAAA,EAAA,CAC1C;AAAA,IAAA,EAAA,CACF,CAAA;AAAA,oBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,+CAAA,EACb,QAAA,kBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,sBAAAA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,sBACtCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,QAAA,CAAQ;AAAA,IAAA,EAAA,CACvC,EAAA,CACF,CAAA;AAAA,IAGC,aAAA,mBACC,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wDAAA,EAEb,QAAA,EAAA;AAAA,sBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sDAAA,EAEb,QAAA,EAAA;AAAA,wBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uCAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,6BAAAA,aAAC,CAAc,IAAA,EAAd,EAAmB,IAAA,EAAK,IAAA,EAAK,SAAA,EAAU,UAAA,CAAU,CAAA;AAAA,0BAClDA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,MAAA,CAAM;AAAA,QAAA,EAAA,CACxC,CAAA;AAAA,wBAGA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uCAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,6BAAAA,aAAC,CAAc,IAAA,EAAd,EAAmB,IAAA,EAAK,IAAA,EAAK,SAAA,EAAU,UAAA,CAAU,CAAA;AAAA,0BAClDA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO;AAAA,QAAA,EAAA,CACzC;AAAA,MAAA,EAAA,CACF,CAAA;AAAA,sBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,eAAA,EACb,QAAA,kBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,oFAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iFAAA,EACb,QAAA,kBAAAA,6BAAAA,aAAC,CAAc,IAAA,EAAd,EAAmB,IAAA,EAAK,IAAA,EAAK,SAAA,EAAU,cAAA,CAAc,EAAA,CACxD,CAAA;AAAA,wBACAA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO;AAAA,MAAA,EAAA,CACzC,EAAA,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,EAAA,CAEJ,CAAA;AAEJ;AAKA,SAAS,eAAA,CAAgB,EAAE,WAAA,EAAa,aAAa,CAAA,EAAoD;AACvG,EAAA,uBACE,8BAAA,oBAAA,EAAA,EAEE,QAAA,EAAA;AAAA,oBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uFAAA,EACb,QAAA,kBAAAA,6BAAAA,aAAC,CAAc,SAAA,EAAd,CAAA,CAAwB,EAAA,CAC3B,CAAA;AAAA,oBAGA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,6BAAA,EAEb,QAAA,EAAA;AAAA,sBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yEAAA,EACb,QAAA,kBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,6BAAAA,YAAC,CAAa,UAAA,EAAb,EAAwB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,wBAC5CA,6BAAAA,YAAC,CAAa,UAAA,EAAb,EAAwB,SAAA,EAAU,QAAA,CAAQ;AAAA,MAAA,EAAA,CAC7C,EAAA,CACF,CAAA;AAAA,sBAGA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uCAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,6BAAAA,mBAAC,CAAoB,IAAA,EAApB,EAAyB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,wBAC3CA,6BAAAA,mBAAC,CAAoB,IAAA,EAApB,EAAyB,SAAA,EAAU,OAAA,CAAO;AAAA,MAAA,EAAA,CAC7C,CAAA;AAAA,sBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,EACb,QAAA,kBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,wBACtCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,QAAA,CAAQ;AAAA,MAAA,EAAA,CACvC,EAAA,CACF,CAAA;AAAA,MAGC,YAAA,mBACCA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,MAAA,EACb,QAAA,kBAAAA,6BAAAA,mBAAC,CAAoB,MAAA,EAApB,EAA2B,SAAA,EAAU,WAAA,CAAW,EAAA,CACnD,CAAA;AAAA,MAID,aAAA,mBACCA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,SAAA,EACb,QAAA,kBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yEAAA,EAEb,QAAA,EAAA;AAAA,wBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wCAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,6BAAAA,aAAC,CAAc,MAAA,EAAd,EAAqB,IAAA,EAAK,KAAA,CAAK,CAAA;AAAA,0BAChCA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO;AAAA,QAAA,EAAA,CACzC,CAAA;AAAA,wBAGA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sDAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,0BAEvCA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uCAAA,CAAuC,CAAA;AAAA,0BACtDA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO;AAAA,QAAA,EAAA,CACzC;AAAA,MAAA,EAAA,CACF,EAAA,CACF;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,EAAA,CACF,CAAA;AAEJ;AAKA,SAAS,mBAAA,CAAA,EAAsB;AAC7B,EAAA,uBACE,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EAEb,QAAA,EAAA;AAAA,oBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,KAAA,EAAA,mBACjCA,6BAAAA,aAAC,CAAc,IAAA,EAAd,EAA+B,IAAA,EAAK,IAAA,EAAK,SAAA,EAAU,gBAAA,CAAA,EAA3B,KAA2C,CACrE,EAAA,CACH,CAAA;AAAA,oBAGA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,sBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,6BAAAA,YAAC,CAAa,UAAA,EAAb,EAAwB,SAAA,EAAU,QAAA,CAAQ,CAAA;AAAA,wBAC3CA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,QAAA,CAAQ;AAAA,MAAA,EAAA,CAC1C,CAAA;AAAA,sBAEA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,8DAAA,EACb,QAAA,EAAA;AAAA,wBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,0BACtCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,QAAA,CAAQ;AAAA,QAAA,EAAA,CACvC,CAAA;AAAA,wBAEAA,6BAAAA,mBAAC,CAAoB,MAAA,EAApB,EAA2B,SAAA,EAAU,0CAAA,CAA0C;AAAA,MAAA,EAAA,CAClF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,EAAA,CACF,CAAA;AAEJ;AAKA,SAAS,sBAAA,CAAuB,EAAE,YAAY,CAAA,EAA6B;AACzE,EAAA,uBACE,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4BAAA,EACb,QAAA,EAAA;AAAA,oBAAAA,6BAAAA,aAAC,CAAc,MAAA,EAAd,EAAqB,IAAA,EAAK,IAAA,EAAK,SAAA,EAAU,gBAAA,CAAgB,CAAA;AAAA,oBAE1D,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,sBAAA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,6BAAAA,YAAC,CAAa,UAAA,EAAb,EAAwB,SAAA,EAAU,QAAA,CAAQ,CAAA;AAAA,wBAC3CA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO;AAAA,MAAA,EAAA,CACzC,CAAA;AAAA,sBAEA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,wBACtCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,QAAA,CAAQ;AAAA,MAAA,EAAA,CACvC,CAAA;AAAA,MAEC,YAAA,mBACCA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,MAAA,EACb,QAAA,kBAAAA,6BAAAA,mBAAC,CAAoB,MAAA,EAApB,EAA2B,SAAA,EAAU,WAAA,CAAW,EAAA,CACnD;AAAA,IAAA,EAAA,CAEJ;AAAA,EAAA,EAAA,CACF,CAAA;AAEJ;AAKO,SAAS,gBAAA,CAAiB;AAAA,EAC/B,MAAA,EAAQ,CAAA;AAAA,EACR,QAAA,EAAU,QAAA;AAAA,EACV,SAAA;AAAA,EACA,kBAAA;AAAA,EACA,GAAG;AACL,CAAA,EAKwC;AACtC,EAAA,uBACEA,6BAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,qBAAA;AAAA;AAAA,QAEA,QAAA,IAAY,SAAA,GAAY,2CAAA;AAAA,QACxB,QAAA,IAAY,OAAA,GAAU,4BAAA;AAAA,QACtB,QAAA,IAAY,WAAA,GAAc,2CAAA;AAAA,QAC1B,QAAA,IAAY,cAAA,GAAiB,aAAA;AAAA,QAC7B;AAAA,MACF,CAAA;AAAA,MACA,IAAA,EAAK,QAAA;AAAA,MACL,YAAA,EAAY,CAAA,QAAA,EAAW,KAAK,CAAA,CAAA,EAAI,OAAO,CAAA,MAAA,CAAA;AAAA,MAEtC,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,MAAM,CAAA,EAAG,CAAC,CAAA,EAAG,KAAA,EAAA,mBACjCA,6BAAAA,YAAC,EAAA,EAAyB,OAAA,EAAkB,kBAAA,EAAyC,GAAG,MAAA,CAAA,EAArE,KAA4E,CAChG;AAAA,IAAA;AAAA,EACH,CAAA;AAEJ;AH8FA;AACA;AI/WU;AANH,SAAS,uBAAA,CAAA,EAA0B;AACxC,EAAA,uBACEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,+CAAA,EACb,QAAA,kBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4CAAA,EACb,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,6HAAA,EAEb,QAAA,EAAA;AAAA,oBAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gEAAA,CAAgE,CAAA;AAAA,oBAG/EC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uCAAA,EACb,QAAA,EAAA;AAAA,sBAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4DAAA,CAA4D,CAAA;AAAA,sBAC3EA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,6EAAA,CAA6E;AAAA,IAAA,EAAA,CAC9F,CAAA;AAAA,oBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uDAAA,CAAuD;AAAA,EAAA,EAAA,CACxE,EAAA,CACF,EAAA,CACF,CAAA;AAEJ;AA2CO,SAAS,YAAA,CAAA,EAAe;AAC7B,EAAA,uBACEC,8BAAAA;AAAA,IAAC,SAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAU,wGAAA;AAAA,MACV,KAAA,EAAO;AAAA,QACL,UAAA,EAAY;AAAA,MACd,CAAA;AAAA,MAGA,QAAA,EAAA;AAAA,wBAAAD,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,qDAAA,CAAqD,CAAA;AAAA,wBAGrFC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,+CAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,aAAA,CAAa,CAAA;AAAA,0BAC1CA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,2BAAA,CAA2B;AAAA,QAAA,EAAA,CAC1D,CAAA;AAAA,wBAGAA,6BAAAA,mBAAC,CAAoB,MAAA,EAApB,EAA2B,SAAA,EAAU,sBAAA,CAAsB;AAAA,MAAA;AAAA,IAAA;AAAA,EAC9D,CAAA;AAEJ;AAKO,SAAS,uBAAA,CAAwB;AAAA,EACtC,SAAA;AAAA,EACA,YAAA,EAAc;AAChB,CAAA,EAAwD;AACtD,EAAA,uBACEC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAW,kCAAA,WAAG,EAAa,UAAA,GAAa,EAAE,CAAA,EAE7C,QAAA,EAAA;AAAA,oBAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,sBAAAD,6BAAAA,mBAAC,CAAoB,KAAA,EAApB,EAA0B,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,sBAC9CA,6BAAAA,mBAAC,CAAoB,MAAA,EAApB,EAA2B,SAAA,EAAU,eAAA,CAAe;AAAA,IAAA,EAAA,CACvD,CAAA;AAAA,IAGC,YAAA,mBACCC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EACb,QAAA,EAAA;AAAA,sBAAAD,6BAAAA,mBAAC,CAAoB,IAAA,EAApB,CAAA,CAAyB,CAAA;AAAA,sBAC1BA,6BAAAA,mBAAC,CAAoB,IAAA,EAApB,EAAyB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,sBAC3CA,6BAAAA,mBAAC,CAAoB,IAAA,EAApB,EAAyB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,sBAC3CA,6BAAAA,mBAAC,CAAoB,IAAA,EAApB,EAAyB,SAAA,EAAU,OAAA,CAAO;AAAA,IAAA,EAAA,CAC7C;AAAA,EAAA,EAAA,CAEJ,CAAA;AAEJ;AAKO,SAAS,uBAAA,CAAwB,EAAE,UAAU,CAAA,EAA4B;AAE9E,EAAA,MAAM,MAAA,EAAQ;AAAA,IACZ,EAAE,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,MAAM,CAAA;AAAA,IAC/C,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IAChD,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IAChD,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IAChD,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IAChD,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IAChD,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IAChD,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IAChD,EAAE,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,MAAA,EAAQ,QAAA,EAAU,KAAK,CAAA;AAAA,IAChD,EAAE,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,OAAO,CAAA;AAAA,IAC9B,EAAE,IAAA,EAAM,MAAA,EAAQ,KAAA,EAAO,OAAO;AAAA,EAChC,CAAA;AAEA,EAAA,uBACEC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAW,kCAAA,qBAAG,EAAuB,SAAS,CAAA,EAEjD,QAAA,EAAA;AAAA,oBAAAD,6BAAAA,eAAC,EAAA,EAAgB,SAAA,EAAU,6BAAA,CAA6B,CAAA;AAAA,oBAGxDA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,aAAA,EACZ,QAAA,EAAA,KAAA,CAAM,GAAA,CAAI,CAAC,IAAA,EAAM,KAAA,EAAA,mBAChBC,8BAAAA;AAAA,MAAC,KAAA;AAAA,MAAA;AAAA,QAEC,SAAA,EAAW,kCAAA;AAAA,UACT,8FAAA;AAAA,UACA,MAAA,IAAU,EAAA,EAAI,mBAAA,EAAqB;AAAA,QACrC,CAAA;AAAA,QAEA,QAAA,EAAA;AAAA,0BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,eAAC,EAAA,EAAgB,SAAA,EAAU,2BAAA,CAA2B,CAAA;AAAA,4BACtDA,6BAAAA,eAAC,EAAA,EAAgB,SAAA,EAAW,CAAA,SAAA,EAAY,IAAA,CAAK,KAAK,CAAA,QAAA,EAAA,CAAY;AAAA,UAAA,EAAA,CAChE,CAAA;AAAA,0BACAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kCAAA,EACZ,QAAA,EAAA;AAAA,YAAA,IAAA,CAAK,SAAA,mBAAYD,6BAAAA,eAAC,EAAA,EAAgB,SAAA,EAAU,wBAAA,CAAwB,CAAA;AAAA,YACpE,IAAA,CAAK,KAAA,IAAS,SAAA,mBAAYA,6BAAAA,eAAC,EAAA,EAAgB,SAAA,EAAU,kBAAA,CAAkB;AAAA,UAAA,EAAA,CAC1E;AAAA,QAAA;AAAA,MAAA,CAAA;AAAA,MAbK;AAAA,IAcP,CACD,EAAA,CACH;AAAA,EAAA,EAAA,CACF,CAAA;AAEJ;AAKO,SAAS,kBAAA,CAAmB,EAAE,UAAU,CAAA,EAA4B;AACzE,EAAA,uBACEC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAW,kCAAA,kCAAG,EAAoC,SAAS,CAAA,EAC9D,QAAA,EAAA;AAAA,oBAAAD,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,oBACvCA,6BAAAA,eAAC,EAAA,EAAgB,OAAA,EAAQ,SAAA,EAAU,SAAA,EAAU,uBAAA,CAAuB,CAAA;AAAA,oBACpEA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,oBACvCA,6BAAAA,eAAC,EAAA,EAAgB,OAAA,EAAQ,SAAA,EAAU,SAAA,EAAU,uBAAA,CAAuB,CAAA;AAAA,oBACpEA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,oBACvCA,6BAAAA,eAAC,EAAA,EAAgB,OAAA,EAAQ,SAAA,EAAU,SAAA,EAAU,uBAAA,CAAuB,CAAA;AAAA,oBACpEA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO;AAAA,EAAA,EAAA,CACzC,CAAA;AAEJ;AAKO,SAAS,qBAAA,CAAsB,EAAE,UAAU,CAAA,EAA4B;AAC5E,EAAA,uBACEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAW,kCAAA,kFAAG,EAAoF,SAAS,CAAA,EAC9G,QAAA,kBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,kBAAAA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,OAAA,CAAO,EAAA,CAEtC,EAAA,CACF,CAAA;AAEJ;AAKO,SAAS,uBAAA,CAAwB;AAAA,EACtC,SAAA;AAAA,EACA,cAAA;AAAA,EACA,WAAA;AAAA,EACA,gBAAA,EAAkB;AACpB,CAAA,EAIG;AACD,EAAA,MAAM,QAAA,EAAU,eAAA,mBAAkBA,6BAAAA,uBAAC,EAAA,CAAA,CAAwB,CAAA;AAC3D,EAAA,MAAM,KAAA,EAAO,YAAA,mBAAeA,6BAAAA,gBAAC,EAAA,EAAiB,KAAA,EAAO,EAAA,CAAG,CAAA;AAExD,EAAA,uBACEA,6BAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,0DAAA;AAAA,QACA,gBAAA,IAAoB,QAAA,GAAW,0BAAA;AAAA,QAC/B;AAAA,MACF,CAAA;AAAA,MAEC,QAAA,EAAA,gBAAA,IAAoB,OAAA,kBACnBC,8BAAAA,oBAAAC,EAAA,EACE,QAAA,EAAA;AAAA,wBAAAF,6BAAAA,OAAC,EAAA,EAAM,SAAA,EAAU,oBAAA,EAAsB,QAAA,EAAA,QAAA,CAAQ,CAAA;AAAA,wBAC/CA,6BAAAA,MAAC,EAAA,EAAK,SAAA,EAAU,oBAAA,EAAsB,QAAA,EAAA,KAAA,CAAK;AAAA,MAAA,EAAA,CAC7C,EAAA,kBAEAC,8BAAAA,oBAAAC,EAAA,EACE,QAAA,EAAA;AAAA,wBAAAF,6BAAAA,MAAC,EAAA,EAAK,SAAA,EAAU,SAAA,EAAW,QAAA,EAAA,KAAA,CAAK,CAAA;AAAA,wBAChCA,6BAAAA,OAAC,EAAA,EAAM,SAAA,EAAU,SAAA,EAAW,QAAA,EAAA,QAAA,CAAQ;AAAA,MAAA,EAAA,CACtC;AAAA,IAAA;AAAA,EAEJ,CAAA;AAEJ;AAKO,SAAS,qBAAA,CAAsB,EAAE,UAAU,CAAA,EAA4B;AAC5E,EAAA,uBACEC,8BAAAA,SAAC,EAAA,EAAQ,SAAA,EAAW,kCAAA,mBAAG,EAAqB,SAAS,CAAA,EAEnD,QAAA,EAAA;AAAA,oBAAAA,8BAAAA,QAAC,EAAA,EAAO,SAAA,EAAU,sCAAA,EAEhB,QAAA,EAAA;AAAA,sBAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,YAAA,EACb,QAAA,EAAA;AAAA,wBAAAD,6BAAAA,mBAAC,CAAoB,IAAA,EAApB,CAAA,CAAyB,CAAA;AAAA,wBAC1BA,6BAAAA,mBAAC,CAAoB,IAAA,EAApB,EAAyB,SAAA,EAAU,OAAA,CAAO;AAAA,MAAA,EAAA,CAC7C,CAAA;AAAA,sBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,wBAAAD,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,wBACzCA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,QAAA,CAAQ;AAAA,MAAA,EAAA,CAC1C,CAAA;AAAA,sBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,0DAAA,EACb,QAAA,EAAA;AAAA,wBAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,aAAC,CAAc,MAAA,EAAd,EAAqB,IAAA,EAAK,KAAA,CAAK,CAAA;AAAA,0BAChCA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO;AAAA,QAAA,EAAA,CACzC,CAAA;AAAA,wBACAA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,wBACvCA,6BAAAA,YAAC,CAAa,OAAA,EAAb,EAAqB,SAAA,EAAU,OAAA,CAAO;AAAA,MAAA,EAAA,CACzC;AAAA,IAAA,EAAA,CACF,CAAA;AAAA,oBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,eAAA,EACb,QAAA,kBAAAA,6BAAAA,aAAC,CAAc,SAAA,EAAd,EAAwB,SAAA,EAAU,sBAAA,CAAsB,EAAA,CAC3D,CAAA;AAAA,oBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yCAAA,EAEZ,QAAA,EAAA;AAAA,MAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,GAAG,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,KAAA,EAAA,mBAClCA,8BAAAA,KAAC,EAAA,EAAgB,SAAA,EAAU,WAAA,EACzB,QAAA,EAAA;AAAA,wBAAAD,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,wBACtCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,wBACtCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,QAAA,CAAQ,CAAA;AAAA,wBACrCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,QAAA,CAAQ;AAAA,MAAA,EAAA,CAAA,EAJ7B,KAKV,CACD,CAAA;AAAA,sBAGDC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,MAAA,EACb,QAAA,EAAA;AAAA,wBAAAD,6BAAAA,YAAC,CAAa,UAAA,EAAb,EAAwB,SAAA,EAAU,aAAA,CAAa,CAAA;AAAA,wBAChDC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,0BACtCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,QAAA,CAAQ;AAAA,QAAA,EAAA,CACvC;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,EAAA,CACF,CAAA;AAEJ;AAMO,SAAS,0BAAA,CAA2B,EAAE,UAAU,CAAA,EAA4B;AACjF,EAAA,uBACEA,6BAAAA,MAAC,EAAA,EAAK,SAAA,EAAW,kCAAA,wBAAG,EAA0B,SAAS,CAAA,EACrD,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,oDAAA,EAEb,QAAA,EAAA;AAAA,oBAAAD,6BAAAA,kBAAC,EAAA,EAAmB,SAAA,EAAU,OAAA,CAAO,CAAA;AAAA,oBAGrCC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qCAAA,EAEb,QAAA,EAAA;AAAA,sBAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gBAAA,EAEb,QAAA,EAAA;AAAA,wBAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,OAAA,EAEb,QAAA,EAAA;AAAA,0BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iBAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wFAAA,CAAwF,CAAA;AAAA,4BAEvGC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,8BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EAEb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,6EAAA,CAA6E,CAAA;AAAA,gCAE5FA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wDAAA,CAAwD;AAAA,cAAA,EAAA,CACzE,CAAA;AAAA,8BAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,gCAChEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD;AAAA,cAAA,EAAA,CAClE;AAAA,YAAA,EAAA,CACF;AAAA,UAAA,EAAA,CACF,CAAA;AAAA,0BAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gHAAA,CAAgH,CAAA;AAAA,0BAC/HA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,aAAA,EACb,QAAA,kBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yDAAA,CAAyD,EAAA,CAC1E;AAAA,QAAA,EAAA,CACF,CAAA;AAAA,wBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iBAAA,EACb,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EAEb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,0BAGpFC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,4BACpFA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE;AAAA,UAAA,EAAA,CACtF,CAAA;AAAA,0BAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qDAAA,EAEb,QAAA,EAAA;AAAA,4BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yCAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gDAAA,CAAgD,CAAA;AAAA,8BAC/DC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,gCAChEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD;AAAA,cAAA,EAAA,CAClE;AAAA,YAAA,EAAA,CACF,CAAA;AAAA,4BAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,eAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,yBAAA,EACrB,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gDAAA,CAAgD,CAAA;AAAA,8BAC/DC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EACb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,gCAChEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gDAAA,CAAgD;AAAA,cAAA,EAAA,CACjE;AAAA,YAAA,EAAA,CAAA,EALQ,CAMV,CACD,EAAA,CACH;AAAA,UAAA,EAAA,CACF,CAAA;AAAA,0BAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,4BACpFA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,4BACpFA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE;AAAA,UAAA,EAAA,CACtF;AAAA,QAAA,EAAA,CACF,EAAA,CACF,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yDAAA,CAAyD,CAAA;AAAA,0BAGxEC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,4BAChEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCA,6BAAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBAEC,SAAA,EAAU;AAAA,cAAA,CAAA;AAAA,cADL;AAAA,YAEN,CACF,EAAA,CACH;AAAA,UAAA,EAAA,CACF,CAAA;AAAA,0BAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,4BAChEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCA,6BAAAA;AAAA,cAAC,KAAA;AAAA,cAAA;AAAA,gBAEC,SAAA,EAAU;AAAA,cAAA,CAAA;AAAA,cADL;AAAA,YAEN,CACF,EAAA,CACH;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,EAAA,CACF,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yDAAA,CAAyD,CAAA;AAAA,0BAExEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4DAAA,EACb,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4CAAA,CAA4C,CAAA;AAAA,4BAC3DA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4CAAA,CAA4C,CAAA;AAAA,4BAC3DA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,4BACjEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD;AAAA,UAAA,EAAA,CACnE,EAAA,CACF;AAAA,QAAA,EAAA,CACF,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yDAAA,CAAyD,CAAA;AAAA,0BACxEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sDAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,gEAAA,EACrB,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gEAAA,CAAgE,CAAA;AAAA,4BAC/EC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,8BACjEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4CAAA,CAA4C;AAAA,YAAA,EAAA,CAC7D;AAAA,UAAA,EAAA,CAAA,EALQ,CAMV,CACD,EAAA,CACH;AAAA,QAAA,EAAA,CACF,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yDAAA,CAAyD,CAAA;AAAA,0BACxEC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uCAAA,EAEb,QAAA,EAAA;AAAA,4BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,cAC/D,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,gEAAA,EACrB,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mFAAA,CAAmF,CAAA;AAAA,gCAClGC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,kCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,kCACjEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4CAAA,CAA4C;AAAA,gBAAA,EAAA,CAC7D;AAAA,cAAA,EAAA,CAAA,EALQ,CAMV,CACD;AAAA,YAAA,EAAA,CACH,CAAA;AAAA,4BAEAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,cAC/D,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,gEAAA,EACrB,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mFAAA,CAAmF,CAAA;AAAA,gCAClGC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,kCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,kCACjEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4CAAA,CAA4C;AAAA,gBAAA,EAAA,CAC7D;AAAA,cAAA,EAAA,CAAA,EALQ,CAMV,CACD;AAAA,YAAA,EAAA,CACH;AAAA,UAAA,EAAA,CACF;AAAA,QAAA,EAAA,CACF,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yDAAA,CAAyD,CAAA;AAAA,0BACxEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sDAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,+DAAA,EAErB,QAAA,EAAA;AAAA,4BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wBAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sFAAA,CAAsF,CAAA;AAAA,8BACrGC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kBAAA,EACb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,gCACjEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD;AAAA,cAAA,EAAA,CACnE,CAAA;AAAA,8BACAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD;AAAA,YAAA,EAAA,CAClE,CAAA;AAAA,4BAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4CAAA,CAA4C,CAAA;AAAA,8BAC3DA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,8BACjEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD;AAAA,YAAA,EAAA,CACnE,CAAA;AAAA,4BAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mEAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,8BAChEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD;AAAA,YAAA,EAAA,CAClE;AAAA,UAAA,EAAA,CAAA,EAtBQ,CAuBV,CACD,EAAA,CACH,CAAA;AAAA,0BAEAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gDAAA,CAAgD;AAAA,QAAA,EAAA,CACjE,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2BAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yDAAA,CAAyD,CAAA;AAAA,0BAGxEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,YAAA,EACb,QAAA,EAAA;AAAA,4BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sEAAA,EAEb,QAAA,EAAA;AAAA,8BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gCAAA,EACb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qDAAA,CAAqD,CAAA;AAAA,gCACpEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mDAAA,CAAmD;AAAA,cAAA,EAAA,CACpE,CAAA;AAAA,8BAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,KAAA,EACb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sDAAA,CAAsD,CAAA;AAAA,gCACrEC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,kCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mDAAA,CAAmD,CAAA;AAAA,kCAClEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,kCACjEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD;AAAA,gBAAA,EAAA,CACnE;AAAA,cAAA,EAAA,CACF;AAAA,YAAA,EAAA,CACF,CAAA;AAAA,4BAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,oBAAA,EACb,QAAA,kBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,0DAAA,CAA0D,EAAA,CAC3E;AAAA,UAAA,EAAA,CACF,EAAA,CACF,CAAA;AAAA,0BAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,qDAAA,EAErB,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sHAAA,EACb,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yBAAA,EAEb,QAAA,EAAA;AAAA,8BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mFAAA,EACb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mDAAA,CAAmD,CAAA;AAAA,gCAClEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD;AAAA,cAAA,EAAA,CAClE,CAAA;AAAA,8BAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD;AAAA,YAAA,EAAA,CAClE,EAAA,CACF,CAAA;AAAA,4BAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,8BACjEC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,gCAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mDAAA,CAAmD,CAAA;AAAA,gCAClEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD,CAAA;AAAA,gCACjEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD;AAAA,cAAA,EAAA,CACnE;AAAA,YAAA,EAAA,CACF;AAAA,UAAA,EAAA,CAAA,EAvBQ,CAwBV,CACD,EAAA,CACH;AAAA,QAAA,EAAA,CACF;AAAA,MAAA,EAAA,CACF,CAAA;AAAA,sBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yCAAA,EACb,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EAEb,QAAA,EAAA;AAAA,wBAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,wBAGpFC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,0BACpFA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE;AAAA,QAAA,EAAA,CACtF,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qDAAA,EAEb,QAAA,EAAA;AAAA,0BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yCAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gDAAA,CAAgD,CAAA;AAAA,4BAC/DC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,8BAChEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD;AAAA,YAAA,EAAA,CAClE;AAAA,UAAA,EAAA,CACF,CAAA;AAAA,0BAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,eAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,yBAAA,EACrB,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gDAAA,CAAgD,CAAA;AAAA,4BAC/DC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,iDAAA,CAAiD,CAAA;AAAA,8BAChEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gDAAA,CAAgD;AAAA,YAAA,EAAA,CACjE;AAAA,UAAA,EAAA,CAAA,EALQ,CAMV,CACD,EAAA,CACH;AAAA,QAAA,EAAA,CACF,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,0BACpFA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,0BACpFA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE;AAAA,QAAA,EAAA,CACtF;AAAA,MAAA,EAAA,CACF,EAAA,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,EAAA,CACF,EAAA,CACF,CAAA;AAEJ;AAKO,SAAS,oBAAA,CAAqB,EAAE,SAAA,EAAW,QAAA,EAAU,EAAE,CAAA,EAAmD;AAC/G,EAAA,uBACEA,6BAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,2BAAA;AAAA,QACA,QAAA,IAAY,EAAA,GAAK,4BAAA;AAAA,QACjB,QAAA,IAAY,EAAA,GAAK,2CAAA;AAAA,QACjB;AAAA,MACF,CAAA;AAAA,MAEC,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,QAAQ,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACvCA,6BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,qDAAA,EACrB,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,wBAAAD,6BAAAA,aAAC,CAAc,IAAA,EAAd,EAAmB,IAAA,EAAK,KAAA,CAAK,CAAA;AAAA,wBAC9BA,6BAAAA,YAAC,CAAa,UAAA,EAAb,EAAwB,SAAA,EAAU,QAAA,CAAQ,CAAA;AAAA,wBAC3CC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,SAAA,CAAS,CAAA;AAAA,0BACtCA,6BAAAA,YAAC,CAAa,IAAA,EAAb,EAAkB,SAAA,EAAU,QAAA,CAAQ;AAAA,QAAA,EAAA,CACvC;AAAA,MAAA,EAAA,CACF,EAAA,CAAA,EARQ,CASV,CACD;AAAA,IAAA;AAAA,EACH,CAAA;AAEJ;AAMO,SAAS,oBAAA,CAAqB,EAAE,UAAU,CAAA,EAA4B;AAC3E,EAAA,uBACEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAW,kCAAA,WAAG,EAAa,SAAS,CAAA,EACvC,QAAA,kBAAAA,6BAAAA,gBAAC,EAAA,EAAiB,KAAA,EAAO,CAAA,EAAG,OAAA,EAAQ,MAAA,EAAO,SAAA,EAAU,6BAAA,CAA6B,EAAA,CACpF,CAAA;AAEJ;AAMO,SAAS,kBAAA,CAAmB,EAAE,UAAU,CAAA,EAA4B;AACzE,EAAA,uBACEA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAW,kCAAA,WAAG,EAAa,SAAS,CAAA,EACvC,QAAA,kBAAAA,6BAAAA,gBAAC,EAAA,EAAiB,KAAA,EAAO,EAAA,EAAI,OAAA,EAAQ,QAAA,EAAS,SAAA,EAAU,4CAAA,CAA4C,EAAA,CACtG,CAAA;AAEJ;AAMO,SAAS,sBAAA,CAAA,EAAyB;AACvC,EAAA,uBACEA,6BAAAA,SAAC,EAAA,EAAQ,SAAA,EAAU,wDAAA,EAAyD,YAAA,EAAW,yBAAA,EAErF,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qCAAA,EAEb,QAAA,EAAA;AAAA,oBAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,QAAA,EACb,QAAA,kBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wEAAA,CAAwE,EAAA,CACzF,CAAA;AAAA,oBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uFAAA,EAEb,QAAA,EAAA;AAAA,sBAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2IAAA,EACb,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,eAAA,EAEb,QAAA,EAAA;AAAA,wBAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,CAAmC,CAAA;AAAA,4BAClDA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,CAAmC;AAAA,UAAA,EAAA,CACpD,CAAA;AAAA,0BACAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,CAAmC;AAAA,QAAA,EAAA,CACpD,CAAA;AAAA,wBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qBAAA,EACb,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wCAAA,CAAwC,CAAA;AAAA,UACtD,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,gDAAA,EACrB,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kCAAA,CAAkC,CAAA;AAAA,4BACjDC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,QAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wCAAA,CAAwC,CAAA;AAAA,8BACvDA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,CAAmC;AAAA,YAAA,EAAA,CACpD;AAAA,UAAA,EAAA,CAAA,EALQ,CAMV,CACD;AAAA,QAAA,EAAA,CACH,CAAA;AAAA,wBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sCAAA,EACb,QAAA,kBAAAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,WAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,CAAmC,CAAA;AAAA,4BAClDA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kCAAA,CAAkC;AAAA,UAAA,EAAA,CACnD,CAAA;AAAA,0BACAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sBAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,mCAAA,CAAmC,CAAA;AAAA,4BAClDA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kCAAA,CAAkC;AAAA,UAAA,EAAA,CACnD;AAAA,QAAA,EAAA,CACF,EAAA,CACF;AAAA,MAAA,EAAA,CACF,EAAA,CACF,CAAA;AAAA,sBAGAC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gLAAA,EAEb,QAAA,EAAA;AAAA,wBAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sHAAA,EACb,QAAA,EAAA;AAAA,0BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,yBAAA,EACb,QAAA,EAAA;AAAA,4BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gDAAA,CAAgD,CAAA;AAAA,4BAC/DA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,kDAAA,CAAkD;AAAA,UAAA,EAAA,CACnE,CAAA;AAAA,0BACAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2CAAA,CAA2C;AAAA,QAAA,EAAA,CAC5D,CAAA;AAAA,wBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,oHAAA,EACZ,QAAA,EAAA,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,EAAE,CAAC,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,CAAA,EAAA,mBACjCC,8BAAAA,KAAC,EAAA,EAAY,SAAA,EAAU,qBAAA,EACrB,QAAA,EAAA;AAAA,0BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,qEAAA,CAAqE,CAAA;AAAA,0BACpFC,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,QAAA,EACb,QAAA,EAAA;AAAA,4BAAAA,8BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,8BAAA,EACb,QAAA,EAAA;AAAA,8BAAAD,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2CAAA,CAA2C,CAAA;AAAA,8BAC1DA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,2CAAA,CAA2C;AAAA,YAAA,EAAA,CAC5D,CAAA;AAAA,4BACAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,4CAAA,CAA4C;AAAA,UAAA,EAAA,CAC7D;AAAA,QAAA,EAAA,CAAA,EARQ,CASV,CACD,EAAA,CACH,CAAA;AAAA,wBAGAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,sFAAA,EACb,QAAA,kBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,oDAAA,EACb,QAAA,kBAAAA,6BAAAA,KAAC,EAAA,EAAI,SAAA,EAAU,uDAAA,CAAuD,EAAA,CACxE,EAAA,CACF;AAAA,MAAA,EAAA,CACF;AAAA,IAAA,EAAA,CACF;AAAA,EAAA,EAAA,CACF,EAAA,CACF,CAAA;AAEJ;AJmDA;AACA;AK3wBI;AATG,SAAS,wBAAA,CAAyB;AAAA,EACvC,SAAA;AAAA,EACA,QAAA;AAAA,EACA,SAAA;AAAA,EACA,gBAAA,EAAkB,GAAA;AAAA,EAClB,mBAAA,EAAqB,GAAA;AAAA,EACrB,mBAAA,EAAqB;AACvB,CAAA,EAAkC;AAChC,EAAA,uBACEC,8BAAAA;AAAA,IAAC,KAAA;AAAA,IAAA;AAAA,MACC,SAAA,EAAW,kCAAA;AAAA,QACT,qCAAA;AAAA,QACA,UAAA,GAAa,mBAAA,GAAsB,qBAAA;AAAA,QACnC;AAAA,MACF,CAAA;AAAA,MACA,KAAA,EAAO;AAAA,QACL,OAAA,EAAS,UAAA,EAAY,gBAAA,EAAkB,CAAA;AAAA,QACvC,kBAAA,EAAoB,CAAA,EAAA;AACtB,MAAA;AACK,MAAA;AACM,MAAA;AACA,MAAA;AACG,MAAA;AAGb,MAAA;AACC,QAAA;AAMFD,wBAAAA;AAAC,UAAA;AAAA,UAAA;AACe,YAAA;AACP,YAAA;AACL,cAAA;AACF,YAAA;AAEC,YAAA;AAAA,UAAA;AACH,QAAA;AAIE,QAAA;AAEA,MAAA;AAAA,IAAA;AAEJ,EAAA;AAEJ;AAWgB;AAEZA,EAAAA;AAAC,IAAA;AAAA,IAAA;AACC,MAAA;AACoB,MAAA;AACpB,MAAA;AAEC,MAAA;AAAA,IAAA;AACH,EAAA;AAEJ;AAWoC;AAEhCA,EAAAA;AAAC,IAAA;AAAA,IAAA;AACC,MAAA;AACiB,MAAA;AACH,MAAA;AAEb,MAAA;AAAA,IAAA;AACH,EAAA;AAEJ;AAWgB;AAEZA,EAAAA;AAIJ;AL4uB6B;AACA;AM74Bb;AAgGP;AA5DoB;AAUb;AACmC,EAAA;AAEhC,EAAA;AACS,IAAA;AACR,IAAA;AACD,MAAA;AACf,IAAA;AACuB,IAAA;AACV,IAAA;AAEY,MAAA;AACN,QAAA;AACjB,MAAA;AACF,IAAA;AACG,EAAA;AAEC,EAAA;AAGqB,IAAA;AACD,IAAA;AACT,MAAA;AACG,QAAA;AAChB,MAAA;AACF,IAAA;AAGmB,IAAA;AACK,MAAA;AACxB,IAAA;AACO,IAAA;AACJ,EAAA;AAEY,EAAA;AACY,IAAA;AACb,MAAA;AACK,MAAA;AACA,MAAA;AACC,MAAA;AACC,MAAA;AACZ,MAAA;AACT,IAAA;AACoB,IAAA;AACtB,EAAA;AAEoB,EAAA;AAEA,EAAA;AAEbA,EAAAA;AACT;AAM8D;AACrC,EAAA;AACb,EAAA;AAED,IAAA;AACiB,MAAA;AAAC,MAAA;AACP,MAAA;AACG,MAAA;AACrB,IAAA;AACF,EAAA;AACO,EAAA;AACT;ANo1B6B;AACA;AOz8BpBG;AAoCoB;AACP,EAAA;AACM,IAAA;AAC1B,EAAA;AACoD,EAAA;AAC7B,EAAA;AACwB,EAAA;AAC3B,EAAA;AAEb,EAAA;AACW,IAAA;AACFC,IAAAA;AACS,IAAA;AACJ,IAAA;AACD,IAAA;AACE,IAAA;AACtB,EAAA;AACF;AAEmC;AACb,EAAA;AACd,EAAA;AAGCD,EAAAA;AACgB,IAAA;AACf,MAAA;AACe,QAAA;AACP,UAAA;AACG,UAAA;AACA,UAAA;AACZ,QAAA;AACiB,QAAA;AACE,UAAA;AACpB,QAAA;AAC4B,QAAA;AACrB,QAAA;AACO,MAAA;AACP,QAAA;AACI,UAAA;AACF,UAAA;AACT,QAAA;AACF,MAAA;AACF,IAAA;AACC,IAAA;AACH,EAAA;AACF;APo6B6B;AACA;AQr/BV;AAcQ;AACA,EAAA;AACZ,EAAA;AACW,IAAA;AACjB,EAAA;AAGiB,IAAA;AACxB,EAAA;AACF;AAS0B;AACA,EAAA;AACJ,EAAA;AACC,IAAA;AACJ,MAAA;AACf,IAAA;AACF,EAAA;AACoB,EAAA;AACC,IAAA;AACF,MAAA;AACI,MAAA;AACrB,IAAA;AACF,EAAA;AACO,EAAA;AACT;AAeoE;AAC1C,EAAA;AAC1B;AAGwB;AACI,EAAA;AAC5B;AAEqD;AAC3B,EAAA;AACA,EAAA;AAC1B;AAEwB;AACL,EAAA;AACnB;AAcmD;AAC3B,EAAA;AAEA,EAAA;AACE,EAAA;AACA,EAAA;AACL,EAAA;AACK,EAAA;AACD,EAAA;AAEC,EAAA;AACA,EAAA;AAEjB,EAAA;AACL,IAAA;AACA,IAAA;AACwB,IAAA;AACxB,IAAA;AACA,IAAA;AACkB,IAAA;AACA,IAAA;AACJ,IAAA;AACO,IAAA;AACvB,EAAA;AACF;AAGsD;AACxB,EAAA;AACF,EAAA;AACC,EAAA;AACF,IAAA;AACE,IAAA;AAC3B,EAAA;AACO,EAAA;AACT;AAEuB;AACO,EAAA;AACc,EAAA;AACf,EAAA;AACH,IAAA;AACA,IAAA;AACG,IAAA;AACL,IAAA;AACI,IAAA;AACJ,IAAA;AACtB,EAAA;AACO,EAAA;AACT;AAEuB;AACO,EAAA;AACP,EAAA;AACvB;AAesC;AACd,EAAA;AAEC,EAAA;AAEhB,EAAA;AACe,IAAA;AACI,IAAA;AACA,IAAA;AACV,IAAA;AACD,IAAA;AACO,IAAA;AACI,IAAA;AACH,IAAA;AACA,IAAA;AACJ,IAAA;AACO,IAAA;AACF,IAAA;AACxB,EAAA;AACF;AAuBgC;AACH,EAAA;AACH,EAAA;AAEC,EAAA;AAEP,EAAA;AACG,EAAA;AACK,EAAA;AACC,EAAA;AACF,EAAA;AACC,EAAA;AACJ,EAAA;AACEE,EAAAA;AAElB,EAAA;AAKkBA,EAAAA;AACR,EAAA;AACE,IAAA;AACjB,EAAA;AAEqB,EAAA;AAEN,EAAA;AACS,IAAA;AACJ,MAAA;AACC,MAAA;AAED,MAAA;AACD,QAAA;AACI,MAAA;AACJ,QAAA;AAChB,MAAA;AAEc,MAAA;AAEM,MAAA;AACF,QAAA;AACC,QAAA;AACI,UAAA;AAGF,UAAA;AACnB,QAAA;AACiB,QAAA;AACC,UAAA;AACZ,QAAA;AACR,MAAA;AACF,IAAA;AAEwB,IAAA;AACJ,IAAA;AACG,EAAA;AAiBC,EAAA;AACD,EAAA;AACC,EAAA;AACH,IAAA;AACI,IAAA;AACP,MAAA;AACM,MAAA;AACD,QAAA;AAGE,QAAA;AACvB,MAAA;AAKoB,MAAA;AACtB,IAAA;AACF,EAAA;AAIgB,EAAA;AACW,IAAA;AACA,IAAA;AACZ,IAAA;AACM,EAAA;AAEE,EAAA;AACjB,IAAA;AACoB,MAAA;AACT,MAAA;AAEU,MAAA;AAEL,MAAA;AACA,QAAA;AAClB,MAAA;AAE+B,MAAA;AAIR,MAAA;AAEZ,MAAA;AACS,QAAA;AAEd,QAAA;AACI,UAAA;AAGF,UAAA;AACe,YAAA;AACnB,UAAA;AACe,QAAA;AACT,UAAA;AAED,UAAA;AACG,YAAA;AACQ,YAAA;AACZ,cAAA;AACc,cAAA;AACZ,gBAAA;AACO,gBAAA;AACT,cAAA;AACF,YAAA;AACF,UAAA;AACF,QAAA;AACK,MAAA;AACI,QAAA;AACX,MAAA;AACY,IAAA;AACE,MAAA;AACL,MAAA;AACT,IAAA;AACsB,MAAA;AACxB,IAAA;AACF,EAAA;AAEqB,EAAA;AACf,IAAA;AACkB,MAAA;AAMG,MAAA;AAOV,MAAA;AAEK,MAAA;AACH,QAAA;AACL,UAAA;AACO,UAAA;AAMA,UAAA;AAKK,YAAA;AACH,YAAA;AACE,YAAA;AACf,YAAA;AACF,UAAA;AAIc,UAAA;AACR,UAAA;AAIA,UAAA;AACF,UAAA;AAKc,YAAA;AACH,YAAA;AACE,YAAA;AACf,YAAA;AACF,UAAA;AACS,UAAA;AACM,UAAA;AACf,UAAA;AACF,QAAA;AACgB,QAAA;AAClB,MAAA;AAE+B,MAAA;AAClB,MAAA;AACA,MAAA;AAEQ,MAAA;AAEZ,MAAA;AAMa,QAAA;AACA,QAAA;AACF,UAAA;AACD,UAAA;AACjB,QAAA;AACc,QAAA;AACT,MAAA;AACI,QAAA;AACM,QAAA;AACjB,MAAA;AACY,IAAA;AACC,MAAA;AACC,MAAA;AACL,MAAA;AACM,MAAA;AACf,IAAA;AAIa,MAAA;AACf,IAAA;AACF,EAAA;AAMyB,EAAA;AACT,EAAA;AACQ,IAAA;AACvB,EAAA;AAMe,EAAA;AASW,IAAA;AACnB,IAAA;AACU,IAAA;AAIS,IAAA;AACb,IAAA;AACT,EAAA;AAEW,EAAA;AACO,IAAA;AACH,IAAA;AAEe,IAAA;AAER,IAAA;AACT,MAAA;AACT,IAAA;AACQ,MAAA;AAEI,MAAA;AAKM,QAAA;AACF,QAAA;AAKD,UAAA;AAChB,UAAA;AACF,QAAA;AACc,QAAA;AACG,MAAA;AAQG,QAAA;AAClB,UAAA;AACF,QAAA;AACiB,QAAA;AACZ,MAAA;AACS,QAAA;AAChB,MAAA;AACF,IAAA;AAEoB,IAAA;AAClB,MAAA;AACF,IAAA;AAEiB,IAAA;AACC,MAAA;AAEO,MAAA;AACzB,IAAA;AACgB,EAAA;AAECF,EAAAA;AACA,IAAA;AACM,MAAA;AACC,MAAA;AACF,QAAA;AACb,MAAA;AACU,QAAA;AACjB,MAAA;AACO,MAAA;AACR,IAAA;AACE,EAAA;AAMcA,EAAAA;AAC8C,IAAA;AAG3C,MAAA;AACC,QAAA;AACG,UAAA;AACV,YAAA;AACU,YAAA;AACD,YAAA;AACjB,UAAA;AACe,UAAA;AAChB,QAAA;AACI,MAAA;AACa,QAAA;AACC,QAAA;AACA,UAAA;AACnB,QAAA;AACF,MAAA;AAMgB,MAAA;AACK,MAAA;AACN,MAAA;AACJ,MAAA;AACb,IAAA;AACoB,IAAA;AACtB,EAAA;AAEsBA,EAAAA;AACgE,IAAA;AAC3D,MAAA;AACR,MAAA;AACQ,MAAA;AA6BjB,MAAA;AAIiB,MAAA;AACA,QAAA;AACrB,QAAA;AACF,MAAA;AAEuB,MAAA;AACT,QAAA;AACO,UAAA;AACJ,YAAA;AACH,YAAA;AACN,cAAA;AACK,YAAA;AACL,cAAA;AACF,YAAA;AACI,UAAA;AACD,QAAA;AACY,UAAA;AACC,YAAA;AACZ,UAAA;AACR,QAAA;AACF,MAAA;AAEa,MAAA;AACK,QAAA;AACA,QAAA;AACD,QAAA;AAEG,QAAA;AACZ,QAAA;AACY,QAAA;AAECG,QAAAA;AACHC,UAAAA;AACE,UAAA;AAClB,QAAA;AAEqB,QAAA;AAER,QAAA;AACI,UAAA;AACjB,QAAA;AAEa,QAAA;AACb,QAAA;AACF,MAAA;AAIgB,MAAA;AAEA,MAAA;AACA,MAAA;AACD,MAAA;AAEF,MAAA;AACI,QAAA;AACjB,MAAA;AAEkB,MAAA;AACW,MAAA;AACX,MAAA;AACK,MAAA;AAEJ,MAAA;AACH,QAAA;AACC,QAAA;AACjB,MAAA;AAEuB,MAAA;AACA,QAAA;AACvB,MAAA;AACF,IAAA;AACsB,IAAA;AACxB,EAAA;AAEgB,EAAA;AACO,IAAA;AACR,MAAA;AACK,MAAA;AACA,QAAA;AACP,QAAA;AACT,MAAA;AACA,MAAA;AACD,IAAA;AACgB,EAAA;AAOG,EAAA;AACC,IAAA;AACR,IAAA;AACS,IAAA;AACG,EAAA;AAEpB,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACyB,IAAA;AACzB,IAAA;AACkB,IAAA;AACK,IAAA;AACvB,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACS,IAAA;AACT,IAAA;AACF,EAAA;AACF;ARssB6B;AACA;ASt6CpBC;AASa;AAID;AAiBQ;AACL,EAAA;AAChB,EAAA;AAOgBC,EAAAA;AAEKN,EAAAA;AACH,IAAA;AACF,IAAA;AAEC,IAAA;AACJ,IAAA;AAEK,IAAA;AAEL,IAAA;AACM,MAAA;AACjB,IAAA;AACH,EAAA;AAEW,EAAA;AACK,IAAA;AACJ,IAAA;AAEM,IAAA;AACf,MAAA;AAEmB,MAAA;AACF,MAAA;AAER,MAAA;AACK,QAAA;AACD,QAAA;AACI,UAAA;AACjB,UAAA;AACF,QAAA;AACF,MAAA;AAEiB,MAAA;AACnB,IAAA;AAEI,IAAA;AACoB,IAAA;AACT,MAAA;AACC,MAAA;AAChB,IAAA;AAEwB,IAAA;AACX,IAAA;AAEA,IAAA;AACJ,MAAA;AACM,MAAA;AACf,IAAA;AACgB,EAAA;AAEM,EAAA;AAC1B;AT03C6B;AACA;AC7qCTD;AAtRP;AACC,EAAA;AACS,EAAA;AACT,EAAA;AACC,EAAA;AACE,EAAA;AACP,EAAA;AACA,EAAA;AACQ,EAAA;AAClB;AAoGiD;AACxCF,EAAAA;AACT;AAE0B;AACxB,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACe,EAAA;AACH,EAAA;AACJ,EAAA;AACR,EAAA;AACe,EAAA;AACf,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACa,EAAA;AACb,EAAA;AACA,EAAA;AACiB;AAIX,EAAA;AACA,EAAA;AAOc,EAAA;AACd,EAAA;AACJ,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACE,EAAA;AACF,IAAA;AACqB,MAAA;AACF,MAAA;AACjB,MAAA;AACA,MAAA;AACF,IAAA;AACA,IAAA;AACF,EAAA;AAEuB,EAAA;AAER,EAAA;AAMU,EAAA;AAEvB,EAAA;AAGyB,IAAA;AACG,IAAA;AACxB,EAAA;AACY,EAAA;AACR,IAAA;AACR,IAAA;AACA,IAAA;AACoB,IAAA;AACE,IAAA;AACvB,EAAA;AAEuBS,EAAAA;AACD,IAAA;AACA,IAAA;AACE,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASA,MAAA;AACrB,MAAA;AACe,MAAA;AAChB,IAAA;AACU,EAAA;AAOP,EAAA;AAIgB,EAAA;AACK,EAAA;AACL,EAAA;AAEI,EAAA;AAGxB,EAAA;AAIc,EAAA;AACC,EAAA;AAEf,EAAA;AAGA,EAAA;AAGwB,EAAA;AAQxB,EAAA;AAIQ,IAAA;AAAC,MAAA;AAAA,MAAA;AACc,QAAA;AACI,QAAA;AACF,QAAA;AACI,QAAA;AACE,QAAA;AACL,QAAA;AACF,QAAA;AAAU,MAAA;AAC1B,IAAA;AAIAT,IAAAA;AAEK,sBAAA;AACA,sBAAA;AAA4C,QAAA;AAAM,QAAA;AAAyB,MAAA;AAEhF,IAAA;AAIA,IAAA;AACG,sBAAA;AAMQ,wBAAA;AACE,UAAA;AAAA,UAAA;AACQ,YAAA;AACP,YAAA;AACA,YAAA;AACa,YAAA;AACG,YAAA;AACL,YAAA;AACX,YAAA;AAAA,UAAA;AAEJ,QAAA;AAEC,wBAAA;AAGKA,0BAAAA;AACAA,0BAAAA;AAAC,YAAA;AAAA,YAAA;AACQ,cAAA;AACP,cAAA;AACA,cAAA;AACa,cAAA;AACb,cAAA;AACW,cAAA;AACX,cAAA;AAAA,YAAA;AACF,UAAA;AAGN,QAAA;AAIR,MAAA;AAEC,sBAAA;AACE,QAAA;AAAA,QAAA;AACY,UAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOT,UAAA;AAUF,UAAA;AAAAA,4BAAAA;AAcC,YAAA;AAGK,8BAAA;AACA,8BAAA;AAEK,gBAAA;AAAA,gBAAA;AAEY,kBAAA;AAIX,kBAAA;AAAmF,gBAAA;AAL9E,gBAAA;AAQX,cAAA;AAEJ,YAAA;AAGgB,YAAA;AAGZ,8BAAA;AACA,8BAAA;AAAC,gBAAA;AAAA,gBAAA;AACW,kBAAA;AACV,kBAAA;AACA,kBAAA;AACA,kBAAA;AACA,kBAAA;AAAY,gBAAA;AACd,cAAA;AAEJ,YAAA;AAAA,UAAA;AAAA,QAAA;AAGN,MAAA;AACF,IAAA;AAIR,EAAA;AAOA,EAAA;AAIJ;AD0vC6B;AACA;AUrpDvB;AAH6B;AAE/BC,EAAAA;AACG,oBAAA;AACE,sBAAA;AACA,sBAAA;AACA,sBAAA;AACA,sBAAA;AACH,IAAA;AACC,oBAAA;AACE,sBAAA;AACA,sBAAA;AACA,sBAAA;AACA,sBAAA;AACH,IAAA;AACC,oBAAA;AACE,sBAAA;AACA,sBAAA;AACA,sBAAA;AACA,sBAAA;AACA,sBAAA;AACH,IAAA;AACC,oBAAA;AACA,oBAAA;AACA,oBAAA;AACE,sBAAA;AACA,sBAAA;AACA,sBAAA;AACA,sBAAA;AACH,IAAA;AACC,oBAAA;AACE,sBAAA;AACA,sBAAA;AACA,sBAAA;AACA,sBAAA;AACA,sBAAA;AACH,IAAA;AACC,oBAAA;AACA,oBAAA;AACE,sBAAA;AACA,sBAAA;AACA,sBAAA;AACH,IAAA;AACC,oBAAA;AACE,sBAAA;AACA,sBAAA;AACA,sBAAA;AACA,sBAAA;AACH,IAAA;AACC,oBAAA;AACH,EAAA;AAEJ;AA0BgC;AAIT,EAAA;AAEjBD,IAAAA;AAEK,sBAAA;AACA,sBAAA;AACE,wBAAA;AACA,wBAAA;AACH,MAAA;AACC,sBAAA;AAEL,IAAA;AAEJ,EAAA;AAIoB,EAAA;AAGlBC,EAAAA;AAIG,oBAAA;AAEE,sBAAA;AACE,wBAAA;AACA,wBAAA;AACH,MAAA;AAEC,sBAAA;AAKH,IAAA;AAGAD,oBAAAA;AAAC,MAAA;AAAA,MAAA;AACW,QAAA;AACO,QAAA;AAAsB,MAAA;AACzC,IAAA;AACF,EAAA;AAEJ;AV2mD6B;AACA;AWpsDzB;AAJkB;AAEhB;AAYmC;AAOnC;AAOqC;AAOF;AACtC,EAAA;AAAA,EAAA;AACmB,IAAA;AACA,IAAA;AACA,IAAA;AACD,IAAA;AAAA,EAAA;AACnB;AAG6B;AAET;AAAA;AAIjB,kBAAA;AAA0C;AAgBnB;AAClB,EAAA;AACR,EAAA;AACmB,EAAA;AACF,EAAA;AACJ,EAAA;AACD,EAAA;AACG,EAAA;AACZ,EAAA;AACgB;AAC8B,EAAA;AACrC,IAAA;AACL,IAAA;AACS,IAAA;AACP,IAAA;AACD,IAAA;AACR,EAAA;AAEwD,EAAA;AACjC,IAAA;AACJ,IAAA;AACQ,IAAA;AAC3B,EAAA;AAGEA,EAAAA;AAAC,IAAA;AAAA,IAAA;AACK,MAAA;AACJ,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AACA,MAAA;AAAA,IAAA;AACF,EAAA;AAEJ;AX2pD6B;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-M4KQWGEL.cjs","sourcesContent":[null,"'use client';\n\nimport type React from 'react';\nimport { useMemo } from 'react';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport type { DocContent, DocNode, DocRenderHandlers, DocSourceId } from '../../types/doc-source';\nimport { findDocNodeByPath } from '../../utils/doc-tree-nav';\nimport { PageShell } from '../layout/article-detail-layout';\nimport { PageLayout } from '../layout/page-layout';\nimport { CategorySidebarSkeleton } from '../loading/page-layout-skeleton';\nimport { MultiLevelNavigation, MobileNavigationDropdown } from '../navigation/multi-level-navigation';\nimport { StickySectionNav } from '../navigation/sticky-section-nav';\nimport { PersistentSidebar, PersistentMobileDropdown } from '../persistent-filter-controls';\nimport { DocSearchBar, useDocSearch } from '../shared/doc-search';\nimport { useDocNavigation } from './doc-navigation-context';\nimport { useDocsResolveLink } from './use-docs-resolve-link';\nimport { useDocumentTree } from './use-document-tree';\nimport { useScrollSpy } from './use-scroll-spy';\n\n/** Color tokens for the doc-viewer chrome. Hub-side `DocViewer` callers share\n * this constant; no need to override per source — the palette is intentionally\n * uniform across knowledge-base + data-room (both use ODS dark tokens). */\nexport const DEFAULT_DOC_VIEWER_PALETTE = {\n background: 'bg-ods-bg',\n containerBackground: 'transparent',\n headerText: 'text-ods-text-primary',\n primaryText: 'text-ods-text-primary',\n secondaryText: 'text-ods-text-secondary',\n accent: 'var(--ods-accent)',\n border: 'border-ods-border',\n cardBackground: 'bg-ods-card',\n} as const;\n\nexport interface DocViewerProps {\n /**\n * Registry source id (`'openframe-docs'`, `'data-room-docs'`, …). Flowed through\n * `renderContent`'s handlers for `/api/docs/resolve-link` POSTs.\n */\n sourceId: DocSourceId;\n\n /**\n * Render the content body. The page shell owns this — it picks the markdown\n * renderer, dispatches PDF/Figma/Sheets/file branches, etc. No renderer\n * interface in between.\n */\n renderContent: (content: DocContent, handlers: DocRenderHandlers) => React.ReactNode;\n\n /**\n * Render the loading skeleton. Receives the selected node's `documentType`\n * (undefined while structure is still loading) so the caller can return a\n * markdown-shaped skeleton vs an embed-shaped skeleton.\n */\n renderSkeleton: (documentType: DocNode['documentType']) => React.ReactNode;\n\n /**\n * Chat-source identifier — passed in by the page shell from server-side\n * `currentPlatform()`. Lib has no platform context; the page shell is the\n * trusted boundary that wires this. NEVER pass user input here.\n */\n chatSource: string;\n\n /** Page title — rendered as the inline hero `<h1>` (same DOM\n * `<DevSectionView>`'s hero uses) so the doc-viewer chrome matches the\n * dev-section pages. ReactNode is intentionally not supported here —\n * every consumer renders the same typography. */\n title?: string;\n /** Optional icon rendered inline before the title text — same slot\n * `<DevSectionView>`'s hero uses (Map for Roadmap, Rocket for Releases,\n * etc.). Pass a pre-rendered React element styled with\n * `SECTION_HERO_ICON_CLASS` (`h-10 w-10 text-ods-accent`) for visual\n * parity with other lib pages. */\n titleIcon?: React.ReactNode;\n /** Subtitle (h6, secondary text) rendered beneath the title. */\n subtitle?: string;\n /** Render a yellow accent dot (`.`) after the title — same flag as\n * the hub's legacy `<AdminPageHeader accentDot>` so the docs-hub\n * surface keeps its existing accent styling after the migration. */\n accentDot?: boolean;\n /** Override the default ODS palette. Optional — most callers should omit. */\n colorPalette?: typeof DEFAULT_DOC_VIEWER_PALETTE;\n className?: string;\n /** Render the standalone `<PageShell>` (own `<main>` + bg + max-width). Default\n * true. Pass false when the host layout already provides the page container —\n * only the padding box renders, avoiding a nested `<main>`. */\n shell?: boolean;\n\n /** Initial doc path (URL `[...path]`). */\n docPath?: string;\n\n /** Sidebar header copy (`'DOCUMENTATION'`, `'DATA ROOM'`). */\n sidebarLabel?: string;\n\n /**\n * API endpoint for fetching the document tree structure. Defaults to the\n * dispatcher path `/api/docs/sources/${sourceId}/structure`. Override only\n * if hosting the viewer behind a different route.\n */\n structureEndpoint?: string;\n /** Same shape as `structureEndpoint`. Defaults to `/api/docs/sources/${sourceId}/content`. */\n contentEndpoint?: string;\n /** RAG-search endpoint that backs the in-source search bar (when `showAIChat`\n * is on). Defaults to `/api/docs/search`. Override for proxy-prefix embeds —\n * same injectability pattern as `structureEndpoint` / `contentEndpoint`. */\n searchEndpoint?: string;\n /** POST internal-link resolver. The viewer threads an async `onResolveLink`\n * into `renderContent`'s `handlers` that posts `{ link, currentPath, source }`\n * here. Defaults to `/api/docs/resolve-link`. Override for proxy-prefix embeds —\n * same injectability pattern as `structureEndpoint` / `contentEndpoint` /\n * `searchEndpoint`, with `ChatRuntime.endpoints.docsResolveLinkUrl` as a\n * runtime fallback (prop → runtime → default). */\n resolveLinkEndpoint?: string;\n /** Base route path for URL navigation. */\n baseRoute: string;\n\n /** Empty state copy when no doc is selected. */\n emptyStateText?: string;\n\n /** Whether to render the doc-search bar (bound to chat). */\n showAIChat?: boolean;\n\n /** Folder-index filename (default `'README.md'`). */\n folderIndexFile?: string;\n\n /** Back-button shown above the title. Mirrors `<DevSectionPage>` /\n * `<HelpCenterList>` / `<LegalDocumentPage>` so every embeddable surface\n * shares the same chrome. Defaults to `{ label: 'Back to home', href: '/' }`.\n * Pass `false` to hide; pass `{ href: '/docs' }` etc. when the embed's\n * home isn't `/`. */\n backButton?: { label?: string; href?: string } | false;\n}\n\nexport function DocViewer(props: DocViewerProps) {\n return <DocViewerContent {...props} />;\n}\n\nfunction DocViewerContent({\n sourceId,\n renderContent,\n renderSkeleton,\n chatSource,\n title,\n subtitle,\n colorPalette = DEFAULT_DOC_VIEWER_PALETTE,\n className = '',\n shell = true,\n docPath,\n sidebarLabel = 'DOCUMENTATION',\n structureEndpoint,\n contentEndpoint,\n searchEndpoint,\n resolveLinkEndpoint,\n baseRoute,\n emptyStateText,\n showAIChat = false,\n folderIndexFile,\n backButton,\n}: DocViewerProps) {\n // Default endpoints derived from sourceId. Hub callers omit the props in 99%\n // of cases; the override is for embed contexts where the doc-viewer sits\n // behind a non-standard route.\n const resolvedStructureEndpoint = structureEndpoint ?? `/api/docs/sources/${sourceId}/structure`;\n const resolvedContentEndpoint = contentEndpoint ?? `/api/docs/sources/${sourceId}/content`;\n // Resolve-link endpoint chain (prop → ChatRuntime.endpoints → hub default)\n // + the full fetch + JSON-parse pipeline live in `useDocsResolveLink`.\n // Keeping it factored out as a proper hook makes the contract reusable\n // by any embedder rendering doc content outside `<DocViewer>` (custom\n // markdown renderers, link-resolver previews, etc.) and keeps this\n // component focused on layout + state.\n const resolveLink = useDocsResolveLink(sourceId, resolveLinkEndpoint);\n const {\n structure,\n selectedPath,\n content,\n isLoadingStructure,\n isLoadingContent,\n error,\n expandedNodes,\n selectNode,\n toggleNode,\n navigateToDoc,\n } = useDocumentTree(\n {\n structureEndpoint: resolvedStructureEndpoint,\n contentEndpoint: resolvedContentEndpoint,\n baseRoute,\n folderIndexFile,\n },\n docPath,\n );\n\n const { activeSection, handleSectionClick } = useScrollSpy(content?.sections);\n\n const docNav = useDocNavigation();\n\n // Back-button config — mirrors `<DevSectionPage>` so the docs surface\n // matches every other embeddable page's chrome. Default target is `/`\n // (the embed's home); pass `backButton: false` to hide entirely, or\n // override the href when the embed's home isn't `/`.\n const router = useRouter();\n const backCfg =\n backButton === false\n ? null\n : {\n label: backButton?.label ?? 'Back to home',\n onClick: () => router.push(backButton?.href ?? '/'),\n };\n const docSearch = useDocSearch({\n source: chatSource,\n baseRoute,\n searchEndpoint,\n onNavigate: path => navigateToDoc(path, { fromInternalLink: true }),\n onInPageSwap: path => docNav.navigate(path),\n });\n\n const renderedContent = useMemo(() => {\n if (!content) return null;\n return renderContent(content, {\n onInternalLinkClick: navigateToDoc,\n // Relative-link base = the RENDERED document's path, NOT `selectedPath`.\n // They diverge for a no-README folder: selection stays on the folder\n // (e.g. `repo/diagrams`) while the body is its first descendant doc\n // (e.g. `repo/diagrams/architecture/README.md`, via `findFirstDocPath`).\n // Resolving `./sibling.mmd` against the folder would 404; resolving it\n // against `content.path` lands in the descendant's directory. The DAL\n // sets `content.path` to the served doc in all cases (file / README\n // folder / first-child fallback), so this is correct everywhere.\n currentPath: content.path,\n sourceId,\n onResolveLink: resolveLink,\n });\n }, [content, renderContent, navigateToDoc, sourceId, resolveLink]);\n\n // Selected node's documentType drives:\n // - which skeleton the caller renders during fetch (markdown vs embed)\n // - the article max-width + sticky-nav visibility (markdown only)\n // `undefined` documentType is treated as `'markdown'` (per the DocNode\n // discriminator's documented default).\n const selectedNodeDocType =\n selectedPath && structure.length > 0 ? findDocNodeByPath(selectedPath, structure)?.documentType : undefined;\n // During loading, the in-flight content's type isn't known yet — fall back\n // to the selected node's type (or markdown if neither is set).\n const activeDocType = content?.documentType ?? selectedNodeDocType;\n const isMarkdownContent = !activeDocType || activeDocType === 'markdown';\n const showStickyNav = isMarkdownContent;\n\n const stickyNavSections = content?.sections?.map(s => ({ id: s.id, label: s.title })) ?? [];\n\n const isColorValue =\n colorPalette.background.startsWith('#') ||\n colorPalette.background.startsWith('rgb') ||\n colorPalette.background.startsWith('var(');\n\n const bgStyle = isColorValue ? { backgroundColor: colorPalette.background } : {};\n const bgClass = !isColorValue ? colorPalette.background : '';\n const containerBgStyle =\n colorPalette.containerBackground !== 'transparent' ? { backgroundColor: colorPalette.containerBackground } : {};\n\n const defaultEmptyText =\n structure.length > 0\n ? 'Select a document from the sidebar to view'\n : 'No documents yet. Add content from the admin panel.';\n const resolvedEmptyText = emptyStateText || defaultEmptyText;\n\n // Unified header: title/subtitle route through the canonical (frozen)\n // `PageLayout` `TitleBlock` (text-h2) — same as every other help-center page —\n // so the docs hub shares one header. The `gap-10` column then holds the search\n // bar + content grid. `colorPalette` / `className` / `bgStyle` flow through the\n // shell's contentClassName + an inner style-passthrough wrapper.\n const inner = (\n <div style={{ ...bgStyle, ...containerBgStyle }}>\n <PageLayout title={title} subtitle={subtitle} titleSize=\"h1\" titleWrap backButton={backCfg ?? undefined}>\n <div className=\"flex w-full flex-col gap-10\">\n {showAIChat && (\n <DocSearchBar\n placeholder={`Search ${sidebarLabel?.toLowerCase() || 'documents'}...`}\n query={docSearch.query}\n onQueryChange={docSearch.setQuery}\n results={docSearch.results}\n isLoading={docSearch.isLoading}\n onResultSelect={docSearch.handleResultSelect}\n showDropdown={docSearch.keepDropdownOpen}\n />\n )}\n\n {error && (\n <div className=\"flex justify-center\">\n <div className=\"max-w-md rounded-lg border border-ods-border bg-ods-card p-8 text-center\">\n <h2 className=\"text-ods-text-primary text-h3\">Error Loading Documents</h2>\n <p className=\"mt-2 text-ods-text-secondary\">{error}. Please try again later.</p>\n </div>\n </div>\n )}\n\n {!error && (\n <div className=\"flex flex-1 flex-col items-start gap-6 lg:flex-row lg:gap-10\">\n <div className=\"w-full lg:w-[320px] lg:shrink-0\">\n <div className=\"lg:sticky lg:top-20\">\n {isLoadingStructure ? (\n <CategorySidebarSkeleton />\n ) : (\n <>\n <PersistentMobileDropdown isLoading={false}>\n <MobileNavigationDropdown\n nodes={structure}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={selectNode}\n onToggleExpand={toggleNode}\n isLoading={false}\n folderIndexFile={folderIndexFile}\n />\n </PersistentMobileDropdown>\n\n <PersistentSidebar isLoading={false}>\n <div className=\"hidden lg:block\">\n <div className=\"space-y-4\">\n <h3 className=\"font-semibold text-ods-text-secondary text-h5\">{sidebarLabel}</h3>\n <MultiLevelNavigation\n nodes={structure}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={selectNode}\n onToggleExpand={toggleNode}\n isLoading={false}\n folderIndexFile={folderIndexFile}\n />\n </div>\n </div>\n </PersistentSidebar>\n </>\n )}\n </div>\n </div>\n\n <div className=\"w-full min-w-0 flex-1\">\n <div\n className={`grid grid-cols-1 ${\n // \"On this page\" right column only makes sense for\n // MARKDOWN content (PDFs / Sheets / Figma / file have no\n // sections to navigate to). Gating the grid template on\n // `isMarkdownContent` also suppresses the section-skeleton\n // bars during embed loads — the user-reported \"skeleton\n // shouldn't be on file pages\" bug.\n isMarkdownContent &&\n ((showStickyNav && stickyNavSections.length > 0) || isLoadingContent || isLoadingStructure)\n ? 'lg:grid-cols-[1fr_280px]'\n : ''\n } gap-8`}\n >\n {/* min-w-0: grid items default to min-width:auto, which would\n let a long unbreakable token push this column past the\n track width. Pair with the inherited overflow-wrap:anywhere\n (app-globals.css) so content wraps instead of overflowing. */}\n <div className={`w-full min-w-0 ${isMarkdownContent ? 'mx-auto max-w-4xl' : ''}`}>\n <article className=\"space-y-2\">\n {isLoadingContent || isLoadingStructure ? (\n renderSkeleton(selectedNodeDocType)\n ) : !content ? (\n <div className=\"py-16 text-center\">\n <p className=\"text-ods-text-secondary text-h4\">{resolvedEmptyText}</p>\n </div>\n ) : (\n renderedContent\n )}\n </article>\n </div>\n\n {isMarkdownContent && (isLoadingContent || isLoadingStructure) && (\n <div className=\"hidden lg:block\">\n <div className=\"sticky top-24\">\n <div className=\"mb-5 h-[14px] w-28 animate-pulse rounded bg-ods-border\" />\n <div className=\"space-y-0\">\n {[130, 170, 190, 220, 110, 200, 80, 100, 120, 140, 90].map((w, i) => (\n <div\n key={i}\n className={`border-l-2 py-[13px] pl-3 ${\n i === 0 ? 'border-ods-accent' : 'border-transparent'\n }`}\n >\n <div className=\"h-[13px] animate-pulse rounded bg-ods-border\" style={{ width: w }} />\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {showStickyNav && content && stickyNavSections.length > 0 && !isLoadingContent && (\n <div className=\"hidden lg:block\">\n <div className=\"sticky top-24\">\n <h3 className=\"mb-4 font-semibold text-ods-text-secondary text-h5\">ON THIS PAGE</h3>\n <StickySectionNav\n sections={stickyNavSections}\n activeSection={activeSection}\n onSectionClick={handleSectionClick}\n ribbonPosition=\"left\"\n ribbonColor=\"var(--ods-accent)\"\n />\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n </PageLayout>\n </div>\n );\n\n // `shell` true → standalone `<PageShell>`; false → padding-only box (no nested\n // <main>) for hosts whose layout already provides the container. Both carry the\n // palette/className via the same `page-shell-content` styling hook.\n return shell ? (\n <PageShell contentClassName={`${bgClass} ${className}`}>{inner}</PageShell>\n ) : (\n <div className={`page-shell-content ${bgClass} ${className}`.trim()}>{inner}</div>\n );\n}\n","import type React from 'react';\nimport { cn } from '../../utils/cn';\n\ninterface UnifiedSkeletonProps extends React.HTMLAttributes<HTMLDivElement> {\n className?: string;\n variant?: 'default' | 'text' | 'circular' | 'rectangular';\n width?: string | number;\n height?: string | number;\n /**\n * Disable animation for users who prefer reduced motion\n */\n animate?: boolean;\n /**\n * Accessibility label for screen readers\n */\n 'aria-label'?: string;\n}\n\n/**\n * UnifiedSkeleton Component\n *\n * Base skeleton component with consistent styling across the application.\n *\n * Design Specifications:\n * - Color: #2A2A2A base with #3A3A3A pulse highlight\n * - Animation: 1.5s pulse duration, ease-in-out timing\n * - Responsive: Mobile-first approach with proper scaling\n * - Accessibility: ARIA labels and reduced motion support\n *\n * @param variant - Shape variant: default (rounded), text (rounded-sm), circular, rectangular\n * @param width - Custom width (defaults to full width)\n * @param height - Custom height (defaults to auto based on variant)\n * @param animate - Enable/disable animation (respects prefers-reduced-motion)\n * @param aria-label - Accessibility label for screen readers\n */\nexport function UnifiedSkeleton({\n className,\n variant = 'default',\n width,\n height,\n animate = true,\n 'aria-label': ariaLabel,\n ...props\n}: UnifiedSkeletonProps) {\n const baseClasses = 'bg-ods-skeleton';\n\n const variantClasses = {\n default: 'rounded-md',\n text: 'rounded-sm',\n circular: 'rounded-full',\n rectangular: 'rounded-none',\n };\n\n const defaultHeights = {\n default: 'h-4',\n text: 'h-4',\n circular: 'h-12 w-12',\n rectangular: 'h-4',\n };\n\n const animationClasses = animate ? 'animate-pulse motion-reduce:animate-none' : '';\n\n const style: React.CSSProperties = {\n width: width,\n height: height,\n };\n\n return (\n <div\n className={cn(\n baseClasses,\n variantClasses[variant],\n !height && !style.height && defaultHeights[variant],\n !width && !style.width && variant !== 'circular' && 'w-full',\n animationClasses,\n className,\n )}\n style={style}\n role=\"status\"\n aria-label={ariaLabel || 'Loading content'}\n {...props}\n />\n );\n}\n\n/**\n * Text skeleton variants for consistent typography loading\n */\nexport const TextSkeleton = {\n /**\n * Large heading skeleton (h1, h2)\n */\n Heading: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"text\"\n className={cn('h-8 md:h-10 lg:h-12', className)}\n aria-label=\"Loading heading\"\n {...props}\n />\n ),\n\n /**\n * Medium heading skeleton (h3, h4)\n */\n Subheading: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"text\"\n className={cn('h-6 md:h-7 lg:h-8', className)}\n aria-label=\"Loading subheading\"\n {...props}\n />\n ),\n\n /**\n * Body text skeleton\n */\n Body: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton variant=\"text\" className={cn('h-4 md:h-5', className)} aria-label=\"Loading text\" {...props} />\n ),\n\n /**\n * Small text skeleton (captions, metadata)\n */\n Caption: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton variant=\"text\" className={cn('h-3 md:h-4', className)} aria-label=\"Loading caption\" {...props} />\n ),\n};\n\n/**\n * Interactive element skeletons\n */\nexport const InteractiveSkeleton = {\n /**\n * Button skeleton with proper touch targets\n */\n Button: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn('h-10 w-32 md:h-12 md:w-40', className)}\n aria-label=\"Loading button\"\n {...props}\n />\n ),\n\n /**\n * Input field skeleton\n */\n Input: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn('h-10 w-full md:h-12', className)}\n aria-label=\"Loading input field\"\n {...props}\n />\n ),\n\n /**\n * Filter chip skeleton\n */\n Chip: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn('h-8 w-20 rounded-full', className)}\n aria-label=\"Loading filter\"\n {...props}\n />\n ),\n};\n\n/**\n * Media skeletons for images and icons\n */\nexport const MediaSkeleton = {\n /**\n * Avatar/profile image skeleton\n */\n Avatar: ({\n size = 'md',\n className,\n ...props\n }: Omit<UnifiedSkeletonProps, 'variant'> & { size?: 'sm' | 'md' | 'lg' }) => {\n const sizeClasses = {\n sm: 'h-8 w-8',\n md: 'h-12 w-12',\n lg: 'h-16 w-16',\n };\n\n return (\n <UnifiedSkeleton\n variant=\"circular\"\n className={cn(sizeClasses[size], className)}\n aria-label=\"Loading profile image\"\n {...props}\n />\n );\n },\n\n /**\n * Card image skeleton (1200×630 OG aspect — matches entity-cards default\n * so loading state never causes a height shift when card data arrives)\n */\n CardImage: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn('aspect-[1200/630] w-full', className)}\n aria-label=\"Loading image\"\n {...props}\n />\n ),\n\n /**\n * Icon skeleton\n */\n Icon: ({\n size = 'md',\n className,\n ...props\n }: Omit<UnifiedSkeletonProps, 'variant'> & { size?: 'sm' | 'md' | 'lg' }) => {\n const sizeClasses = {\n sm: 'h-4 w-4',\n md: 'h-6 w-6',\n lg: 'h-8 w-8',\n };\n\n return (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn(sizeClasses[size], className)}\n aria-label=\"Loading icon\"\n {...props}\n />\n );\n },\n};\n","import { cn } from '../../utils/cn';\nimport { TextSkeleton, MediaSkeleton, InteractiveSkeleton } from './unified-skeleton';\n\ninterface CardSkeletonProps {\n className?: string;\n /**\n * Card layout variant\n */\n variant?: 'vendor' | 'blog' | 'category' | 'alternative';\n /**\n * Show action buttons area\n */\n showActions?: boolean;\n /**\n * Show metadata footer\n */\n showMetadata?: boolean;\n /** Optional tailwind classes to override the card container background & border */\n containerClassName?: string;\n}\n\n/**\n * Unified card skeleton component for consistent card loading states\n *\n * Supports different card types used across the application:\n * - vendor: Vendor cards with logo, title, description, and actions\n * - blog: Blog post cards with image, title, summary, and metadata \n * - category: Category cards with icon, title, and description\n * - alternative: Alternative vendor cards in comparison lists\n */\nexport function CardSkeleton({\n className,\n containerClassName,\n variant = 'vendor',\n showActions = true,\n showMetadata = true,\n ...props\n}: CardSkeletonProps) {\n const cardContent = {\n vendor: <VendorCardContent showMetadata={showMetadata} />,\n blog: <BlogCardContent showActions={showActions} showMetadata={showMetadata} />,\n category: <CategoryCardContent />,\n alternative: <AlternativeCardContent showActions={showActions} />,\n };\n\n return (\n <div\n className={cn(\n containerClassName || 'border border-ods-border bg-ods-card',\n 'overflow-hidden rounded-lg',\n // Flex layouts for certain variants\n variant === 'blog' && 'flex h-full flex-col',\n variant === 'vendor' && 'flex h-full flex-col',\n className,\n )}\n role=\"status\"\n aria-label={`Loading ${variant} card`}\n {...props}\n >\n {cardContent[variant]}\n </div>\n );\n}\n\n/**\n * Vendor card skeleton content - matches exact VendorCard structure\n */\nfunction VendorCardContent({ showMetadata }: { showMetadata: boolean }) {\n return (\n <div className=\"flex flex-col gap-3 p-4\">\n {/* Header Section - Row layout matching actual VendorCard */}\n <div className=\"flex w-full items-start gap-3\">\n {/* Logo Frame - 60px width fixed, matching actual structure */}\n <div className=\"flex h-[60px] w-[60px] flex-shrink-0 items-center justify-center rounded-lg border border-ods-border bg-ods-bg p-2\">\n <MediaSkeleton.Avatar size=\"sm\" className=\"h-11 w-11\" />\n </div>\n\n {/* Text Container - Column layout, matching actual structure */}\n <div className=\"flex min-w-0 flex-1 flex-col justify-center space-y-1 py-2\">\n {/* Title - Single line with proper width */}\n <TextSkeleton.Subheading className=\"w-3/4\" />\n {/* Category - Single line, shorter */}\n <TextSkeleton.Caption className=\"w-1/2\" />\n </div>\n </div>\n\n {/* Description Section - Fixed 48px height matching actual VendorCard */}\n <div className=\"flex h-12 w-full items-center overflow-hidden\">\n <div className=\"w-full space-y-1\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-2/3\" />\n </div>\n </div>\n\n {/* Footer Section - Responsive layout matching actual structure */}\n {showMetadata && (\n <div className=\"flex w-full min-w-0 items-center justify-between gap-2\">\n {/* Stats Container - Flexible width, no overflow */}\n <div className=\"flex min-w-0 flex-shrink items-center gap-3 md:gap-4\">\n {/* OpenMSP Score skeleton */}\n <div className=\"flex flex-shrink-0 items-center gap-1\">\n <MediaSkeleton.Icon size=\"sm\" className=\"h-5 w-5\" />\n <TextSkeleton.Caption className=\"w-8\" />\n </div>\n\n {/* GitHub Stats skeleton */}\n <div className=\"flex flex-shrink-0 items-center gap-1\">\n <MediaSkeleton.Icon size=\"sm\" className=\"h-5 w-5\" />\n <TextSkeleton.Caption className=\"w-10\" />\n </div>\n </div>\n\n {/* Tag Section - Contained within card boundaries */}\n <div className=\"flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 rounded border border-ods-border bg-ods-bg px-2.5 py-1.5\">\n <div className=\"flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-sm bg-ods-border\">\n <MediaSkeleton.Icon size=\"sm\" className=\"h-2.5 w-2.5\" />\n </div>\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Blog card skeleton content - matches fixed height structure\n */\nfunction BlogCardContent({ showActions, showMetadata }: { showActions: boolean; showMetadata: boolean }) {\n return (\n <>\n {/* Image Section — OG 1200×630 aspect (matches blog-card.tsx loaded state) */}\n <div className=\"blog-card-image-container relative aspect-[1200/630] w-full overflow-hidden bg-ods-bg\">\n <MediaSkeleton.CardImage />\n </div>\n\n {/* Content - Fixed height structure to match BlogCard */}\n <div className=\"flex flex-grow flex-col p-4\">\n {/* Title Section - Fixed 2 lines with vertical centering */}\n <div className=\"mb-3 flex min-h-[50.4px] items-center md:min-h-[56px] lg:min-h-[61.6px]\">\n <div className=\"w-full space-y-1\">\n <TextSkeleton.Subheading className=\"w-full\" />\n <TextSkeleton.Subheading className=\"w-3/4\" />\n </div>\n </div>\n\n {/* Chips Section - Fixed single line height */}\n <div className=\"mb-3 flex h-[28px] items-center gap-2\">\n <InteractiveSkeleton.Chip className=\"w-16\" />\n <InteractiveSkeleton.Chip className=\"w-12\" />\n </div>\n\n {/* Description Section - Fixed 2 lines with vertical centering */}\n <div className=\"mb-3 flex min-h-[42px] items-center md:min-h-[45px] lg:min-h-[48px]\">\n <div className=\"w-full space-y-1\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-1/2\" />\n </div>\n </div>\n\n {/* Actions - only if requested */}\n {showActions && (\n <div className=\"pt-2\">\n <InteractiveSkeleton.Button className=\"h-8 w-24\" />\n </div>\n )}\n\n {/* Metadata footer - Matches BlogMeta horizontal layout */}\n {showMetadata && (\n <div className=\"mt-auto\">\n <div className=\"flex items-center justify-between gap-4 border-t border-ods-border pt-4\">\n {/* Author section - matches AuthorMeta */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <MediaSkeleton.Avatar size=\"sm\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n\n {/* Date and reading time section - matches BlogMeta right side */}\n <div className=\"flex shrink-0 items-center gap-3 text-ods-text-muted\">\n <TextSkeleton.Caption className=\"w-12\" />\n {/* Separator dot */}\n <div className=\"h-1 w-1 rounded-full bg-ods-skeleton\"></div>\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </div>\n </div>\n )}\n </div>\n </>\n );\n}\n\n/**\n * Category card skeleton content\n */\nfunction CategoryCardContent() {\n return (\n <div className=\"space-y-4 p-4 md:p-6\">\n {/* Icon grid */}\n <div className=\"flex gap-2 md:gap-3\">\n {Array.from({ length: 6 }).map((_, index) => (\n <MediaSkeleton.Icon key={index} size=\"lg\" className=\"flex-shrink-0\" />\n ))}\n </div>\n\n {/* Content */}\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <TextSkeleton.Subheading className=\"w-3/4\" />\n <TextSkeleton.Caption className=\"w-1/2\" />\n </div>\n\n <div className=\"flex items-start justify-between gap-4 md:items-end md:gap-6\">\n <div className=\"flex-1 space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-2/3\" />\n </div>\n\n <InteractiveSkeleton.Button className=\"h-10 w-10 flex-shrink-0 md:h-12 md:w-12\" />\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Alternative card skeleton content (for vendor alternatives/comparisons)\n */\nfunction AlternativeCardContent({ showActions }: { showActions: boolean }) {\n return (\n <div className=\"flex items-start gap-4 p-4\">\n <MediaSkeleton.Avatar size=\"md\" className=\"flex-shrink-0\" />\n\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-center justify-between\">\n <TextSkeleton.Subheading className=\"w-1/3\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n\n <div className=\"space-y-1\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-5/6\" />\n </div>\n\n {showActions && (\n <div className=\"pt-2\">\n <InteractiveSkeleton.Button className=\"h-8 w-20\" />\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * Grid of card skeletons for loading lists\n */\nexport function CardSkeletonGrid({\n count = 6,\n variant = 'vendor',\n className,\n containerClassName,\n ...props\n}: {\n count?: number;\n variant?: CardSkeletonProps['variant'];\n className?: string;\n containerClassName?: string;\n} & Omit<CardSkeletonProps, 'variant'>) {\n return (\n <div\n className={cn(\n 'grid gap-4 md:gap-6',\n // Responsive grid based on card type\n variant === 'vendor' && 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n variant === 'blog' && 'grid-cols-1 md:grid-cols-2',\n variant === 'category' && 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',\n variant === 'alternative' && 'grid-cols-1',\n className,\n )}\n role=\"status\"\n aria-label={`Loading ${count} ${variant} cards`}\n >\n {Array.from({ length: count }, (_, index) => (\n <CardSkeleton key={index} variant={variant} containerClassName={containerClassName} {...props} />\n ))}\n </div>\n );\n}\n","import type React from 'react';\nimport { cn } from '../../utils/cn';\nimport { CardSkeletonGrid } from './card-skeleton';\nimport { UnifiedSkeleton, TextSkeleton, MediaSkeleton, InteractiveSkeleton } from './unified-skeleton';\n\ninterface PageLayoutSkeletonProps {\n className?: string;\n}\n\n/**\n * Announcement bar skeleton that matches the AnnouncementBar component\n */\nexport function AnnouncementBarSkeleton() {\n return (\n <div className=\"relative w-full animate-pulse bg-ods-skeleton\">\n <div className=\"relative flex w-full flex-row items-center\">\n <div className=\"relative box-border flex w-full flex-row items-center justify-start gap-4 py-3 pl-4 pr-12 md:gap-6 md:py-4 md:pl-6 md:pr-16\">\n {/* Logo skeleton */}\n <div className=\"relative h-6 w-6 shrink-0 rounded bg-ods-border md:h-8 md:w-8\"></div>\n\n {/* Text content skeleton */}\n <div className=\"min-w-0 flex-1 space-y-1 md:space-y-2\">\n <div className=\"h-[14px] w-3/4 max-w-md rounded bg-ods-border md:h-[18px]\"></div>\n <div className=\"hidden h-[12px] w-full max-w-lg rounded bg-ods-border md:block md:h-[18px]\"></div>\n </div>\n\n {/* Close button skeleton */}\n <div className=\"absolute right-2 top-2 h-6 w-6 rounded bg-ods-border\"></div>\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Header skeleton that matches the ClientOnlyHeader placeholder but with proper animations\n */\nexport function HeaderSkeleton() {\n return (\n <header className=\"sticky top-0 z-40 flex w-full animate-pulse items-center justify-between border-b border-ods-border bg-ods-card bg-ods-card/95 px-4 py-3 backdrop-blur-sm md:px-[80px] md:py-[12px]\">\n {/* Left: Logo skeleton */}\n <div className=\"flex min-w-0 flex-1 items-center justify-start\">\n <div className=\"h-[26px] w-[110px] rounded bg-ods-skeleton md:h-8 md:w-[137px]\"></div>\n </div>\n\n {/* Center: Navigation skeleton - hidden on mobile, visible on desktop */}\n <nav className=\"hidden min-w-0 flex-1 basis-1/3 items-center justify-center gap-2 md:flex\">\n <div className=\"flex items-center gap-2\">\n <InteractiveSkeleton.Button className=\"h-10 w-24\" />\n <InteractiveSkeleton.Button className=\"h-10 w-24\" />\n <InteractiveSkeleton.Button className=\"h-10 w-24\" />\n </div>\n </nav>\n\n {/* Right: Actions skeleton */}\n <div className=\"flex min-w-0 flex-1 items-center justify-end gap-4\">\n {/* Mobile: Show hamburger skeleton */}\n <div className=\"md:hidden\">\n <InteractiveSkeleton.Button className=\"h-10 w-10\" />\n </div>\n\n {/* Desktop: Show action buttons skeletons */}\n <div className=\"hidden items-center gap-4 md:flex\">\n <InteractiveSkeleton.Button className=\"h-10 w-10\" />\n <InteractiveSkeleton.Button className=\"h-10 w-32\" />\n <InteractiveSkeleton.Button className=\"h-10 w-20\" />\n </div>\n </div>\n </header>\n );\n}\n\n/**\n * Hero section skeleton for static content areas\n */\nexport function HeroSkeleton() {\n return (\n <section\n className=\"flex w-full animate-pulse flex-col items-center justify-center px-4 py-12 text-center md:px-8 md:py-20\"\n style={{\n background: 'radial-gradient(circle at 50% 0%, #242323 0%, #1A1A1A 100%)',\n }}\n >\n {/* Title skeleton */}\n <TextSkeleton.Heading className=\"mb-4 h-12 w-full max-w-4xl md:mb-6 md:h-20 lg:h-24\" />\n\n {/* Subtitle skeleton */}\n <div className=\"mb-8 w-full max-w-4xl space-y-3 px-2 md:mb-10\">\n <TextSkeleton.Body className=\"h-5 md:h-7\" />\n <TextSkeleton.Body className=\"mx-auto h-5 w-3/4 md:h-7\" />\n </div>\n\n {/* CTA Button skeleton */}\n <InteractiveSkeleton.Button className=\"h-12 w-full md:w-64\" />\n </section>\n );\n}\n\n/**\n * Search container skeleton with filters\n */\nexport function SearchContainerSkeleton({\n className,\n showFilters = true,\n}: PageLayoutSkeletonProps & { showFilters?: boolean }) {\n return (\n <div className={cn('space-y-4', className || '')}>\n {/* Search input and button */}\n <div className=\"flex gap-2 md:gap-4\">\n <InteractiveSkeleton.Input className=\"flex-1\" />\n <InteractiveSkeleton.Button className=\"w-32 md:w-40\" />\n </div>\n\n {/* Filter chips */}\n {showFilters && (\n <div className=\"flex flex-wrap gap-2\">\n <InteractiveSkeleton.Chip />\n <InteractiveSkeleton.Chip className=\"w-24\" />\n <InteractiveSkeleton.Chip className=\"w-16\" />\n <InteractiveSkeleton.Chip className=\"w-20\" />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Category sidebar skeleton for filtering pages\n */\nexport function CategorySidebarSkeleton({ className }: PageLayoutSkeletonProps) {\n // Matches the actual MultiLevelNavigation sidebar with folder/file icons, README badges, and chevrons\n const items = [\n { type: 'file', width: 'w-20', hasBadge: false },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'folder', width: 'w-20', hasBadge: true },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'folder', width: 'w-16', hasBadge: true },\n { type: 'folder', width: 'w-20', hasBadge: true },\n { type: 'folder', width: 'w-28', hasBadge: true },\n { type: 'folder', width: 'w-16', hasBadge: true },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'file', width: 'w-28' },\n { type: 'file', width: 'w-36' },\n ];\n\n return (\n <div className={cn('w-full lg:w-[320px]', className)}>\n {/* DATA ROOM label */}\n <UnifiedSkeleton className=\"mb-4 h-[14px] w-24 rounded\" />\n\n {/* Navigation items — each has card background matching actual MultiLevelNavigation */}\n <div className=\"space-y-1.5\">\n {items.map((item, index) => (\n <div\n key={index}\n className={cn(\n 'flex min-h-[50px] items-center justify-between rounded-lg border border-ods-border px-4 py-4',\n index === 0 ? 'bg-ods-accent/20' : 'bg-ods-card',\n )}\n >\n <div className=\"flex items-center gap-2.5\">\n <UnifiedSkeleton className=\"h-4 w-4 shrink-0 rounded\" />\n <UnifiedSkeleton className={`h-[14px] ${item.width} rounded`} />\n </div>\n <div className=\"flex h-[18px] items-center gap-2\">\n {item.hasBadge && <UnifiedSkeleton className=\"h-[18px] w-14 rounded\" />}\n {item.type === 'folder' && <UnifiedSkeleton className=\"h-4 w-4 rounded\" />}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/**\n * Breadcrumb navigation skeleton\n */\nexport function BreadcrumbSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn('mb-6 flex items-center space-x-1', className)}>\n <TextSkeleton.Caption className=\"w-16\" />\n <UnifiedSkeleton variant=\"default\" className=\"h-4 w-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-24\" />\n <UnifiedSkeleton variant=\"default\" className=\"h-4 w-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-32\" />\n <UnifiedSkeleton variant=\"default\" className=\"h-4 w-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-24\" />\n </div>\n );\n}\n\n/**\n * Results header skeleton with count and sorting\n */\nexport function ResultsHeaderSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn('mb-6 flex flex-col items-start justify-between gap-4 md:flex-row md:items-center', className)}>\n <div className=\"space-y-1\">\n <TextSkeleton.Body className=\"w-48\" />\n {/* <TextSkeleton.Caption className=\"w-32\" /> */}\n </div>\n </div>\n );\n}\n\n/**\n * Two-column layout skeleton (sidebar + main content)\n */\nexport function TwoColumnLayoutSkeleton({\n className,\n sidebarContent,\n mainContent,\n sidebarPosition = 'left',\n}: PageLayoutSkeletonProps & {\n sidebarContent?: React.ReactNode;\n mainContent?: React.ReactNode;\n sidebarPosition?: 'left' | 'right';\n}) {\n const sidebar = sidebarContent || <CategorySidebarSkeleton />;\n const main = mainContent || <CardSkeletonGrid count={6} />;\n\n return (\n <div\n className={cn(\n 'grid grid-cols-1 gap-6 lg:grid-cols-[320px_1fr] lg:gap-8',\n sidebarPosition === 'right' && 'lg:grid-cols-[1fr_320px]',\n className,\n )}\n >\n {sidebarPosition === 'left' ? (\n <>\n <aside className=\"order-2 lg:order-1\">{sidebar}</aside>\n <main className=\"order-1 lg:order-2\">{main}</main>\n </>\n ) : (\n <>\n <main className=\"order-1\">{main}</main>\n <aside className=\"order-2\">{sidebar}</aside>\n </>\n )}\n </div>\n );\n}\n\n/**\n * Article/blog post layout skeleton\n */\nexport function ArticleLayoutSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <article className={cn('mx-auto max-w-4xl', className)}>\n {/* Article header */}\n <header className=\"mb-8 space-y-4 md:mb-12 md:space-y-6\">\n {/* Category/tags */}\n <div className=\"flex gap-2\">\n <InteractiveSkeleton.Chip />\n <InteractiveSkeleton.Chip className=\"w-16\" />\n </div>\n\n {/* Title */}\n <div className=\"space-y-3\">\n <TextSkeleton.Heading className=\"w-full\" />\n <TextSkeleton.Heading className=\"w-3/4\" />\n </div>\n\n {/* Metadata */}\n <div className=\"flex items-center gap-4 border-t border-ods-divider pt-4\">\n <div className=\"flex items-center gap-2\">\n <MediaSkeleton.Avatar size=\"sm\" />\n <TextSkeleton.Caption className=\"w-24\" />\n </div>\n <TextSkeleton.Caption className=\"w-20\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </header>\n\n {/* Featured image */}\n <div className=\"mb-8 md:mb-12\">\n <MediaSkeleton.CardImage className=\"h-64 w-full md:h-96\" />\n </div>\n\n {/* Article content */}\n <div className=\"prose prose-invert max-w-none space-y-6\">\n {/* Paragraphs */}\n {Array.from({ length: 12 }).map((_, index) => (\n <div key={index} className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-5/6\" />\n <TextSkeleton.Body className=\"w-3/4\" />\n </div>\n ))}\n\n {/* Subheading in content */}\n <div className=\"py-4\">\n <TextSkeleton.Subheading className=\"mb-4 w-2/3\" />\n <div className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-4/5\" />\n </div>\n </div>\n </div>\n </article>\n );\n}\n\n/**\n * Vendor Detail Layout Skeleton - Complete vendor detail page structure\n * Matches the refactored vendor detail page with proper platform colors and responsive layout\n */\nexport function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <main className={cn('min-h-screen bg-ods-bg', className)}>\n <div className=\"mx-auto max-w-[1920px] px-6 py-6 md:px-20 md:py-10\">\n {/* Breadcrumb */}\n <BreadcrumbSkeleton className=\"mb-6\" />\n\n {/* Main Layout Container */}\n <div className=\"flex flex-col lg:flex-row lg:gap-10\">\n {/* Left Content Area */}\n <div className=\"min-w-0 flex-1\">\n {/* Vendor Hero Section */}\n <div className=\"mb-10\">\n {/* Header - Logo and Title Side by Side */}\n <div className=\"mb-6 flex gap-6\">\n <div className=\"h-20 w-20 flex-shrink-0 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex flex-col gap-2\">\n {/* Large title skeleton */}\n <div className=\"h-12 w-80 max-w-full animate-pulse rounded bg-ods-skeleton md:h-16 lg:h-20\"></div>\n {/* Category text */}\n <div className=\"h-5 w-32 animate-pulse rounded bg-ods-skeleton md:h-6\"></div>\n </div>\n\n {/* Pricing tags */}\n <div className=\"flex items-center gap-2\">\n <div className=\"h-8 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-8 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n\n {/* Vendor Image Display Skeleton */}\n <div className=\"mb-2 h-[300px] w-full animate-pulse rounded-lg border border-ods-border bg-ods-card md:h-[400px] lg:h-[500px]\"></div>\n <div className=\"text-center\">\n <div className=\"mx-auto h-4 w-24 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n\n {/* Mobile Sidebar - Show on mobile only, positioned after title */}\n <div className=\"mb-10 lg:hidden\">\n <div className=\"space-y-4\">\n {/* Deploy Button */}\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n\n {/* Voting Buttons */}\n <div className=\"space-y-2\">\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n </div>\n\n {/* GitHub Score Section */}\n <div className=\"overflow-hidden rounded-lg border border-ods-border\">\n {/* Header */}\n <div className=\"flex items-center gap-3 bg-ods-card p-4\">\n <div className=\"h-8 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex flex-col gap-1\">\n <div className=\"h-4 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n\n {/* Stats */}\n <div className=\"space-y-3 p-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3\">\n <div className=\"h-6 w-6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex items-end gap-1\">\n <div className=\"h-4 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Action Buttons */}\n <div className=\"space-y-3\">\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n </div>\n </div>\n </div>\n\n {/* Alternatives Container */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-48 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n\n {/* Open Source Alternatives */}\n <div className=\"flex flex-col gap-4\">\n <div className=\"h-5 w-48 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex flex-wrap gap-2\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div\n key={i}\n className=\"h-10 w-24 animate-pulse rounded-lg border border-ods-border bg-ods-card\"\n ></div>\n ))}\n </div>\n </div>\n\n {/* Commercial Alternatives */}\n <div className=\"flex flex-col gap-4\">\n <div className=\"h-5 w-52 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex flex-wrap gap-2\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div\n key={i}\n className=\"h-10 w-28 animate-pulse rounded-lg border border-ods-border bg-ods-card\"\n ></div>\n ))}\n </div>\n </div>\n </div>\n\n {/* About Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-64 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n\n <div className=\"rounded-lg border border-ods-border bg-ods-card p-6 md:p-8\">\n <div className=\"space-y-4\">\n <div className=\"h-6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-5/6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-4/5 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n\n {/* Key Features Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-72 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n <div className=\"grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"flex gap-4 rounded-lg border border-ods-border bg-ods-card p-4\">\n <div className=\"h-12 w-12 flex-shrink-0 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-3/4 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Pros and Cons Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-80 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Pros Column */}\n <div className=\"flex flex-col gap-6\">\n <div className=\"h-5 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"flex gap-3 rounded-lg border border-ods-border bg-ods-card p-4\">\n <div className=\"h-12 w-12 flex-shrink-0 animate-pulse rounded border border-ods-border bg-ods-bg\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-2/3 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n {/* Cons Column */}\n <div className=\"flex flex-col gap-6\">\n <div className=\"h-5 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"flex gap-3 rounded-lg border border-ods-border bg-ods-card p-4\">\n <div className=\"h-12 w-12 flex-shrink-0 animate-pulse rounded border border-ods-border bg-ods-bg\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-2/3 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Alternatives Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-72 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n <div className=\"grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"space-y-4 rounded-lg border border-ods-border bg-ods-card p-6\">\n {/* Header */}\n <div className=\"flex items-start gap-4\">\n <div className=\"h-16 w-16 flex-shrink-0 animate-pulse rounded-lg border border-ods-border bg-ods-bg\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-6 w-3/4 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-1/2 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n <div className=\"h-6 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <div className=\"h-4 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-5/6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-4/5 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-between border-t border-ods-border pt-2\">\n <div className=\"h-4 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n {/* Show All Button */}\n <div className=\"h-12 animate-pulse rounded-md bg-ods-skeleton\"></div>\n </div>\n\n {/* Comments Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-64 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n\n {/* Comment Form Skeleton */}\n <div className=\"space-y-3\">\n <div className=\"flex gap-3\">\n <div className=\"flex-1 overflow-hidden rounded-lg border border-ods-border bg-ods-bg\">\n {/* Title Section */}\n <div className=\"border-b border-ods-border p-3\">\n <div className=\"mb-2 h-3 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-full animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Description Section */}\n <div className=\"p-3\">\n <div className=\"mb-2 h-3 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"space-y-2\">\n <div className=\"h-4 w-full animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-3/4 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-1/2 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n\n {/* Send Button */}\n <div className=\"flex items-stretch\">\n <div className=\"h-[120px] w-20 animate-pulse rounded-lg bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n\n {/* Sample Comment Cards */}\n <div className=\"space-y-4\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"rounded-lg border border-ods-border bg-ods-card p-4\">\n {/* Comment Header */}\n <div className=\"mb-3 flex flex-col gap-3 min-[420px]:flex-row min-[420px]:items-center min-[420px]:justify-between min-[420px]:gap-2\">\n <div className=\"flex items-center gap-2\">\n {/* User Info */}\n <div className=\"flex items-center gap-2 rounded-lg border border-ods-border bg-ods-card px-3 py-2\">\n <div className=\"h-8 w-8 animate-pulse rounded-lg bg-ods-skeleton\"></div>\n <div className=\"h-4 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Timestamp */}\n <div className=\"h-4 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n\n {/* Comment Content */}\n <div className=\"space-y-2\">\n <div className=\"h-6 w-2/3 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"space-y-1\">\n <div className=\"h-4 w-full animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-4/5 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-3/5 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Right Sidebar - Desktop Only */}\n <div className=\"hidden w-[290px] flex-shrink-0 lg:block\">\n <div className=\"space-y-4\">\n {/* Deploy Button */}\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n\n {/* Voting Buttons */}\n <div className=\"space-y-2\">\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n </div>\n\n {/* GitHub Score Section */}\n <div className=\"overflow-hidden rounded-lg border border-ods-border\">\n {/* Header */}\n <div className=\"flex items-center gap-3 bg-ods-card p-4\">\n <div className=\"h-8 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex flex-col gap-1\">\n <div className=\"h-4 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n\n {/* Stats */}\n <div className=\"space-y-3 p-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3\">\n <div className=\"h-6 w-6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex items-end gap-1\">\n <div className=\"h-4 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Action Buttons */}\n <div className=\"space-y-3\">\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </main>\n );\n}\n\n/**\n * Stats/features section skeleton for homepage\n */\nexport function StatsSectionSkeleton({ className, columns = 3 }: PageLayoutSkeletonProps & { columns?: number }) {\n return (\n <div\n className={cn(\n 'mb-12 grid gap-6 md:mb-16',\n columns === 3 && 'grid-cols-1 md:grid-cols-3',\n columns === 4 && 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',\n className,\n )}\n >\n {Array.from({ length: columns }).map((_, i) => (\n <div key={i} className=\"rounded-lg border border-ods-border bg-ods-card p-6\">\n <div className=\"space-y-4\">\n <MediaSkeleton.Icon size=\"lg\" />\n <TextSkeleton.Subheading className=\"w-3/4\" />\n <div className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-3/4\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * Blog Card Grid Skeleton - Always displays exactly 4 blog card skeletons\n * Used for consistent blog page layout with predictable height\n */\nexport function BlogCardGridSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn('space-y-6', className)}>\n <CardSkeletonGrid count={4} variant=\"blog\" className=\"grid-cols-1 md:grid-cols-2\" />\n </div>\n );\n}\n\n/**\n * Vendor Grid Skeleton - Always displays exactly 12 vendor card skeletons\n * Used for consistent vendor page layout with predictable height\n */\nexport function VendorGridSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn('space-y-6', className)}>\n <CardSkeletonGrid count={12} variant=\"vendor\" className=\"grid-cols-1 md:grid-cols-2 lg:grid-cols-3\" />\n </div>\n );\n}\n\n/**\n * Slack Community Section Skeleton\n * Matches SlackCommunity component structure with title, channel list, and chat interface\n */\nexport function SlackCommunitySkeleton() {\n return (\n <section className=\"w-full bg-ods-bg px-4 py-12 md:px-20 md:py-20 lg:px-20\" aria-label=\"Slack Community Loading\">\n {/* Frame 651 Container */}\n <div className=\"flex w-full flex-col gap-4 md:gap-6\">\n {/* Title Skeleton */}\n <div className=\"w-full\">\n <div className=\"h-8 max-w-md animate-pulse rounded-lg bg-ods-skeleton md:h-12 lg:h-14\"></div>\n </div>\n\n {/* Content Area - Channel List + Chat Interface */}\n <div className=\"flex w-full min-w-0 flex-col gap-4 md:gap-6 lg:flex-row lg:items-start lg:justify-end\">\n {/* Channel List Skeleton */}\n <div className=\"flex w-full flex-shrink-0 animate-pulse flex-col overflow-hidden rounded border border-ods-border bg-ods-bg lg:w-[290px] lg:max-w-[290px]\">\n <div className=\"space-y-4 p-4\">\n {/* Header */}\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center justify-between\">\n <div className=\"h-4 w-24 rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-16 rounded bg-ods-skeleton\"></div>\n </div>\n <div className=\"h-3 w-32 rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Channels */}\n <div className=\"flex flex-col gap-1\">\n <div className=\"mb-2 h-3 w-16 rounded bg-ods-skeleton\"></div>\n {Array.from({ length: 5 }).map((_, i) => (\n <div key={i} className=\"flex min-h-[48px] items-center gap-2 px-2 py-3\">\n <div className=\"h-4 w-4 rounded bg-ods-skeleton\"></div>\n <div className=\"flex-1\">\n <div className=\"mb-1 h-3 w-24 rounded bg-ods-skeleton\"></div>\n <div className=\"h-2 w-16 rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Stats */}\n <div className=\"mt-2 border-t border-ods-border pt-3\">\n <div className=\"space-y-2\">\n <div className=\"flex justify-between\">\n <div className=\"h-3 w-16 rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-8 rounded bg-ods-skeleton\"></div>\n </div>\n <div className=\"flex justify-between\">\n <div className=\"h-3 w-20 rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-8 rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n {/* Chat Interface Skeleton */}\n <div className=\"flex h-[450px] min-h-[450px] min-w-0 flex-1 animate-pulse flex-col rounded-lg border border-ods-border bg-ods-card md:h-[500px] md:min-h-[500px] lg:h-[600px] lg:min-h-[600px]\">\n {/* Header */}\n <div className=\"flex h-[56px] flex-shrink-0 items-center justify-between border-b border-ods-border bg-ods-bg p-4 md:h-[60px] md:p-6\">\n <div className=\"flex items-center gap-2\">\n <div className=\"h-3 w-3 rounded bg-ods-skeleton md:h-4 md:w-4\"></div>\n <div className=\"h-3 w-24 rounded bg-ods-skeleton md:h-4 md:w-32\"></div>\n </div>\n <div className=\"h-3 w-12 rounded bg-ods-skeleton md:w-16\"></div>\n </div>\n\n {/* Messages */}\n <div className=\"min-h-[280px] flex-1 space-y-3 overflow-hidden bg-ods-bg p-4 md:min-h-[320px] md:space-y-4 md:p-6 lg:min-h-[420px]\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"flex gap-2 md:gap-3\">\n <div className=\"h-10 w-10 flex-shrink-0 rounded-lg bg-ods-skeleton md:h-12 md:w-12\"></div>\n <div className=\"flex-1\">\n <div className=\"mb-1 flex items-center gap-2\">\n <div className=\"h-3 w-16 rounded bg-ods-skeleton md:w-20\"></div>\n <div className=\"h-3 w-12 rounded bg-ods-skeleton md:w-16\"></div>\n </div>\n <div className=\"h-3 w-full rounded bg-ods-skeleton md:h-4\"></div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Input Area */}\n <div className=\"h-[72px] flex-shrink-0 border-t border-ods-border bg-ods-card p-4 md:h-[80px] md:p-6\">\n <div className=\"flex items-end justify-center gap-3 md:justify-end\">\n <div className=\"h-10 w-28 rounded-lg bg-ods-skeleton md:h-12 md:w-32\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </section>\n );\n}\n","'use client';\n\nimport type { ReactNode } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface PersistentFilterControlsProps {\n /**\n * Whether the filters are currently in a loading state\n */\n isLoading: boolean;\n /**\n * The filter control components (search, sidebar, mobile dropdown)\n */\n children: ReactNode;\n /**\n * Additional CSS classes\n */\n className?: string;\n /**\n * Disabled opacity (0-1)\n */\n disabledOpacity?: number;\n /**\n * Transition duration in milliseconds\n */\n transitionDuration?: number;\n /**\n * Whether to prevent pointer events during loading\n */\n preventInteraction?: boolean;\n}\n\n/**\n * PersistentFilterControls\n *\n * A wrapper component that keeps filter controls visible during loading states\n * but provides visual feedback that they are temporarily disabled.\n *\n * Features:\n * - Reduces opacity and disables pointer events during loading\n * - Maintains layout and accessibility during loading states\n * - Provides smooth transitions between enabled/disabled states\n * - Preserves keyboard navigation and screen reader functionality\n * - Applies consistent disabled styling across all filter types\n *\n * Usage:\n * ```tsx\n * <PersistentFilterControls isLoading={isLoadingVendors}>\n * <SearchContainer />\n * <CategoryFilterSidebar />\n * <MobileDropdown />\n * </PersistentFilterControls>\n * ```\n */\nexport function PersistentFilterControls({\n isLoading,\n children,\n className,\n disabledOpacity = 0.6,\n transitionDuration = 300,\n preventInteraction = true,\n}: PersistentFilterControlsProps) {\n return (\n <div\n className={cn(\n 'relative transition-all ease-in-out',\n isLoading && preventInteraction && 'pointer-events-none',\n className,\n )}\n style={{\n opacity: isLoading ? disabledOpacity : 1,\n transitionDuration: `${transitionDuration}ms`,\n }}\n role=\"region\"\n aria-label=\"Filter controls\"\n aria-busy={isLoading}\n data-loading={isLoading}\n >\n {/* Loading indicator overlay for screen readers */}\n {isLoading && (\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n Filters temporarily disabled while loading content\n </div>\n )}\n\n {/* Filter controls with disabled state styling */}\n <div\n className={cn('transition-all ease-in-out', isLoading && 'cursor-not-allowed')}\n style={{\n transitionDuration: `${transitionDuration}ms`,\n }}\n >\n {children}\n </div>\n\n {/* Visual loading indicator */}\n {isLoading && (\n <div className=\"absolute right-2 top-2 z-10\" role=\"status\" aria-label=\"Loading filters\">\n <div className=\"h-4 w-4 animate-spin rounded-full border-2 border-ods-border border-t-ods-accent\" />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Enhanced SearchContainer wrapper that integrates with persistent loading\n */\ninterface PersistentSearchContainerProps {\n isLoading: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function PersistentSearchContainer({ isLoading, children, className }: PersistentSearchContainerProps) {\n return (\n <PersistentFilterControls\n isLoading={isLoading}\n preventInteraction={false} // Keep search interactive during loading\n className={className}\n >\n {children}\n </PersistentFilterControls>\n );\n}\n\n/**\n * Enhanced sidebar wrapper for desktop filter controls\n */\ninterface PersistentSidebarProps {\n isLoading: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function PersistentSidebar({ isLoading, children, className }: PersistentSidebarProps) {\n return (\n <PersistentFilterControls\n isLoading={isLoading}\n disabledOpacity={0.5}\n className={cn('lg:sticky lg:top-20', className)}\n >\n {children}\n </PersistentFilterControls>\n );\n}\n\n/**\n * Enhanced mobile dropdown wrapper\n */\ninterface PersistentMobileDropdownProps {\n isLoading: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function PersistentMobileDropdown({ isLoading, children, className }: PersistentMobileDropdownProps) {\n return (\n <PersistentFilterControls isLoading={isLoading} disabledOpacity={0.7} className={cn('lg:hidden', className)}>\n {children}\n </PersistentFilterControls>\n );\n}\n","'use client';\n\nimport React, { createContext, useCallback, useContext, useRef, type ReactNode } from 'react';\nimport type { DocNode } from '../../types/doc-source';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DocNavigator {\n /** Base route this navigator owns (e.g., '/knowledge-base', '/data-room'). */\n baseRoute: string;\n /** Look up a node in the tree by its storage path (e.g., 'openframe-cli/README.md'). */\n findNodeByPath: (path: string) => DocNode | null;\n /** Navigate to a node using the same flow as a sidebar menu click. */\n selectNode: (node: DocNode) => void;\n}\n\ninterface DocNavigationContextValue {\n /**\n * Register a navigator for a `baseRoute`. Returns a cleanup function that\n * removes the navigator from the map IF the slot still owns it (StrictMode-safe).\n * Duplicate registration for the same baseRoute logs a console.warn and replaces.\n */\n register: (nav: DocNavigator) => () => void;\n /**\n * Navigate to a path inside whichever registered navigator owns its baseRoute prefix.\n * Returns true if handled, false otherwise (caller falls back to opening in a new tab).\n */\n navigate: (path: string) => boolean;\n /** Whether ANY navigator is currently mounted. */\n isAvailable: () => boolean;\n}\n\n// =============================================================================\n// Context\n// =============================================================================\n\nconst DocNavigationContext = createContext<DocNavigationContextValue | null>(null);\n\n/**\n * Bridges `useDocumentTree` instances (deep in the page tree) to `GlobalAskAI`\n * (high up near the root) without global events or URL parsing.\n *\n * Multi-navigator design: a `Map<baseRoute, DocNavigator>` lets multiple\n * viewers coexist (nested viewers, dual-pane scenarios). Today only one\n * viewer mounts at a time, so behavior is unchanged.\n */\nexport function DocNavigationProvider({ children }: { children: ReactNode }) {\n const navMap = useRef<Map<string, DocNavigator>>(new Map());\n\n const register = useCallback((nav: DocNavigator) => {\n const existing = navMap.current.get(nav.baseRoute);\n if (existing && existing !== nav) {\n console.warn(`[DocNavigationContext] duplicate registration for ${nav.baseRoute}; replacing`);\n }\n navMap.current.set(nav.baseRoute, nav);\n return () => {\n // StrictMode-safe identity check: only delete if THIS nav still owns the slot.\n if (navMap.current.get(nav.baseRoute) === nav) {\n navMap.current.delete(nav.baseRoute);\n }\n };\n }, []);\n\n const findOwningNavigator = useCallback((path: string): DocNavigator | null => {\n // First try: a navigator whose baseRoute is a prefix of `path`. Walks the\n // longest match first so nested routes win over a parent.\n const baseRoutes = Array.from(navMap.current.keys()).sort((a, b) => b.length - a.length);\n for (const baseRoute of baseRoutes) {\n if (path === baseRoute || path.startsWith(`${baseRoute}/`)) {\n return navMap.current.get(baseRoute) ?? null;\n }\n }\n // Fallback: a single navigator handles any path (existing behavior — chat\n // chips emit storage paths, not absolute URLs, so prefix matching misses).\n if (navMap.current.size === 1) {\n return navMap.current.values().next().value ?? null;\n }\n return null;\n }, []);\n\n const navigate = useCallback(\n (path: string): boolean => {\n const nav = findOwningNavigator(path);\n if (!nav) return false;\n const node = nav.findNodeByPath(path);\n if (!node) return false;\n nav.selectNode(node);\n return true;\n },\n [findOwningNavigator],\n );\n\n const isAvailable = useCallback(() => navMap.current.size > 0, []);\n\n const value = React.useMemo(() => ({ register, navigate, isAvailable }), [register, navigate, isAvailable]);\n\n return <DocNavigationContext.Provider value={value}>{children}</DocNavigationContext.Provider>;\n}\n\n// =============================================================================\n// Hooks\n// =============================================================================\n\nexport function useDocNavigation(): DocNavigationContextValue {\n const ctx = useContext(DocNavigationContext);\n if (!ctx) {\n // Safe no-op fallback when used outside the provider.\n return {\n register: () => () => {},\n navigate: () => false,\n isAvailable: () => false,\n };\n }\n return ctx;\n}\n","import { useCallback } from 'react';\nimport { useChatRuntime } from '../../contexts/chat-runtime-context';\nimport type { ResolveLinkResult } from '../../types/doc-source';\nimport { contentFetch } from '../../utils/embed-content-fetch';\n\n/**\n * `useDocsResolveLink(sourceId, override?)` — POST `/api/docs/resolve-link`\n * (or the override / `ChatRuntime.endpoints.docsResolveLinkUrl`) for a\n * relative href inside a doc body, returning a `ResolveLinkResult`\n * envelope.\n *\n * The endpoint chain (`override ?? runtime.endpoints.docsResolveLinkUrl\n * ?? '/api/docs/resolve-link'`) mirrors `searchEndpoint` resolution in\n * `<DocViewer>` so embedders configure both the same way: per-instance\n * prop OR ambient `ChatRuntimeProvider`.\n *\n * The full fetch + JSON-parse pipeline is wrapped in try/catch so a\n * network throw (DNS / CORS / offline) or a non-JSON response surfaces\n * as `{ success: false, error }` — the markdown renderer's broken-link\n * badge handles that branch instead of swallowing an unhandled rejection\n * past the click handler.\n */\n/**\n * Narrow the endpoint's answer to a `ResolveLinkResult`.\n *\n * `Response.json()` hands back `any`, and the route answers either the bare\n * result or a `{ data: … }` envelope — so the shape is checked instead of\n * trusted: a non-object body, or a field of the wrong primitive type, degrades\n * to `{ success: false }`, which is the \"leave the link alone\" branch the\n * markdown renderer already handles.\n *\n * Mirrors `toResolveLinkResult` in `ui/markdown/rich/rich-markdown-renderer`,\n * which narrows the same wire shape for the renderer's own fetch (that copy\n * cannot be imported here — it would pull the whole rich renderer chunk into\n * this hook).\n */\nfunction toResolveLinkResult(body: unknown): ResolveLinkResult {\n if (typeof body !== 'object' || body === null) {\n return { success: false };\n }\n const envelope: Record<string, unknown> = { ...body };\n const inner = envelope.data;\n const fields: Record<string, unknown> = typeof inner === 'object' && inner !== null ? { ...inner } : envelope;\n const readString = (key: string): string | undefined => (typeof fields[key] === 'string' ? fields[key] : undefined);\n\n return {\n success: fields.success === true,\n resolvedPath: readString('resolvedPath'),\n type: readString('type'),\n action: readString('action'),\n error: readString('error'),\n message: readString('message'),\n };\n}\n\nexport function useDocsResolveLink(sourceId: string, resolveLinkEndpoint?: string | null) {\n const chatRuntime = useChatRuntime();\n const resolvedResolveLinkEndpoint =\n resolveLinkEndpoint ?? chatRuntime?.endpoints.docsResolveLinkUrl ?? '/api/docs/resolve-link';\n\n return useCallback(\n async (href: string, currentPath: string): Promise<ResolveLinkResult> => {\n try {\n const response = await contentFetch(resolvedResolveLinkEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ link: href, currentPath, source: sourceId }),\n });\n if (!response.ok) {\n return { success: false, error: `Resolve failed: ${response.status}` };\n }\n const json: unknown = await response.json();\n return toResolveLinkResult(json);\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Resolve failed',\n };\n }\n },\n [resolvedResolveLinkEndpoint, sourceId],\n );\n}\n","'use client';\n\nimport { useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport type { DocNode, DocContent, DocumentType } from '../../types/doc-source';\nimport { apiErrorMessage } from '../../utils/common';\nimport {\n stripFolderIndexFromPath,\n findDocNodeByPath,\n getDocAncestorNodeIds,\n DEFAULT_FOLDER_INDEX_FILE,\n} from '../../utils/doc-tree-nav';\nimport { contentFetch } from '../../utils/embed-content-fetch';\nimport { navigateSamePageHash, HUB_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav';\nimport { scrollElementIntoView } from '../../utils/scroll-into-view';\nimport { useDocNavigation } from './doc-navigation-context';\n\nfunction scrollToContent() {\n const article = document.querySelector('article');\n if (article) {\n scrollElementIntoView(article, { headerOffset: HUB_HEADER_OFFSET_PX });\n } else {\n // Same anchoring-proof tween for the no-article fallback — native smooth\n // scrollTo is cancelled by scroll anchoring while the new doc renders in.\n scrollElementIntoView(document.documentElement);\n }\n}\n\n/**\n * First displayable document inside a folder (depth-first): a direct non-Mermaid\n * file, else the first doc found in a subfolder. Used so a folder WITHOUT a\n * README shows its first child's content (mirroring how a README folder shows\n * its README) instead of a blank panel or a redundant in-page listing — the\n * sidebar tree is the directory browser. Returns null for a folder with no docs.\n */\nfunction findFirstDocPath(folder: DocNode): string | null {\n const children = folder.children ?? [];\n for (const child of children) {\n if (child.type === 'file' && !child.path.toLowerCase().endsWith('.mmd')) {\n return child.path;\n }\n }\n for (const child of children) {\n if (child.type === 'folder') {\n const nested = findFirstDocPath(child);\n if (nested) return nested;\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Wire decoding for the two doc-source endpoints\n//\n// `Response.json()` is typed `any`, so every field of both envelopes used to be\n// read off an `any` and the whole tree walk below inherited it. Both endpoints\n// are untrusted input — an embedder's reverse proxy answers them — so the shape\n// is validated once here instead of being trusted at ~30 read sites.\n//\n// Mirrors the narrowing `toResolveLinkResult` does for the resolve-link\n// envelope in `use-docs-resolve-link.ts`; that decoder covers a different\n// endpoint and is not reusable here.\n// ---------------------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** `Array.isArray` narrows `unknown` to `any[]`; this keeps the elements `unknown`. */\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n return Array.isArray(value);\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction isDocumentType(value: unknown): value is DocumentType {\n return value === 'markdown' || value === 'pdf' || value === 'google_sheet' || value === 'figma' || value === 'file';\n}\n\n/**\n * One tree node, or null when the row cannot be rendered.\n *\n * `id`, `name`, `path` and `type` are load-bearing: the sidebar calls\n * `node.name.endsWith('.md')` and every nav helper keys off `id`/`path`, so a\n * row missing one of them throws while rendering the tree rather than merely\n * looking wrong. Dropping the row keeps the rest of the tree browsable.\n *\n * `slug` is declared on `DocNode` but read nowhere in the lib, and the shared\n * `TreeNodeBase` producer contract does not carry it — so it is derived from\n * the path (the derivation the doc-source DALs use) when the payload omits it.\n */\nfunction toDocNode(value: unknown): DocNode | null {\n if (!isRecord(value)) return null;\n\n const id = readString(value, 'id');\n const name = readString(value, 'name');\n const path = readString(value, 'path');\n const type = value.type;\n if (id === undefined || name === undefined || path === undefined) return null;\n if (type !== 'file' && type !== 'folder') return null;\n\n const sortOrder = value.sortOrder;\n const hasReadme = value.hasReadme;\n\n return {\n id,\n name,\n slug: readString(value, 'slug') ?? path.split('/').pop() ?? '',\n path,\n type,\n hasReadme: typeof hasReadme === 'boolean' ? hasReadme : undefined,\n sortOrder: typeof sortOrder === 'number' ? sortOrder : undefined,\n documentType: isDocumentType(value.documentType) ? value.documentType : undefined,\n children: toDocNodes(value.children) ?? undefined,\n };\n}\n\n/** The tree, or null when the payload is not an array at all (a failed load). */\nfunction toDocNodes(value: unknown): DocNode[] | null {\n if (!isUnknownArray(value)) return null;\n const nodes: DocNode[] = [];\n for (const entry of value) {\n const node = toDocNode(entry);\n if (node) nodes.push(node);\n }\n return nodes;\n}\n\nfunction toDocSections(value: unknown): DocContent['sections'] {\n if (!isUnknownArray(value)) return [];\n const sections: DocContent['sections'] = [];\n for (const entry of value) {\n if (!isRecord(entry)) continue;\n const id = readString(entry, 'id');\n const title = readString(entry, 'title');\n const level = entry.level;\n if (id === undefined || title === undefined || typeof level !== 'number') continue;\n sections.push({ id, title, level });\n }\n return sections;\n}\n\nfunction toStringArray(value: unknown): string[] | undefined {\n if (!isUnknownArray(value)) return undefined;\n return value.filter((entry): entry is string => typeof entry === 'string');\n}\n\n/**\n * One document body, or null when the payload is not an object.\n *\n * `content` is declared required but is empty by nature for the rich document\n * types — a `pdf` / `google_sheet` / `figma` doc renders purely from `fileUrl`\n * / `externalUrl` (see `docs-hub-page.tsx`) — so a missing `content` is\n * normalized to `''` rather than treated as a failed load.\n *\n * `sections` and `path` are also declared required, and both leak when absent:\n * `path` feeds `DocRenderHandlers.currentPath`, which is what every relative\n * link inside the body is resolved against. `requestedPath` is the path this\n * fetch asked for, which is the correct value whenever the server omits it.\n */\nfunction toDocContent(value: unknown, requestedPath: string): DocContent | null {\n if (!isRecord(value)) return null;\n\n const fileSize = value.fileSize;\n\n return {\n content: readString(value, 'content') ?? '',\n sections: toDocSections(value.sections),\n path: readString(value, 'path') ?? requestedPath,\n documentType: isDocumentType(value.documentType) ? value.documentType : undefined,\n brokenLinks: toStringArray(value.brokenLinks),\n fileUrl: readString(value, 'fileUrl'),\n externalUrl: readString(value, 'externalUrl'),\n mimeType: readString(value, 'mimeType'),\n fileName: readString(value, 'fileName'),\n fileSize: typeof fileSize === 'number' ? fileSize : undefined,\n publishedAt: readString(value, 'publishedAt'),\n updatedAt: readString(value, 'updatedAt'),\n };\n}\n\nexport interface UseDocumentTreeConfig {\n /** API endpoint for fetching the document tree structure */\n structureEndpoint: string;\n /** API endpoint for fetching document content */\n contentEndpoint: string;\n /** Base route path for URL navigation (e.g., '/knowledge-base', '/data-room') */\n baseRoute: string;\n /** Folder-index filename (defaults to 'README.md'). */\n folderIndexFile?: string;\n}\n\n/**\n * Generic hook for document tree navigation and content fetching.\n * Drives DocViewer across all doc-source consumers.\n *\n * Client-only: structure + content fetches run in parallel on first mount.\n * No SSR pre-population — the previous SSR path required a Supabase admin\n * client (service role key) and silently fell back to client fetches on\n * envs where the key wasn't set; the parallel client fetches keep behavior\n * uniform across local + prod (latency ~= max(structure, content), not sum).\n */\nexport function useDocumentTree(config: UseDocumentTreeConfig, initialPath?: string) {\n const { structureEndpoint, contentEndpoint, baseRoute } = config;\n const folderIndexFile = config.folderIndexFile ?? DEFAULT_FOLDER_INDEX_FILE;\n\n const cleanInitialPath = stripFolderIndexFromPath(initialPath?.replace(/\\/$/, '') || '', folderIndexFile);\n\n const [structure, setStructure] = useState<DocNode[]>([]);\n const [selectedPath, setSelectedPath] = useState<string>(cleanInitialPath);\n const [content, setContent] = useState<DocContent | null>(null);\n const [isLoadingStructure, setIsLoadingStructure] = useState(true);\n const [isLoadingContent, setIsLoadingContent] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());\n const lastFetchedPath = useRef<string | null>(null);\n\n const normalizedBaseRoute = baseRoute.replace(/\\/$/, '');\n\n // Refreshed after every commit rather than in the render body: the reader is\n // a popstate listener, which cannot fire before a commit, and a discarded\n // render attempt must not leave a path behind that was never navigated to.\n const selectedPathRef = useRef(selectedPath);\n useEffect(() => {\n selectedPathRef.current = selectedPath;\n });\n\n const docNavigation = useDocNavigation();\n\n useEffect(() => {\n const handlePopState = () => {\n const pathname = window.location.pathname;\n let pathFromUrl = '';\n\n if (pathname === normalizedBaseRoute || pathname === `${normalizedBaseRoute}/`) {\n pathFromUrl = '';\n } else if (pathname.startsWith(`${normalizedBaseRoute}/`)) {\n pathFromUrl = pathname.substring(`${normalizedBaseRoute}/`.length);\n }\n\n pathFromUrl = stripFolderIndexFromPath(pathFromUrl, folderIndexFile);\n\n if (pathFromUrl !== selectedPathRef.current) {\n setSelectedPath(pathFromUrl);\n if (pathFromUrl) {\n const parentPath = pathFromUrl.includes('/')\n ? pathFromUrl.substring(0, pathFromUrl.lastIndexOf('/'))\n : pathFromUrl;\n setExpandedNodes(new Set(getDocAncestorNodeIds(parentPath)));\n }\n setTimeout(() => {\n scrollToContent();\n }, 150);\n }\n };\n\n window.addEventListener('popstate', handlePopState);\n return () => window.removeEventListener('popstate', handlePopState);\n }, [normalizedBaseRoute, folderIndexFile]);\n\n // External-URL → state sync. The popstate listener above catches browser\n // back/forward, but client-side routers (react-router, Next App Router…)\n // change the URL via `history.pushState` which does NOT fire popstate.\n // The host re-renders the viewer with a new `initialPath` prop instead, so\n // we mirror the popstate logic here against the (memoized) `cleanInitialPath`.\n // Without this, a chat-card click that soft-navigates via react-router\n // updates the URL but the viewer stays on the previously-selected doc.\n //\n // Adjusted while rendering — React's documented pattern for a prop-driven\n // sync — rather than from an effect: the viewer renders `selectedPath` and\n // `expandedNodes` in THIS render, so an effect painted a full frame of the\n // previous document (and the previous expanded branch) before swapping. The\n // guard compares against the `selectedPath` STATE rather than the popstate\n // listener's ref, both because a ref must not be read during render and\n // because at this point the two hold the same value.\n const [syncedInitialPath, setSyncedInitialPath] = useState(cleanInitialPath);\n const [externalNavCount, setExternalNavCount] = useState(0);\n if (syncedInitialPath !== cleanInitialPath) {\n setSyncedInitialPath(cleanInitialPath);\n if (cleanInitialPath !== selectedPath) {\n setSelectedPath(cleanInitialPath);\n if (cleanInitialPath) {\n const parentPath = cleanInitialPath.includes('/')\n ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf('/'))\n : cleanInitialPath;\n setExpandedNodes(new Set(getDocAncestorNodeIds(parentPath)));\n }\n // Counter rather than a flag: it makes the scroll below fire once per\n // ACTUAL external navigation, which is exactly when the old effect\n // reached its `setTimeout` — a bare `cleanInitialPath` dep would also\n // fire for the host mirroring a sidebar click back into the URL.\n setExternalNavCount(n => n + 1);\n }\n }\n\n // Match popstate's scroll-to-content delay; the targeted content fetch\n // dispatched by the selectedPath effect lands before this fires.\n useEffect(() => {\n if (externalNavCount === 0) return undefined;\n const timer = setTimeout(scrollToContent, 150);\n return () => clearTimeout(timer);\n }, [externalNavCount]);\n\n const fetchStructure = async () => {\n try {\n setIsLoadingStructure(true);\n setError(null);\n\n const response = await contentFetch(structureEndpoint);\n\n if (!response.ok) {\n throw new Error('Failed to load documentation structure');\n }\n\n const payload: unknown = await response.json();\n // A non-array `data` used to be handed straight to `setStructure`, and\n // every later `findDocNodeByPath` then tried to iterate it — a hard\n // \"nodes is not iterable\" throw instead of the error banner below.\n const nodes = isRecord(payload) && payload.success ? toDocNodes(payload.data) : null;\n\n if (nodes) {\n setStructure(nodes);\n\n if (cleanInitialPath) {\n const pathForExpansion = cleanInitialPath.includes('.')\n ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf('/'))\n : cleanInitialPath;\n if (pathForExpansion) {\n setExpandedNodes(new Set(getDocAncestorNodeIds(pathForExpansion)));\n }\n } else if (nodes.length > 0) {\n const hasRootReadme = nodes.some(node => node.type === 'file' && node.path === folderIndexFile);\n\n if (!hasRootReadme) {\n const firstNode = nodes[0];\n if (firstNode.type === 'folder') {\n setExpandedNodes(new Set([firstNode.id]));\n if (firstNode.hasReadme) {\n setSelectedPath(firstNode.path);\n window.history.replaceState({}, '', `${normalizedBaseRoute}/${firstNode.path}`);\n }\n }\n }\n }\n } else {\n setError(apiErrorMessage(payload, 'Failed to load documentation structure'));\n }\n } catch (err) {\n console.error('Error fetching documentation structure:', err);\n setError(err instanceof Error ? err.message : 'Failed to load documentation');\n } finally {\n setIsLoadingStructure(false);\n }\n };\n\n const fetchContent = async (path: string) => {\n try {\n setIsLoadingContent(true);\n // Don't clear `error` here — if a previous fetch set an error and this\n // is a stale/speculative call that gets superseded, the guard below\n // returns early without writing to state. Clearing error here would\n // briefly flicker the user-visible error message.\n\n const response = await contentFetch(`${contentEndpoint}?path=${encodeURIComponent(path)}`);\n\n // Request-id guard: between awaits, `lastFetchedPath.current` may have\n // been bumped by a newer fetch (the structure-arrives auto-select issues\n // a more-targeted fetch while the speculative one is in flight). Bail\n // BEFORE writing to state — otherwise the late 404 of the speculative\n // fetch overwrites the targeted fetch's good content with null.\n if (path !== lastFetchedPath.current) return;\n\n if (!response.ok) {\n if (response.status === 404) {\n const errorPayload: unknown = await response.json().catch(() => null);\n if (path !== lastFetchedPath.current) return;\n // Landing-page silent fallback: when the user lands on the source's\n // root URL and there's no root `README.md` (knowledge-base case),\n // the speculative fetch 404s — surface an empty state instead of\n // an error banner. The structure-arrives auto-select will fire\n // a targeted fetch for the first-folder README on the next render.\n if (path === folderIndexFile && selectedPath === '') {\n // Superseded by the auto-select fetch the structure effect fires.\n // Null the request id so the `finally` does NOT drop the spinner —\n // otherwise there's a 1-frame gap (isLoadingContent false, content\n // null) where the empty state flashes before the real fetch starts.\n lastFetchedPath.current = null;\n setError(null);\n setContent(null);\n return;\n }\n // No-README FOLDER → stay silent: the content effect resolves it to\n // the folder's first child, so the folder-path 404 from the direct-\n // load speculative fetch is expected, not an error to flash.\n const probe = findDocNodeByPath(stripFolderIndexFromPath(path, folderIndexFile), structure);\n const probeIsNoReadmeFolder = !!probe && probe.type === 'folder' && !probe.hasReadme;\n // Before the structure has loaded to classify the path (`structure`\n // is [] in the speculative call's closure), only silence FOLDER-LIKE\n // paths (no `.md`) — a genuinely missing `*.md` leaf must still error.\n const preStructureFolderLike = structure.length === 0 && !path.endsWith('.md');\n if (probeIsNoReadmeFolder || preStructureFolderLike) {\n // Superseded by the targeted fetch (first-child / reclassified path)\n // the structure effect fires. Null the request id so the `finally`\n // keeps the spinner up instead of flashing an empty state for a\n // frame before that fetch starts.\n lastFetchedPath.current = null;\n setError(null);\n setContent(null);\n return;\n }\n setError(apiErrorMessage(errorPayload, 'Documentation file not found'));\n setContent(null);\n return;\n }\n throw new Error('Failed to load documentation content');\n }\n\n const payload: unknown = await response.json();\n if (path !== lastFetchedPath.current) return;\n setError(null);\n\n const doc = isRecord(payload) && payload.success ? toDocContent(payload.data, path) : null;\n\n if (doc) {\n // `correctPath` is only honored when it is actually a string. The old\n // `!== undefined` check also admitted `null`, which `setSelectedPath`\n // then stored as the selection: the URL became `${baseRoute}/null` and\n // the content effect's `selectedPath === null` guard bailed on every\n // subsequent run, freezing the viewer on that document.\n const correctPath = isRecord(payload) && payload.redirect ? readString(payload, 'correctPath') : undefined;\n if (correctPath !== undefined) {\n setSelectedPath(correctPath);\n window.history.replaceState({}, '', `${normalizedBaseRoute}/${correctPath}`);\n }\n setContent(doc);\n } else {\n setError(apiErrorMessage(payload, 'Failed to load content'));\n setContent(null);\n }\n } catch (err) {\n if (path !== lastFetchedPath.current) return;\n console.error('Error fetching documentation content:', err);\n setError(err instanceof Error ? err.message : 'Failed to load content');\n setContent(null);\n } finally {\n // Only clear loading state if THIS fetch is still the active one — a\n // superseded speculative shouldn't flip the spinner off while the\n // targeted fetch is still in flight.\n if (path === lastFetchedPath.current) setIsLoadingContent(false);\n }\n };\n // `fetchStructure` / `fetchContent` are plain async functions rebuilt on\n // every render, so neither effect below can take them as dependencies: the\n // init effect would re-fire its speculative fetch and the path effect would\n // refetch the same document on every parent render. Read through a ref that\n // is refreshed after every commit, so both always call the current closures.\n const latestRef = useRef({ fetchContent, fetchStructure, cleanInitialPath, folderIndexFile });\n useEffect(() => {\n latestRef.current = { fetchContent, fetchStructure, cleanInitialPath, folderIndexFile };\n });\n\n // Mount-only. The `isInitialized` state this used to latch on was read\n // NOWHERE else: it was a run-once guard that cost a setState in an effect\n // body (and a second, no-op run of this effect) to say what empty deps say\n // directly.\n useEffect(() => {\n // Kick off the speculative content fetch IN PARALLEL with the structure\n // fetch — the two endpoints are independent and most landing pages have\n // a root README (the default folder-index). If the structure ends up\n // pointing at a different path (e.g. knowledge-base falls back to the\n // first-folder README because there's no root README), the content\n // useEffect issues the correct fetch after structure arrives — the\n // speculative result silently no-ops (the content state update gets\n // overwritten by the targeted fetch).\n const latest = latestRef.current;\n const speculativeContentPath = latest.cleanInitialPath || latest.folderIndexFile;\n lastFetchedPath.current = speculativeContentPath;\n // Both settle their own failures into `error` state via try/catch/finally,\n // so neither can reject; they are deliberately NOT awaited so the two\n // independent endpoints stay in parallel.\n void latest.fetchContent(speculativeContentPath);\n void latest.fetchStructure();\n }, []);\n\n useEffect(() => {\n if (selectedPath === null || selectedPath === undefined) return;\n if (!structure || structure.length === 0) return;\n\n let pathToFetch: string | null = null;\n\n if (selectedPath === '') {\n pathToFetch = folderIndexFile;\n } else {\n const node = findDocNodeByPath(selectedPath, structure);\n\n if (node && node.type === 'folder' && !node.hasReadme) {\n // No-README folder has no body of its own — show its FIRST child doc\n // (mirrors a README folder showing its README). selectedPath stays the\n // folder, so the sidebar keeps it highlighted/expanded; the sidebar is\n // the directory browser, so we render NO separate in-page listing.\n const firstDocPath = findFirstDocPath(node);\n if (!firstDocPath) {\n // Genuinely empty folder — nothing to fetch. The empty state itself\n // is DERIVED (see `isEmptyFolder` below); clearing content/error/\n // loading from here would be a second render pass to publish\n // something `structure` + `selectedPath` already say.\n lastFetchedPath.current = null;\n return;\n }\n pathToFetch = firstDocPath;\n } else if (node && node.type === 'folder' && node.hasReadme) {\n // `getContent(folder)` already resolves a README folder to its README,\n // so the initial speculative fetch (which uses the bare folder path)\n // ALREADY loaded this content. Re-fetching the `${folder}/README.md`\n // variant is a redundant 2nd request whose in-flight `isLoadingContent`\n // flashes the skeleton — content → skeleton → content — on first load.\n // Skip it when the folder path was already the (speculatively) fetched\n // path; the result (or its in-flight request) covers the README.\n if (lastFetchedPath.current === selectedPath) {\n return;\n }\n pathToFetch = `${selectedPath}/${folderIndexFile}`;\n } else {\n pathToFetch = selectedPath;\n }\n }\n\n if (pathToFetch === lastFetchedPath.current) {\n return;\n }\n\n if (pathToFetch) {\n lastFetchedPath.current = pathToFetch;\n // Never rejects — settles its own failures into `error` state.\n void latestRef.current.fetchContent(pathToFetch);\n }\n }, [selectedPath, structure, folderIndexFile]);\n\n const toggleNode = useCallback((nodeId: string) => {\n setExpandedNodes(prev => {\n const next = new Set(prev);\n if (next.has(nodeId)) {\n next.delete(nodeId);\n } else {\n next.add(nodeId);\n }\n return next;\n });\n }, []);\n\n // Structural minimum the body uses — `id`, `path`, `type`, `hasReadme`.\n // Widening from `DocNode` lets the navigation components (which carry the\n // narrower `NavigationNode` row shape) pass their own node back without the\n // cross-type `as` cast. Both DocNode and NavigationNode satisfy this.\n const selectNode = useCallback(\n (node: Pick<DocNode, 'id' | 'path' | 'type' | 'hasReadme'>) => {\n // Expansion only: clicking a folder toggles its own subtree; clicking a file\n // reveals its ancestor chain.\n if (node.type === 'folder') {\n setExpandedNodes(prev => {\n if (prev.has(node.id)) {\n const ancestorIds = getDocAncestorNodeIds(node.path);\n ancestorIds.pop(); // collapse self, keep ancestors open\n return new Set(ancestorIds);\n }\n return new Set(getDocAncestorNodeIds(node.path));\n });\n } else {\n const lastSlash = node.path.lastIndexOf('/');\n if (lastSlash > 0) {\n setExpandedNodes(new Set(getDocAncestorNodeIds(node.path.substring(0, lastSlash))));\n }\n }\n\n // Every node is a navigable destination — a file shows its body, a README\n // folder its README, a no-README folder its first child doc (the content\n // effect resolves which). So selection + URL + scroll are identical for all\n // node types; no per-type special-casing.\n lastFetchedPath.current = null;\n setSelectedPath(node.path);\n window.history.pushState({}, '', `${normalizedBaseRoute}/${node.path}`);\n setTimeout(scrollToContent, 150);\n },\n [normalizedBaseRoute],\n );\n\n const navigateToDoc = useCallback(\n (path: string, options?: { expandFolder?: boolean; fromInternalLink?: boolean }) => {\n const hashIndex = path.indexOf('#');\n const anchor = hashIndex !== -1 ? path.substring(hashIndex) : '';\n const cleanPath = path.replace(/\\/$/, '').split('#')[0];\n\n // Same-doc-different-anchor shortcut. Content is already mounted, so we\n // don't need the 300ms \"wait-for-fetch\" bandaid — the canonical helper\n // owns pushState + synthetic `hashchange` (so any in-doc TOC / accordion\n // bound to the URL hash re-renders) + the anchoring-proof tween in one\n // sync call. `headerOffset: HUB_HEADER_OFFSET_PX` matches the cross-doc path below so\n // anchors land BELOW the docs sticky header on every same-doc internal\n // link click. Cross-doc nav (different cleanPath) falls through to the\n // existing fetch-then-scroll path below.\n //\n // We pass the BARE-hash form to the helper rather than reconstructing\n // a full `${normalizedBaseRoute}/${cleanPath}${anchor}` path: the\n // helper's pathname check compares against `window.location.pathname`,\n // which carries the FOLDER-INDEX-STRIPPED form (`/docs/foo` for\n // `foo/README.md`, `/docs` for the root index). Handing it `cleanPath`\n // — the raw resolved path — produces e.g. `/docs/foo/README.md` and\n // the compare fails → helper returns false → silent dead-click. The\n // bare-hash form sidesteps that entirely: the helper reconstructs\n // `pathname + search + hash` from `window.location`, so the compare\n // is trivially equal. Covers bare `#anchor` links (resolve to\n // `cleanPath=''`) AND folder-index links (`foo/README.md` resolving\n // to the current `/docs/foo`).\n // Bare-hash internal links (`[Click](#section)`) come in as\n // `path === '#section'`, so `cleanPath` becomes `''` and the naive\n // strip-then-compare misses the same-doc shortcut on every NON-root\n // doc (selectedPath is e.g. `'foo/bar'`, not `''`). For that case the\n // current doc IS the same-doc target by definition — short-circuit\n // pathForSelection to the current selection so the shortcut fires.\n const pathForSelection =\n anchor && options?.fromInternalLink && cleanPath === ''\n ? selectedPathRef.current\n : stripFolderIndexFromPath(cleanPath, folderIndexFile);\n if (anchor && options?.fromInternalLink && pathForSelection === selectedPathRef.current) {\n navigateSamePageHash(anchor, { headerOffset: HUB_HEADER_OFFSET_PX });\n return;\n }\n\n const scrollAfterNav = () => {\n if (anchor) {\n setTimeout(() => {\n const el = document.getElementById(anchor.substring(1));\n if (el) {\n scrollElementIntoView(el, { headerOffset: HUB_HEADER_OFFSET_PX });\n } else {\n scrollToContent();\n }\n }, 300);\n } else {\n setTimeout(() => {\n scrollToContent();\n }, 150);\n }\n };\n\n if (options?.expandFolder) {\n lastFetchedPath.current = null;\n setSelectedPath(cleanPath);\n window.history.pushState({}, '', `${normalizedBaseRoute}/${cleanPath}${anchor}`);\n\n const pathParts = cleanPath.split('/');\n const nodeIdsToExpand: string[] = [];\n let currentPath = '';\n\n for (const part of pathParts) {\n currentPath = currentPath ? `${currentPath}-${part}` : part;\n nodeIdsToExpand.push(currentPath.toLowerCase());\n }\n\n setExpandedNodes(new Set(nodeIdsToExpand));\n\n if (options?.fromInternalLink) {\n scrollAfterNav();\n }\n\n setError(null);\n return;\n }\n\n // `pathForSelection` was already computed above (inside the\n // same-doc-anchor shortcut check); reuse it here for cross-doc nav.\n const urlPath = pathForSelection;\n\n lastFetchedPath.current = null;\n setSelectedPath(pathForSelection);\n window.history.pushState({}, '', `${normalizedBaseRoute}/${urlPath}${anchor}`);\n\n if (options?.fromInternalLink) {\n scrollAfterNav();\n }\n\n const pathParts = cleanPath.split('/');\n const parentIds: string[] = [];\n let currentPath = '';\n const partsToProcess = cleanPath.includes('.') ? pathParts.slice(0, -1) : pathParts;\n\n for (const part of partsToProcess) {\n currentPath = currentPath ? `${currentPath}-${part}` : part;\n parentIds.push(currentPath.toLowerCase());\n }\n\n if (parentIds.length > 0) {\n setExpandedNodes(new Set(parentIds));\n }\n },\n [normalizedBaseRoute, folderIndexFile],\n );\n\n useEffect(() => {\n return docNavigation.register({\n baseRoute: normalizedBaseRoute,\n findNodeByPath: path => {\n const clean = stripFolderIndexFromPath(path.replace(/\\/$/, '').split('#')[0], folderIndexFile);\n return findDocNodeByPath(clean, structure) ?? null;\n },\n selectNode,\n });\n }, [docNavigation, normalizedBaseRoute, structure, selectNode, folderIndexFile]);\n\n // A folder with no README and no documents anywhere under it has no body at\n // all, and that is a pure fact about `structure` + `selectedPath` — both\n // available while rendering. Derived here rather than written into state from\n // the content effect, which reached the same conclusion one commit later and\n // meanwhile left the PREVIOUS document's body on screen.\n const isEmptyFolder = useMemo(() => {\n if (!selectedPath || structure.length === 0) return false;\n const node = findDocNodeByPath(selectedPath, structure);\n return !!node && node.type === 'folder' && !node.hasReadme && !findFirstDocPath(node);\n }, [selectedPath, structure]);\n\n return {\n structure,\n selectedPath,\n setSelectedPath,\n content: isEmptyFolder ? null : content,\n isLoadingStructure,\n isLoadingContent: isEmptyFolder ? false : isLoadingContent,\n error: isEmptyFolder ? null : error,\n expandedNodes,\n toggleNode,\n selectNode,\n navigateToDoc,\n refetch: fetchStructure,\n setExpandedNodes,\n };\n}\n\nexport type { DocNode, DocContent };\n","'use client';\n\nimport { useState, useRef, useCallback, useEffect, useMemo } from 'react';\nimport { scrollElementIntoView } from '../../utils/scroll-into-view';\n\n// Single source for the sticky-chrome height. Used for BOTH the scroll target\n// offset (where a clicked section lands) AND the active-section detection\n// threshold (where the scroll listener flips highlight). They must match —\n// previously 100 vs 150 caused a 50px window where the indicator jumped to\n// the next section even though that section's top was still below the\n// clicked one's resting offset.\nconst SCROLL_OFFSET = 100;\n\n// Separator for the section-id key. A newline cannot occur inside an HTML id,\n// so the join is unambiguous and the key can be split back apart.\nconst ID_SEPARATOR = '\\n';\n\ninterface ScrollSpySection {\n id: string;\n title?: string;\n level?: number;\n}\n\ninterface UseScrollSpyReturn {\n activeSection: string;\n handleSectionClick: (sectionId: string) => void;\n}\n\n/**\n * Shared scroll spy hook for tracking active section based on scroll position.\n * Used by DocViewer for sticky section navigation.\n */\nexport function useScrollSpy(sections: ScrollSpySection[] | undefined): UseScrollSpyReturn {\n const [activeSection, setActiveSection] = useState('');\n const isScrollingFromClick = useRef(false);\n\n // The scroll listener only ever needs the section IDS, and callers rebuild\n // the `sections` array on every render — so the value-stable joined key IS\n // the input, and the effect unpacks it again. That replaces a ref written\n // during render whose only job was to keep the array's churning identity out\n // of the dependency array.\n const sectionIdsKey = useMemo(() => sections?.map(s => s.id).join(ID_SEPARATOR) ?? '', [sections]);\n\n const handleSectionClick = useCallback((sectionId: string) => {\n const targetElement = document.getElementById(sectionId);\n if (!targetElement) return;\n\n isScrollingFromClick.current = true;\n setActiveSection(sectionId);\n\n scrollElementIntoView(targetElement, { headerOffset: SCROLL_OFFSET });\n\n setTimeout(() => {\n isScrollingFromClick.current = false;\n }, 800);\n }, []);\n\n useEffect(() => {\n const sectionIds = sectionIdsKey === '' ? [] : sectionIdsKey.split(ID_SEPARATOR);\n if (sectionIds.length === 0) return undefined;\n\n const handleScroll = () => {\n if (isScrollingFromClick.current) return;\n\n const scrollPosition = window.scrollY + SCROLL_OFFSET;\n let currentSection = sectionIds[0] ?? '';\n\n for (let i = sectionIds.length - 1; i >= 0; i--) {\n const element = document.getElementById(sectionIds[i]);\n if (element && scrollPosition >= element.offsetTop) {\n currentSection = sectionIds[i];\n break;\n }\n }\n\n setActiveSection(prev => (prev !== currentSection ? currentSection : prev));\n };\n\n let scrollTimer: ReturnType<typeof setTimeout>;\n const throttledScroll = () => {\n clearTimeout(scrollTimer);\n scrollTimer = setTimeout(handleScroll, 100);\n };\n\n window.addEventListener('scroll', throttledScroll);\n handleScroll();\n\n return () => {\n window.removeEventListener('scroll', throttledScroll);\n clearTimeout(scrollTimer);\n };\n }, [sectionIdsKey]);\n\n return { activeSection, handleSectionClick };\n}\n","/**\n * Text-style skeleton — matches a rendered markdown article layout.\n * Used by `<DocsHubPage>` as the default for `markdown` (and unknown\n * document types). Embedders can override via `renderSkeleton`.\n */\nexport function MarkdownSkeleton() {\n return (\n <div className=\"mt-6 space-y-7\">\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-3/4 animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-5/6 animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-2/3 animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"h-[88px] animate-pulse rounded-lg border border-ods-border bg-ods-card\" />\n <div className=\"h-7 w-1/3 animate-pulse rounded bg-ods-border\" />\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-[72%] animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-[58%] animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"h-7 w-2/5 animate-pulse rounded bg-ods-border\" />\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-[90%] animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-[70%] animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"h-[88px] animate-pulse rounded-lg border border-ods-border bg-ods-card\" />\n </div>\n );\n}\n\n/**\n * Embed-style skeleton — matches the iframe loading state for `pdf`,\n * `google_sheet`, `figma`, and `file` document types. Used by\n * `<DocsHubPage>` as the default for non-markdown documentTypes.\n *\n * The skeleton is documentType-aware so its layout matches the actual\n * viewer that will replace it:\n * - `pdf` → header with title + TWO buttons (Preview, Download)\n * - `google_sheet`/`figma` → header with title + ONE button/toggle\n * - `file` → centered FileDownloadCard-style box\n * - undefined / others → generic (1-button header)\n *\n * IMPORTANT: bars use `bg-ods-border` (NOT `bg-ods-skeleton`). The\n * `--ods-skeleton` token resolves to TRANSPARENT in this build, leaving\n * the skeleton box visually empty — the embed skeleton was the loudest\n * surface affected (a full-height iframe area showing nothing). Same fix\n * the chat-message-row skeleton already documents in its inline comment.\n */\nexport interface EmbedSkeletonProps {\n /** When provided, the header layout matches the eventual viewer's\n * button count + arrangement, so the layout doesn't shift on load. */\n documentType?: 'pdf' | 'google_sheet' | 'figma' | 'file' | string;\n}\n\nexport function EmbedSkeleton({ documentType }: EmbedSkeletonProps = {}) {\n // Centered card shape for the `file` documentType — matches\n // `<FileDownloadCard>`'s `flex flex-col items-center justify-center py-16`\n // + bordered card with icon, name, type/size row, Download button.\n if (documentType === 'file') {\n return (\n <div className=\"flex flex-col items-center justify-center py-16\">\n <div className=\"w-full max-w-md space-y-4 rounded-xl border border-ods-border bg-ods-card p-8 text-center\">\n <div className=\"mx-auto h-16 w-16 animate-pulse rounded bg-ods-border\" />\n <div className=\"space-y-2\">\n <div className=\"mx-auto h-5 w-2/3 animate-pulse rounded bg-ods-border\" />\n <div className=\"mx-auto h-4 w-1/2 animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"h-10 w-full animate-pulse rounded bg-ods-border\" />\n </div>\n </div>\n );\n }\n\n // PDF viewer has TWO buttons (Preview + Download); Sheets / Figma\n // render ONE (Open / view-toggle). Default to one for unknown types.\n const buttonCount = documentType === 'pdf' ? 2 : 1;\n\n return (\n <div className=\"space-y-4\">\n {/* Header — matches the actual viewer's\n * `flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`\n * (mobile-stacked, desktop-row). */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n {/* Left: icon + title */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <div className=\"h-5 w-5 shrink-0 animate-pulse rounded bg-ods-border\" />\n <div className=\"h-6 w-2/3 animate-pulse rounded bg-ods-border\" />\n </div>\n {/* Right: 1 or 2 buttons. Mobile = full-width; desktop = auto. */}\n <div className=\"flex w-full items-center gap-2 sm:w-auto\">\n {Array.from({ length: buttonCount }).map((_, i) => (\n <div key={i} className=\"h-10 w-full flex-1 animate-pulse rounded bg-ods-border sm:w-32 sm:flex-initial\" />\n ))}\n </div>\n </div>\n {/* Body — clean iframe-sized rectangle, no fake inner placeholder\n * cruft. Matches the viewer's default `calc(100vh - 250px)` height. */}\n <div\n className=\"w-full animate-pulse rounded-lg border border-ods-border bg-ods-card\"\n style={{ height: 'calc(100vh - 250px)' }}\n />\n </div>\n );\n}\n","import type React from 'react';\nimport type { DocContent, DocRenderHandlers, DocumentType } from '../../types/doc-source';\nimport { FigmaEmbed } from '../embeds/figma-embed';\nimport { FileDownloadCard } from '../embeds/file-download-card';\nimport { GoogleSheetsViewer } from '../embeds/google-sheets-viewer';\nimport { PdfViewer } from '../embeds/pdf-viewer';\nimport { DocViewer, type DocViewerProps } from './doc-viewer';\nimport { MarkdownSkeleton, EmbedSkeleton } from './skeletons';\n\ntype DocRenderer = (content: DocContent, handlers: DocRenderHandlers) => React.ReactNode;\n\n/**\n * Per-document-type renderer map. `markdown` is required (the lib does NOT\n * ship a default markdown renderer — embedders pick their own library +\n * sanitization to avoid an XSS surface in the lib).\n *\n * `pdf` / `google_sheet` / `figma` / `file` are optional — the lib provides\n * defaults from `components/embeds`. Override only when you want different\n * props than the default (e.g. a custom PDF toolbar, embedded credentials).\n */\nexport type DocumentTypeRenderers = { markdown: DocRenderer } & Partial<Record<DocumentType, DocRenderer>>;\n\nexport interface DocsHubPageProps extends Omit<DocViewerProps, 'renderContent' | 'renderSkeleton' | 'showAIChat'> {\n /** Per-document-type renderer map. `markdown` is REQUIRED. */\n documentTypeRenderers: DocumentTypeRenderers;\n\n /** Renderer for unknown / future document types. Defaults to a lib-styled\n * \"Unsupported document type\" message. */\n fallbackRenderer?: DocRenderer;\n\n /** Loading skeleton picker. Defaults: `markdown` / `undefined` →\n * `<MarkdownSkeleton>`, everything else → `<EmbedSkeleton>`. */\n renderSkeleton?: (documentType: DocumentType | undefined) => React.ReactNode;\n\n /** Defaults to `true` (the embeddable wrapper favors the chat-enabled\n * experience). Only mounts the in-source RAG search bar\n * (`<DocSearchBar>`) — does NOT require `ChatRuntimeContext`. */\n showAIChat?: boolean;\n}\n\nconst DEFAULT_TITLE = 'Documents';\n\nconst defaultFallbackRenderer: DocRenderer = () => (\n <div className=\"py-16 text-center\">\n <p className=\"text-ods-text-secondary\">Unsupported document type</p>\n </div>\n);\n\n// When the DAL hasn't populated the URL field for a rich-content type, the\n// embed-viewer components render a broken iframe (empty src). Fall back to\n// the same lib-styled \"Unsupported document type\" panel the explicit\n// fallback uses — the surface is honest about the missing data instead of\n// pretending to load. `FileDownloadCard` handles its own missing-URL state\n// (hides the Download button), so it doesn't need this guard.\nconst defaultPdfRenderer: DocRenderer = (content, handlers) =>\n content.fileUrl ? (\n <PdfViewer src={content.fileUrl} fileName={content.fileName} />\n ) : (\n defaultFallbackRenderer(content, handlers)\n );\n\nconst defaultGoogleSheetRenderer: DocRenderer = (content, handlers) =>\n content.externalUrl ? (\n <GoogleSheetsViewer externalUrl={content.externalUrl} fileName={content.fileName} />\n ) : (\n defaultFallbackRenderer(content, handlers)\n );\n\nconst defaultFigmaRenderer: DocRenderer = (content, handlers) =>\n content.externalUrl ? (\n <FigmaEmbed url={content.externalUrl} title={content.fileName} loading=\"eager\" />\n ) : (\n defaultFallbackRenderer(content, handlers)\n );\n\nconst defaultFileRenderer: DocRenderer = content => (\n <FileDownloadCard\n fileName={content.fileName}\n mimeType={content.mimeType}\n fileSize={content.fileSize}\n fileUrl={content.fileUrl}\n />\n);\n\nconst defaultRenderSkeleton = (documentType: DocumentType | undefined) =>\n !documentType || documentType === 'markdown' ? (\n <MarkdownSkeleton />\n ) : (\n // Forward the documentType so the embed skeleton renders the right\n // shape (PDF=2 buttons, sheets/figma=1 button, file=centered card).\n <EmbedSkeleton documentType={documentType} />\n );\n\n/**\n * Embeddable docs-hub page. Bundles `<DocViewer>` with safe defaults so the\n * minimum embed is a one-line mount (consumer only has to supply\n * `documentTypeRenderers.markdown`).\n *\n * Used by the hub at `/knowledge-base` and `/data-room`, and by third-party\n * React apps that embed the docs experience behind their own proxy. See\n * `docs/EMBEDDING_DOCS_HUB.md` for the embedder setup.\n *\n * SEO note: this component is `'use client'` (via the docs barrel) — server-\n * side SEO is the host's responsibility. The hub's `<DocSeoContent>` is the\n * canonical implementation embedders can reference.\n */\nexport function DocsHubPage({\n title = DEFAULT_TITLE,\n documentTypeRenderers,\n fallbackRenderer = defaultFallbackRenderer,\n renderSkeleton = defaultRenderSkeleton,\n showAIChat = true,\n className = 'min-h-screen',\n sidebarLabel = 'DOCUMENTATION',\n ...docViewerProps\n}: DocsHubPageProps) {\n const resolvedRenderers: DocumentTypeRenderers = {\n markdown: documentTypeRenderers.markdown,\n pdf: documentTypeRenderers.pdf ?? defaultPdfRenderer,\n google_sheet: documentTypeRenderers.google_sheet ?? defaultGoogleSheetRenderer,\n figma: documentTypeRenderers.figma ?? defaultFigmaRenderer,\n file: documentTypeRenderers.file ?? defaultFileRenderer,\n };\n\n const renderContent: DocViewerProps['renderContent'] = (content, handlers) => {\n const type = content.documentType ?? 'markdown';\n const renderer = resolvedRenderers[type] ?? fallbackRenderer;\n return renderer(content, handlers);\n };\n\n return (\n <DocViewer\n {...docViewerProps}\n title={title}\n showAIChat={showAIChat}\n className={className}\n sidebarLabel={sidebarLabel}\n renderContent={renderContent}\n renderSkeleton={renderSkeleton}\n />\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-ORQQZQBB.cjs","../src/components/docs/doc-viewer.tsx","../src/components/loading/unified-skeleton.tsx","../src/components/loading/card-skeleton.tsx","../src/components/loading/page-layout-skeleton.tsx","../src/components/persistent-filter-controls.tsx","../src/components/docs/doc-navigation-context.tsx","../src/components/docs/use-docs-resolve-link.ts","../src/components/docs/use-document-tree.ts","../src/components/docs/use-scroll-spy.ts","../src/components/docs/skeletons.tsx","../src/components/docs/docs-hub-page.tsx"],"names":["jsx","jsxs","Fragment","useCallback","readString","useRef","pathParts","currentPath","useState","useMemo"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-ORQQZQBB.cjs","sourcesContent":[null,"'use client';\n\nimport type React from 'react';\nimport { useMemo } from 'react';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport type { DocContent, DocNode, DocRenderHandlers, DocSourceId } from '../../types/doc-source';\nimport { findDocNodeByPath } from '../../utils/doc-tree-nav';\nimport { PageShell } from '../layout/article-detail-layout';\nimport { PageLayout } from '../layout/page-layout';\nimport { CategorySidebarSkeleton } from '../loading/page-layout-skeleton';\nimport { MultiLevelNavigation, MobileNavigationDropdown } from '../navigation/multi-level-navigation';\nimport { StickySectionNav } from '../navigation/sticky-section-nav';\nimport { PersistentSidebar, PersistentMobileDropdown } from '../persistent-filter-controls';\nimport { DocSearchBar, useDocSearch } from '../shared/doc-search';\nimport { useDocNavigation } from './doc-navigation-context';\nimport { useDocsResolveLink } from './use-docs-resolve-link';\nimport { useDocumentTree } from './use-document-tree';\nimport { useScrollSpy } from './use-scroll-spy';\n\n/** Color tokens for the doc-viewer chrome. Hub-side `DocViewer` callers share\n * this constant; no need to override per source — the palette is intentionally\n * uniform across knowledge-base + data-room (both use ODS dark tokens). */\nexport const DEFAULT_DOC_VIEWER_PALETTE = {\n background: 'bg-ods-bg',\n containerBackground: 'transparent',\n headerText: 'text-ods-text-primary',\n primaryText: 'text-ods-text-primary',\n secondaryText: 'text-ods-text-secondary',\n accent: 'var(--ods-accent)',\n border: 'border-ods-border',\n cardBackground: 'bg-ods-card',\n} as const;\n\nexport interface DocViewerProps {\n /**\n * Registry source id (`'openframe-docs'`, `'data-room-docs'`, …). Flowed through\n * `renderContent`'s handlers for `/api/docs/resolve-link` POSTs.\n */\n sourceId: DocSourceId;\n\n /**\n * Render the content body. The page shell owns this — it picks the markdown\n * renderer, dispatches PDF/Figma/Sheets/file branches, etc. No renderer\n * interface in between.\n */\n renderContent: (content: DocContent, handlers: DocRenderHandlers) => React.ReactNode;\n\n /**\n * Render the loading skeleton. Receives the selected node's `documentType`\n * (undefined while structure is still loading) so the caller can return a\n * markdown-shaped skeleton vs an embed-shaped skeleton.\n */\n renderSkeleton: (documentType: DocNode['documentType']) => React.ReactNode;\n\n /**\n * Chat-source identifier — passed in by the page shell from server-side\n * `currentPlatform()`. Lib has no platform context; the page shell is the\n * trusted boundary that wires this. NEVER pass user input here.\n */\n chatSource: string;\n\n /** Page title — rendered as the inline hero `<h1>` (same DOM\n * `<DevSectionView>`'s hero uses) so the doc-viewer chrome matches the\n * dev-section pages. ReactNode is intentionally not supported here —\n * every consumer renders the same typography. */\n title?: string;\n /** Optional icon rendered inline before the title text — same slot\n * `<DevSectionView>`'s hero uses (Map for Roadmap, Rocket for Releases,\n * etc.). Pass a pre-rendered React element styled with\n * `SECTION_HERO_ICON_CLASS` (`h-10 w-10 text-ods-accent`) for visual\n * parity with other lib pages. */\n titleIcon?: React.ReactNode;\n /** Subtitle (h6, secondary text) rendered beneath the title. */\n subtitle?: string;\n /** Render a yellow accent dot (`.`) after the title — same flag as\n * the hub's legacy `<AdminPageHeader accentDot>` so the docs-hub\n * surface keeps its existing accent styling after the migration. */\n accentDot?: boolean;\n /** Override the default ODS palette. Optional — most callers should omit. */\n colorPalette?: typeof DEFAULT_DOC_VIEWER_PALETTE;\n className?: string;\n /** Render the standalone `<PageShell>` (own `<main>` + bg + max-width). Default\n * true. Pass false when the host layout already provides the page container —\n * only the padding box renders, avoiding a nested `<main>`. */\n shell?: boolean;\n\n /** Initial doc path (URL `[...path]`). */\n docPath?: string;\n\n /** Sidebar header copy (`'DOCUMENTATION'`, `'DATA ROOM'`). */\n sidebarLabel?: string;\n\n /**\n * API endpoint for fetching the document tree structure. Defaults to the\n * dispatcher path `/api/docs/sources/${sourceId}/structure`. Override only\n * if hosting the viewer behind a different route.\n */\n structureEndpoint?: string;\n /** Same shape as `structureEndpoint`. Defaults to `/api/docs/sources/${sourceId}/content`. */\n contentEndpoint?: string;\n /** RAG-search endpoint that backs the in-source search bar (when `showAIChat`\n * is on). Defaults to `/api/docs/search`. Override for proxy-prefix embeds —\n * same injectability pattern as `structureEndpoint` / `contentEndpoint`. */\n searchEndpoint?: string;\n /** POST internal-link resolver. The viewer threads an async `onResolveLink`\n * into `renderContent`'s `handlers` that posts `{ link, currentPath, source }`\n * here. Defaults to `/api/docs/resolve-link`. Override for proxy-prefix embeds —\n * same injectability pattern as `structureEndpoint` / `contentEndpoint` /\n * `searchEndpoint`, with `ChatRuntime.endpoints.docsResolveLinkUrl` as a\n * runtime fallback (prop → runtime → default). */\n resolveLinkEndpoint?: string;\n /** Base route path for URL navigation. */\n baseRoute: string;\n\n /** Empty state copy when no doc is selected. */\n emptyStateText?: string;\n\n /** Whether to render the doc-search bar (bound to chat). */\n showAIChat?: boolean;\n\n /** Folder-index filename (default `'README.md'`). */\n folderIndexFile?: string;\n\n /** Back-button shown above the title. Mirrors `<DevSectionPage>` /\n * `<HelpCenterList>` / `<LegalDocumentPage>` so every embeddable surface\n * shares the same chrome. Defaults to `{ label: 'Back to home', href: '/' }`.\n * Pass `false` to hide; pass `{ href: '/docs' }` etc. when the embed's\n * home isn't `/`. */\n backButton?: { label?: string; href?: string } | false;\n}\n\nexport function DocViewer(props: DocViewerProps) {\n return <DocViewerContent {...props} />;\n}\n\nfunction DocViewerContent({\n sourceId,\n renderContent,\n renderSkeleton,\n chatSource,\n title,\n subtitle,\n colorPalette = DEFAULT_DOC_VIEWER_PALETTE,\n className = '',\n shell = true,\n docPath,\n sidebarLabel = 'DOCUMENTATION',\n structureEndpoint,\n contentEndpoint,\n searchEndpoint,\n resolveLinkEndpoint,\n baseRoute,\n emptyStateText,\n showAIChat = false,\n folderIndexFile,\n backButton,\n}: DocViewerProps) {\n // Default endpoints derived from sourceId. Hub callers omit the props in 99%\n // of cases; the override is for embed contexts where the doc-viewer sits\n // behind a non-standard route.\n const resolvedStructureEndpoint = structureEndpoint ?? `/api/docs/sources/${sourceId}/structure`;\n const resolvedContentEndpoint = contentEndpoint ?? `/api/docs/sources/${sourceId}/content`;\n // Resolve-link endpoint chain (prop → ChatRuntime.endpoints → hub default)\n // + the full fetch + JSON-parse pipeline live in `useDocsResolveLink`.\n // Keeping it factored out as a proper hook makes the contract reusable\n // by any embedder rendering doc content outside `<DocViewer>` (custom\n // markdown renderers, link-resolver previews, etc.) and keeps this\n // component focused on layout + state.\n const resolveLink = useDocsResolveLink(sourceId, resolveLinkEndpoint);\n const {\n structure,\n selectedPath,\n content,\n isLoadingStructure,\n isLoadingContent,\n error,\n expandedNodes,\n selectNode,\n toggleNode,\n navigateToDoc,\n } = useDocumentTree(\n {\n structureEndpoint: resolvedStructureEndpoint,\n contentEndpoint: resolvedContentEndpoint,\n baseRoute,\n folderIndexFile,\n },\n docPath,\n );\n\n const { activeSection, handleSectionClick } = useScrollSpy(content?.sections);\n\n const docNav = useDocNavigation();\n\n // Back-button config — mirrors `<DevSectionPage>` so the docs surface\n // matches every other embeddable page's chrome. Default target is `/`\n // (the embed's home); pass `backButton: false` to hide entirely, or\n // override the href when the embed's home isn't `/`.\n const router = useRouter();\n const backCfg =\n backButton === false\n ? null\n : {\n label: backButton?.label ?? 'Back to home',\n onClick: () => router.push(backButton?.href ?? '/'),\n };\n const docSearch = useDocSearch({\n source: chatSource,\n baseRoute,\n searchEndpoint,\n onNavigate: path => navigateToDoc(path, { fromInternalLink: true }),\n onInPageSwap: path => docNav.navigate(path),\n });\n\n const renderedContent = useMemo(() => {\n if (!content) return null;\n return renderContent(content, {\n onInternalLinkClick: navigateToDoc,\n // Relative-link base = the RENDERED document's path, NOT `selectedPath`.\n // They diverge for a no-README folder: selection stays on the folder\n // (e.g. `repo/diagrams`) while the body is its first descendant doc\n // (e.g. `repo/diagrams/architecture/README.md`, via `findFirstDocPath`).\n // Resolving `./sibling.mmd` against the folder would 404; resolving it\n // against `content.path` lands in the descendant's directory. The DAL\n // sets `content.path` to the served doc in all cases (file / README\n // folder / first-child fallback), so this is correct everywhere.\n currentPath: content.path,\n sourceId,\n onResolveLink: resolveLink,\n });\n }, [content, renderContent, navigateToDoc, sourceId, resolveLink]);\n\n // Selected node's documentType drives:\n // - which skeleton the caller renders during fetch (markdown vs embed)\n // - the article max-width + sticky-nav visibility (markdown only)\n // `undefined` documentType is treated as `'markdown'` (per the DocNode\n // discriminator's documented default).\n const selectedNodeDocType =\n selectedPath && structure.length > 0 ? findDocNodeByPath(selectedPath, structure)?.documentType : undefined;\n // During loading, the in-flight content's type isn't known yet — fall back\n // to the selected node's type (or markdown if neither is set).\n const activeDocType = content?.documentType ?? selectedNodeDocType;\n const isMarkdownContent = !activeDocType || activeDocType === 'markdown';\n const showStickyNav = isMarkdownContent;\n\n const stickyNavSections = content?.sections?.map(s => ({ id: s.id, label: s.title })) ?? [];\n\n const isColorValue =\n colorPalette.background.startsWith('#') ||\n colorPalette.background.startsWith('rgb') ||\n colorPalette.background.startsWith('var(');\n\n const bgStyle = isColorValue ? { backgroundColor: colorPalette.background } : {};\n const bgClass = !isColorValue ? colorPalette.background : '';\n const containerBgStyle =\n colorPalette.containerBackground !== 'transparent' ? { backgroundColor: colorPalette.containerBackground } : {};\n\n const defaultEmptyText =\n structure.length > 0\n ? 'Select a document from the sidebar to view'\n : 'No documents yet. Add content from the admin panel.';\n const resolvedEmptyText = emptyStateText || defaultEmptyText;\n\n // Unified header: title/subtitle route through the canonical (frozen)\n // `PageLayout` `TitleBlock` (text-h2) — same as every other help-center page —\n // so the docs hub shares one header. The `gap-10` column then holds the search\n // bar + content grid. `colorPalette` / `className` / `bgStyle` flow through the\n // shell's contentClassName + an inner style-passthrough wrapper.\n const inner = (\n <div style={{ ...bgStyle, ...containerBgStyle }}>\n <PageLayout title={title} subtitle={subtitle} titleSize=\"h1\" titleWrap backButton={backCfg ?? undefined}>\n <div className=\"flex w-full flex-col gap-10\">\n {showAIChat && (\n <DocSearchBar\n placeholder={`Search ${sidebarLabel?.toLowerCase() || 'documents'}...`}\n query={docSearch.query}\n onQueryChange={docSearch.setQuery}\n results={docSearch.results}\n isLoading={docSearch.isLoading}\n onResultSelect={docSearch.handleResultSelect}\n showDropdown={docSearch.keepDropdownOpen}\n />\n )}\n\n {error && (\n <div className=\"flex justify-center\">\n <div className=\"max-w-md rounded-lg border border-ods-border bg-ods-card p-8 text-center\">\n <h2 className=\"text-ods-text-primary text-h3\">Error Loading Documents</h2>\n <p className=\"mt-2 text-ods-text-secondary\">{error}. Please try again later.</p>\n </div>\n </div>\n )}\n\n {!error && (\n <div className=\"flex flex-1 flex-col items-start gap-6 lg:flex-row lg:gap-10\">\n <div className=\"w-full lg:w-[320px] lg:shrink-0\">\n <div className=\"lg:sticky lg:top-20\">\n {isLoadingStructure ? (\n <CategorySidebarSkeleton />\n ) : (\n <>\n <PersistentMobileDropdown isLoading={false}>\n <MobileNavigationDropdown\n nodes={structure}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={selectNode}\n onToggleExpand={toggleNode}\n isLoading={false}\n folderIndexFile={folderIndexFile}\n />\n </PersistentMobileDropdown>\n\n <PersistentSidebar isLoading={false}>\n <div className=\"hidden lg:block\">\n <div className=\"space-y-4\">\n <h3 className=\"font-semibold text-ods-text-secondary text-h5\">{sidebarLabel}</h3>\n <MultiLevelNavigation\n nodes={structure}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={selectNode}\n onToggleExpand={toggleNode}\n isLoading={false}\n folderIndexFile={folderIndexFile}\n />\n </div>\n </div>\n </PersistentSidebar>\n </>\n )}\n </div>\n </div>\n\n <div className=\"w-full min-w-0 flex-1\">\n <div\n className={`grid grid-cols-1 ${\n // \"On this page\" right column only makes sense for\n // MARKDOWN content (PDFs / Sheets / Figma / file have no\n // sections to navigate to). Gating the grid template on\n // `isMarkdownContent` also suppresses the section-skeleton\n // bars during embed loads — the user-reported \"skeleton\n // shouldn't be on file pages\" bug.\n isMarkdownContent &&\n ((showStickyNav && stickyNavSections.length > 0) || isLoadingContent || isLoadingStructure)\n ? 'lg:grid-cols-[1fr_280px]'\n : ''\n } gap-8`}\n >\n {/* min-w-0: grid items default to min-width:auto, which would\n let a long unbreakable token push this column past the\n track width. Pair with the inherited overflow-wrap:anywhere\n (app-globals.css) so content wraps instead of overflowing. */}\n <div className={`w-full min-w-0 ${isMarkdownContent ? 'mx-auto max-w-4xl' : ''}`}>\n <article className=\"space-y-2\">\n {isLoadingContent || isLoadingStructure ? (\n renderSkeleton(selectedNodeDocType)\n ) : !content ? (\n <div className=\"py-16 text-center\">\n <p className=\"text-ods-text-secondary text-h4\">{resolvedEmptyText}</p>\n </div>\n ) : (\n renderedContent\n )}\n </article>\n </div>\n\n {isMarkdownContent && (isLoadingContent || isLoadingStructure) && (\n <div className=\"hidden lg:block\">\n <div className=\"sticky top-24\">\n <div className=\"mb-5 h-[14px] w-28 animate-pulse rounded bg-ods-border\" />\n <div className=\"space-y-0\">\n {[130, 170, 190, 220, 110, 200, 80, 100, 120, 140, 90].map((w, i) => (\n <div\n key={i}\n className={`border-l-2 py-[13px] pl-3 ${\n i === 0 ? 'border-ods-accent' : 'border-transparent'\n }`}\n >\n <div className=\"h-[13px] animate-pulse rounded bg-ods-border\" style={{ width: w }} />\n </div>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {showStickyNav && content && stickyNavSections.length > 0 && !isLoadingContent && (\n <div className=\"hidden lg:block\">\n <div className=\"sticky top-24\">\n <h3 className=\"mb-4 font-semibold text-ods-text-secondary text-h5\">ON THIS PAGE</h3>\n <StickySectionNav\n sections={stickyNavSections}\n activeSection={activeSection}\n onSectionClick={handleSectionClick}\n ribbonPosition=\"left\"\n ribbonColor=\"var(--ods-accent)\"\n />\n </div>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n </PageLayout>\n </div>\n );\n\n // `shell` true → standalone `<PageShell>`; false → padding-only box (no nested\n // <main>) for hosts whose layout already provides the container. Both carry the\n // palette/className via the same `page-shell-content` styling hook.\n return shell ? (\n <PageShell contentClassName={`${bgClass} ${className}`}>{inner}</PageShell>\n ) : (\n <div className={`page-shell-content ${bgClass} ${className}`.trim()}>{inner}</div>\n );\n}\n","import type React from 'react';\nimport { cn } from '../../utils/cn';\n\ninterface UnifiedSkeletonProps extends React.HTMLAttributes<HTMLDivElement> {\n className?: string;\n variant?: 'default' | 'text' | 'circular' | 'rectangular';\n width?: string | number;\n height?: string | number;\n /**\n * Disable animation for users who prefer reduced motion\n */\n animate?: boolean;\n /**\n * Accessibility label for screen readers\n */\n 'aria-label'?: string;\n}\n\n/**\n * UnifiedSkeleton Component\n *\n * Base skeleton component with consistent styling across the application.\n *\n * Design Specifications:\n * - Color: #2A2A2A base with #3A3A3A pulse highlight\n * - Animation: 1.5s pulse duration, ease-in-out timing\n * - Responsive: Mobile-first approach with proper scaling\n * - Accessibility: ARIA labels and reduced motion support\n *\n * @param variant - Shape variant: default (rounded), text (rounded-sm), circular, rectangular\n * @param width - Custom width (defaults to full width)\n * @param height - Custom height (defaults to auto based on variant)\n * @param animate - Enable/disable animation (respects prefers-reduced-motion)\n * @param aria-label - Accessibility label for screen readers\n */\nexport function UnifiedSkeleton({\n className,\n variant = 'default',\n width,\n height,\n animate = true,\n 'aria-label': ariaLabel,\n ...props\n}: UnifiedSkeletonProps) {\n const baseClasses = 'bg-ods-skeleton';\n\n const variantClasses = {\n default: 'rounded-md',\n text: 'rounded-sm',\n circular: 'rounded-full',\n rectangular: 'rounded-none',\n };\n\n const defaultHeights = {\n default: 'h-4',\n text: 'h-4',\n circular: 'h-12 w-12',\n rectangular: 'h-4',\n };\n\n const animationClasses = animate ? 'animate-pulse motion-reduce:animate-none' : '';\n\n const style: React.CSSProperties = {\n width: width,\n height: height,\n };\n\n return (\n <div\n className={cn(\n baseClasses,\n variantClasses[variant],\n !height && !style.height && defaultHeights[variant],\n !width && !style.width && variant !== 'circular' && 'w-full',\n animationClasses,\n className,\n )}\n style={style}\n role=\"status\"\n aria-label={ariaLabel || 'Loading content'}\n {...props}\n />\n );\n}\n\n/**\n * Text skeleton variants for consistent typography loading\n */\nexport const TextSkeleton = {\n /**\n * Large heading skeleton (h1, h2)\n */\n Heading: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"text\"\n className={cn('h-8 md:h-10 lg:h-12', className)}\n aria-label=\"Loading heading\"\n {...props}\n />\n ),\n\n /**\n * Medium heading skeleton (h3, h4)\n */\n Subheading: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"text\"\n className={cn('h-6 md:h-7 lg:h-8', className)}\n aria-label=\"Loading subheading\"\n {...props}\n />\n ),\n\n /**\n * Body text skeleton\n */\n Body: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton variant=\"text\" className={cn('h-4 md:h-5', className)} aria-label=\"Loading text\" {...props} />\n ),\n\n /**\n * Small text skeleton (captions, metadata)\n */\n Caption: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton variant=\"text\" className={cn('h-3 md:h-4', className)} aria-label=\"Loading caption\" {...props} />\n ),\n};\n\n/**\n * Interactive element skeletons\n */\nexport const InteractiveSkeleton = {\n /**\n * Button skeleton with proper touch targets\n */\n Button: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn('h-10 w-32 md:h-12 md:w-40', className)}\n aria-label=\"Loading button\"\n {...props}\n />\n ),\n\n /**\n * Input field skeleton\n */\n Input: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn('h-10 w-full md:h-12', className)}\n aria-label=\"Loading input field\"\n {...props}\n />\n ),\n\n /**\n * Filter chip skeleton\n */\n Chip: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn('h-8 w-20 rounded-full', className)}\n aria-label=\"Loading filter\"\n {...props}\n />\n ),\n};\n\n/**\n * Media skeletons for images and icons\n */\nexport const MediaSkeleton = {\n /**\n * Avatar/profile image skeleton\n */\n Avatar: ({\n size = 'md',\n className,\n ...props\n }: Omit<UnifiedSkeletonProps, 'variant'> & { size?: 'sm' | 'md' | 'lg' }) => {\n const sizeClasses = {\n sm: 'h-8 w-8',\n md: 'h-12 w-12',\n lg: 'h-16 w-16',\n };\n\n return (\n <UnifiedSkeleton\n variant=\"circular\"\n className={cn(sizeClasses[size], className)}\n aria-label=\"Loading profile image\"\n {...props}\n />\n );\n },\n\n /**\n * Card image skeleton (1200×630 OG aspect — matches entity-cards default\n * so loading state never causes a height shift when card data arrives)\n */\n CardImage: ({ className, ...props }: Omit<UnifiedSkeletonProps, 'variant'>) => (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn('aspect-[1200/630] w-full', className)}\n aria-label=\"Loading image\"\n {...props}\n />\n ),\n\n /**\n * Icon skeleton\n */\n Icon: ({\n size = 'md',\n className,\n ...props\n }: Omit<UnifiedSkeletonProps, 'variant'> & { size?: 'sm' | 'md' | 'lg' }) => {\n const sizeClasses = {\n sm: 'h-4 w-4',\n md: 'h-6 w-6',\n lg: 'h-8 w-8',\n };\n\n return (\n <UnifiedSkeleton\n variant=\"default\"\n className={cn(sizeClasses[size], className)}\n aria-label=\"Loading icon\"\n {...props}\n />\n );\n },\n};\n","import { cn } from '../../utils/cn';\nimport { TextSkeleton, MediaSkeleton, InteractiveSkeleton } from './unified-skeleton';\n\ninterface CardSkeletonProps {\n className?: string;\n /**\n * Card layout variant\n */\n variant?: 'vendor' | 'blog' | 'category' | 'alternative';\n /**\n * Show action buttons area\n */\n showActions?: boolean;\n /**\n * Show metadata footer\n */\n showMetadata?: boolean;\n /** Optional tailwind classes to override the card container background & border */\n containerClassName?: string;\n}\n\n/**\n * Unified card skeleton component for consistent card loading states\n *\n * Supports different card types used across the application:\n * - vendor: Vendor cards with logo, title, description, and actions\n * - blog: Blog post cards with image, title, summary, and metadata \n * - category: Category cards with icon, title, and description\n * - alternative: Alternative vendor cards in comparison lists\n */\nexport function CardSkeleton({\n className,\n containerClassName,\n variant = 'vendor',\n showActions = true,\n showMetadata = true,\n ...props\n}: CardSkeletonProps) {\n const cardContent = {\n vendor: <VendorCardContent showMetadata={showMetadata} />,\n blog: <BlogCardContent showActions={showActions} showMetadata={showMetadata} />,\n category: <CategoryCardContent />,\n alternative: <AlternativeCardContent showActions={showActions} />,\n };\n\n return (\n <div\n className={cn(\n containerClassName || 'border border-ods-border bg-ods-card',\n 'overflow-hidden rounded-lg',\n // Flex layouts for certain variants\n variant === 'blog' && 'flex h-full flex-col',\n variant === 'vendor' && 'flex h-full flex-col',\n className,\n )}\n role=\"status\"\n aria-label={`Loading ${variant} card`}\n {...props}\n >\n {cardContent[variant]}\n </div>\n );\n}\n\n/**\n * Vendor card skeleton content - matches exact VendorCard structure\n */\nfunction VendorCardContent({ showMetadata }: { showMetadata: boolean }) {\n return (\n <div className=\"flex flex-col gap-3 p-4\">\n {/* Header Section - Row layout matching actual VendorCard */}\n <div className=\"flex w-full items-start gap-3\">\n {/* Logo Frame - 60px width fixed, matching actual structure */}\n <div className=\"flex h-[60px] w-[60px] flex-shrink-0 items-center justify-center rounded-lg border border-ods-border bg-ods-bg p-2\">\n <MediaSkeleton.Avatar size=\"sm\" className=\"h-11 w-11\" />\n </div>\n\n {/* Text Container - Column layout, matching actual structure */}\n <div className=\"flex min-w-0 flex-1 flex-col justify-center space-y-1 py-2\">\n {/* Title - Single line with proper width */}\n <TextSkeleton.Subheading className=\"w-3/4\" />\n {/* Category - Single line, shorter */}\n <TextSkeleton.Caption className=\"w-1/2\" />\n </div>\n </div>\n\n {/* Description Section - Fixed 48px height matching actual VendorCard */}\n <div className=\"flex h-12 w-full items-center overflow-hidden\">\n <div className=\"w-full space-y-1\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-2/3\" />\n </div>\n </div>\n\n {/* Footer Section - Responsive layout matching actual structure */}\n {showMetadata && (\n <div className=\"flex w-full min-w-0 items-center justify-between gap-2\">\n {/* Stats Container - Flexible width, no overflow */}\n <div className=\"flex min-w-0 flex-shrink items-center gap-3 md:gap-4\">\n {/* OpenMSP Score skeleton */}\n <div className=\"flex flex-shrink-0 items-center gap-1\">\n <MediaSkeleton.Icon size=\"sm\" className=\"h-5 w-5\" />\n <TextSkeleton.Caption className=\"w-8\" />\n </div>\n\n {/* GitHub Stats skeleton */}\n <div className=\"flex flex-shrink-0 items-center gap-1\">\n <MediaSkeleton.Icon size=\"sm\" className=\"h-5 w-5\" />\n <TextSkeleton.Caption className=\"w-10\" />\n </div>\n </div>\n\n {/* Tag Section - Contained within card boundaries */}\n <div className=\"flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 rounded border border-ods-border bg-ods-bg px-2.5 py-1.5\">\n <div className=\"flex h-4 w-4 flex-shrink-0 items-center justify-center rounded-sm bg-ods-border\">\n <MediaSkeleton.Icon size=\"sm\" className=\"h-2.5 w-2.5\" />\n </div>\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </div>\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Blog card skeleton content - matches fixed height structure\n */\nfunction BlogCardContent({ showActions, showMetadata }: { showActions: boolean; showMetadata: boolean }) {\n return (\n <>\n {/* Image Section — OG 1200×630 aspect (matches blog-card.tsx loaded state) */}\n <div className=\"blog-card-image-container relative aspect-[1200/630] w-full overflow-hidden bg-ods-bg\">\n <MediaSkeleton.CardImage />\n </div>\n\n {/* Content - Fixed height structure to match BlogCard */}\n <div className=\"flex flex-grow flex-col p-4\">\n {/* Title Section - Fixed 2 lines with vertical centering */}\n <div className=\"mb-3 flex min-h-[50.4px] items-center md:min-h-[56px] lg:min-h-[61.6px]\">\n <div className=\"w-full space-y-1\">\n <TextSkeleton.Subheading className=\"w-full\" />\n <TextSkeleton.Subheading className=\"w-3/4\" />\n </div>\n </div>\n\n {/* Chips Section - Fixed single line height */}\n <div className=\"mb-3 flex h-[28px] items-center gap-2\">\n <InteractiveSkeleton.Chip className=\"w-16\" />\n <InteractiveSkeleton.Chip className=\"w-12\" />\n </div>\n\n {/* Description Section - Fixed 2 lines with vertical centering */}\n <div className=\"mb-3 flex min-h-[42px] items-center md:min-h-[45px] lg:min-h-[48px]\">\n <div className=\"w-full space-y-1\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-1/2\" />\n </div>\n </div>\n\n {/* Actions - only if requested */}\n {showActions && (\n <div className=\"pt-2\">\n <InteractiveSkeleton.Button className=\"h-8 w-24\" />\n </div>\n )}\n\n {/* Metadata footer - Matches BlogMeta horizontal layout */}\n {showMetadata && (\n <div className=\"mt-auto\">\n <div className=\"flex items-center justify-between gap-4 border-t border-ods-border pt-4\">\n {/* Author section - matches AuthorMeta */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <MediaSkeleton.Avatar size=\"sm\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n\n {/* Date and reading time section - matches BlogMeta right side */}\n <div className=\"flex shrink-0 items-center gap-3 text-ods-text-muted\">\n <TextSkeleton.Caption className=\"w-12\" />\n {/* Separator dot */}\n <div className=\"h-1 w-1 rounded-full bg-ods-skeleton\"></div>\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </div>\n </div>\n )}\n </div>\n </>\n );\n}\n\n/**\n * Category card skeleton content\n */\nfunction CategoryCardContent() {\n return (\n <div className=\"space-y-4 p-4 md:p-6\">\n {/* Icon grid */}\n <div className=\"flex gap-2 md:gap-3\">\n {Array.from({ length: 6 }).map((_, index) => (\n <MediaSkeleton.Icon key={index} size=\"lg\" className=\"flex-shrink-0\" />\n ))}\n </div>\n\n {/* Content */}\n <div className=\"space-y-2\">\n <div className=\"space-y-1\">\n <TextSkeleton.Subheading className=\"w-3/4\" />\n <TextSkeleton.Caption className=\"w-1/2\" />\n </div>\n\n <div className=\"flex items-start justify-between gap-4 md:items-end md:gap-6\">\n <div className=\"flex-1 space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-2/3\" />\n </div>\n\n <InteractiveSkeleton.Button className=\"h-10 w-10 flex-shrink-0 md:h-12 md:w-12\" />\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Alternative card skeleton content (for vendor alternatives/comparisons)\n */\nfunction AlternativeCardContent({ showActions }: { showActions: boolean }) {\n return (\n <div className=\"flex items-start gap-4 p-4\">\n <MediaSkeleton.Avatar size=\"md\" className=\"flex-shrink-0\" />\n\n <div className=\"flex-1 space-y-2\">\n <div className=\"flex items-center justify-between\">\n <TextSkeleton.Subheading className=\"w-1/3\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n\n <div className=\"space-y-1\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-5/6\" />\n </div>\n\n {showActions && (\n <div className=\"pt-2\">\n <InteractiveSkeleton.Button className=\"h-8 w-20\" />\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/**\n * Grid of card skeletons for loading lists\n */\nexport function CardSkeletonGrid({\n count = 6,\n variant = 'vendor',\n className,\n containerClassName,\n ...props\n}: {\n count?: number;\n variant?: CardSkeletonProps['variant'];\n className?: string;\n containerClassName?: string;\n} & Omit<CardSkeletonProps, 'variant'>) {\n return (\n <div\n className={cn(\n 'grid gap-4 md:gap-6',\n // Responsive grid based on card type\n variant === 'vendor' && 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3',\n variant === 'blog' && 'grid-cols-1 md:grid-cols-2',\n variant === 'category' && 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',\n variant === 'alternative' && 'grid-cols-1',\n className,\n )}\n role=\"status\"\n aria-label={`Loading ${count} ${variant} cards`}\n >\n {Array.from({ length: count }, (_, index) => (\n <CardSkeleton key={index} variant={variant} containerClassName={containerClassName} {...props} />\n ))}\n </div>\n );\n}\n","import type React from 'react';\nimport { cn } from '../../utils/cn';\nimport { CardSkeletonGrid } from './card-skeleton';\nimport { UnifiedSkeleton, TextSkeleton, MediaSkeleton, InteractiveSkeleton } from './unified-skeleton';\n\ninterface PageLayoutSkeletonProps {\n className?: string;\n}\n\n/**\n * Announcement bar skeleton that matches the AnnouncementBar component\n */\nexport function AnnouncementBarSkeleton() {\n return (\n <div className=\"relative w-full animate-pulse bg-ods-skeleton\">\n <div className=\"relative flex w-full flex-row items-center\">\n <div className=\"relative box-border flex w-full flex-row items-center justify-start gap-4 py-3 pl-4 pr-12 md:gap-6 md:py-4 md:pl-6 md:pr-16\">\n {/* Logo skeleton */}\n <div className=\"relative h-6 w-6 shrink-0 rounded bg-ods-border md:h-8 md:w-8\"></div>\n\n {/* Text content skeleton */}\n <div className=\"min-w-0 flex-1 space-y-1 md:space-y-2\">\n <div className=\"h-[14px] w-3/4 max-w-md rounded bg-ods-border md:h-[18px]\"></div>\n <div className=\"hidden h-[12px] w-full max-w-lg rounded bg-ods-border md:block md:h-[18px]\"></div>\n </div>\n\n {/* Close button skeleton */}\n <div className=\"absolute right-2 top-2 h-6 w-6 rounded bg-ods-border\"></div>\n </div>\n </div>\n </div>\n );\n}\n\n/**\n * Header skeleton that matches the ClientOnlyHeader placeholder but with proper animations\n */\nexport function HeaderSkeleton() {\n return (\n <header className=\"sticky top-0 z-40 flex w-full animate-pulse items-center justify-between border-b border-ods-border bg-ods-card bg-ods-card/95 px-4 py-3 backdrop-blur-sm md:px-[80px] md:py-[12px]\">\n {/* Left: Logo skeleton */}\n <div className=\"flex min-w-0 flex-1 items-center justify-start\">\n <div className=\"h-[26px] w-[110px] rounded bg-ods-skeleton md:h-8 md:w-[137px]\"></div>\n </div>\n\n {/* Center: Navigation skeleton - hidden on mobile, visible on desktop */}\n <nav className=\"hidden min-w-0 flex-1 basis-1/3 items-center justify-center gap-2 md:flex\">\n <div className=\"flex items-center gap-2\">\n <InteractiveSkeleton.Button className=\"h-10 w-24\" />\n <InteractiveSkeleton.Button className=\"h-10 w-24\" />\n <InteractiveSkeleton.Button className=\"h-10 w-24\" />\n </div>\n </nav>\n\n {/* Right: Actions skeleton */}\n <div className=\"flex min-w-0 flex-1 items-center justify-end gap-4\">\n {/* Mobile: Show hamburger skeleton */}\n <div className=\"md:hidden\">\n <InteractiveSkeleton.Button className=\"h-10 w-10\" />\n </div>\n\n {/* Desktop: Show action buttons skeletons */}\n <div className=\"hidden items-center gap-4 md:flex\">\n <InteractiveSkeleton.Button className=\"h-10 w-10\" />\n <InteractiveSkeleton.Button className=\"h-10 w-32\" />\n <InteractiveSkeleton.Button className=\"h-10 w-20\" />\n </div>\n </div>\n </header>\n );\n}\n\n/**\n * Hero section skeleton for static content areas\n */\nexport function HeroSkeleton() {\n return (\n <section\n className=\"flex w-full animate-pulse flex-col items-center justify-center px-4 py-12 text-center md:px-8 md:py-20\"\n style={{\n background: 'radial-gradient(circle at 50% 0%, #242323 0%, #1A1A1A 100%)',\n }}\n >\n {/* Title skeleton */}\n <TextSkeleton.Heading className=\"mb-4 h-12 w-full max-w-4xl md:mb-6 md:h-20 lg:h-24\" />\n\n {/* Subtitle skeleton */}\n <div className=\"mb-8 w-full max-w-4xl space-y-3 px-2 md:mb-10\">\n <TextSkeleton.Body className=\"h-5 md:h-7\" />\n <TextSkeleton.Body className=\"mx-auto h-5 w-3/4 md:h-7\" />\n </div>\n\n {/* CTA Button skeleton */}\n <InteractiveSkeleton.Button className=\"h-12 w-full md:w-64\" />\n </section>\n );\n}\n\n/**\n * Search container skeleton with filters\n */\nexport function SearchContainerSkeleton({\n className,\n showFilters = true,\n}: PageLayoutSkeletonProps & { showFilters?: boolean }) {\n return (\n <div className={cn('space-y-4', className || '')}>\n {/* Search input and button */}\n <div className=\"flex gap-2 md:gap-4\">\n <InteractiveSkeleton.Input className=\"flex-1\" />\n <InteractiveSkeleton.Button className=\"w-32 md:w-40\" />\n </div>\n\n {/* Filter chips */}\n {showFilters && (\n <div className=\"flex flex-wrap gap-2\">\n <InteractiveSkeleton.Chip />\n <InteractiveSkeleton.Chip className=\"w-24\" />\n <InteractiveSkeleton.Chip className=\"w-16\" />\n <InteractiveSkeleton.Chip className=\"w-20\" />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Category sidebar skeleton for filtering pages\n */\nexport function CategorySidebarSkeleton({ className }: PageLayoutSkeletonProps) {\n // Matches the actual MultiLevelNavigation sidebar with folder/file icons, README badges, and chevrons\n const items = [\n { type: 'file', width: 'w-20', hasBadge: false },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'folder', width: 'w-20', hasBadge: true },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'folder', width: 'w-16', hasBadge: true },\n { type: 'folder', width: 'w-20', hasBadge: true },\n { type: 'folder', width: 'w-28', hasBadge: true },\n { type: 'folder', width: 'w-16', hasBadge: true },\n { type: 'folder', width: 'w-24', hasBadge: true },\n { type: 'file', width: 'w-28' },\n { type: 'file', width: 'w-36' },\n ];\n\n return (\n <div className={cn('w-full lg:w-[320px]', className)}>\n {/* DATA ROOM label */}\n <UnifiedSkeleton className=\"mb-4 h-[14px] w-24 rounded\" />\n\n {/* Navigation items — each has card background matching actual MultiLevelNavigation */}\n <div className=\"space-y-1.5\">\n {items.map((item, index) => (\n <div\n key={index}\n className={cn(\n 'flex min-h-[50px] items-center justify-between rounded-lg border border-ods-border px-4 py-4',\n index === 0 ? 'bg-ods-accent/20' : 'bg-ods-card',\n )}\n >\n <div className=\"flex items-center gap-2.5\">\n <UnifiedSkeleton className=\"h-4 w-4 shrink-0 rounded\" />\n <UnifiedSkeleton className={`h-[14px] ${item.width} rounded`} />\n </div>\n <div className=\"flex h-[18px] items-center gap-2\">\n {item.hasBadge && <UnifiedSkeleton className=\"h-[18px] w-14 rounded\" />}\n {item.type === 'folder' && <UnifiedSkeleton className=\"h-4 w-4 rounded\" />}\n </div>\n </div>\n ))}\n </div>\n </div>\n );\n}\n\n/**\n * Breadcrumb navigation skeleton\n */\nexport function BreadcrumbSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn('mb-6 flex items-center space-x-1', className)}>\n <TextSkeleton.Caption className=\"w-16\" />\n <UnifiedSkeleton variant=\"default\" className=\"h-4 w-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-24\" />\n <UnifiedSkeleton variant=\"default\" className=\"h-4 w-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-32\" />\n <UnifiedSkeleton variant=\"default\" className=\"h-4 w-4 rounded-full\" />\n <TextSkeleton.Caption className=\"w-24\" />\n </div>\n );\n}\n\n/**\n * Results header skeleton with count and sorting\n */\nexport function ResultsHeaderSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn('mb-6 flex flex-col items-start justify-between gap-4 md:flex-row md:items-center', className)}>\n <div className=\"space-y-1\">\n <TextSkeleton.Body className=\"w-48\" />\n {/* <TextSkeleton.Caption className=\"w-32\" /> */}\n </div>\n </div>\n );\n}\n\n/**\n * Two-column layout skeleton (sidebar + main content)\n */\nexport function TwoColumnLayoutSkeleton({\n className,\n sidebarContent,\n mainContent,\n sidebarPosition = 'left',\n}: PageLayoutSkeletonProps & {\n sidebarContent?: React.ReactNode;\n mainContent?: React.ReactNode;\n sidebarPosition?: 'left' | 'right';\n}) {\n const sidebar = sidebarContent || <CategorySidebarSkeleton />;\n const main = mainContent || <CardSkeletonGrid count={6} />;\n\n return (\n <div\n className={cn(\n 'grid grid-cols-1 gap-6 lg:grid-cols-[320px_1fr] lg:gap-8',\n sidebarPosition === 'right' && 'lg:grid-cols-[1fr_320px]',\n className,\n )}\n >\n {sidebarPosition === 'left' ? (\n <>\n <aside className=\"order-2 lg:order-1\">{sidebar}</aside>\n <main className=\"order-1 lg:order-2\">{main}</main>\n </>\n ) : (\n <>\n <main className=\"order-1\">{main}</main>\n <aside className=\"order-2\">{sidebar}</aside>\n </>\n )}\n </div>\n );\n}\n\n/**\n * Article/blog post layout skeleton\n */\nexport function ArticleLayoutSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <article className={cn('mx-auto max-w-4xl', className)}>\n {/* Article header */}\n <header className=\"mb-8 space-y-4 md:mb-12 md:space-y-6\">\n {/* Category/tags */}\n <div className=\"flex gap-2\">\n <InteractiveSkeleton.Chip />\n <InteractiveSkeleton.Chip className=\"w-16\" />\n </div>\n\n {/* Title */}\n <div className=\"space-y-3\">\n <TextSkeleton.Heading className=\"w-full\" />\n <TextSkeleton.Heading className=\"w-3/4\" />\n </div>\n\n {/* Metadata */}\n <div className=\"flex items-center gap-4 border-t border-ods-divider pt-4\">\n <div className=\"flex items-center gap-2\">\n <MediaSkeleton.Avatar size=\"sm\" />\n <TextSkeleton.Caption className=\"w-24\" />\n </div>\n <TextSkeleton.Caption className=\"w-20\" />\n <TextSkeleton.Caption className=\"w-16\" />\n </div>\n </header>\n\n {/* Featured image */}\n <div className=\"mb-8 md:mb-12\">\n <MediaSkeleton.CardImage className=\"h-64 w-full md:h-96\" />\n </div>\n\n {/* Article content */}\n <div className=\"prose prose-invert max-w-none space-y-6\">\n {/* Paragraphs */}\n {Array.from({ length: 12 }).map((_, index) => (\n <div key={index} className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-5/6\" />\n <TextSkeleton.Body className=\"w-3/4\" />\n </div>\n ))}\n\n {/* Subheading in content */}\n <div className=\"py-4\">\n <TextSkeleton.Subheading className=\"mb-4 w-2/3\" />\n <div className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-4/5\" />\n </div>\n </div>\n </div>\n </article>\n );\n}\n\n/**\n * Vendor Detail Layout Skeleton - Complete vendor detail page structure\n * Matches the refactored vendor detail page with proper platform colors and responsive layout\n */\nexport function VendorDetailLayoutSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <main className={cn('min-h-screen bg-ods-bg', className)}>\n <div className=\"mx-auto max-w-[1920px] px-6 py-6 md:px-20 md:py-10\">\n {/* Breadcrumb */}\n <BreadcrumbSkeleton className=\"mb-6\" />\n\n {/* Main Layout Container */}\n <div className=\"flex flex-col lg:flex-row lg:gap-10\">\n {/* Left Content Area */}\n <div className=\"min-w-0 flex-1\">\n {/* Vendor Hero Section */}\n <div className=\"mb-10\">\n {/* Header - Logo and Title Side by Side */}\n <div className=\"mb-6 flex gap-6\">\n <div className=\"h-20 w-20 flex-shrink-0 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex flex-col gap-2\">\n {/* Large title skeleton */}\n <div className=\"h-12 w-80 max-w-full animate-pulse rounded bg-ods-skeleton md:h-16 lg:h-20\"></div>\n {/* Category text */}\n <div className=\"h-5 w-32 animate-pulse rounded bg-ods-skeleton md:h-6\"></div>\n </div>\n\n {/* Pricing tags */}\n <div className=\"flex items-center gap-2\">\n <div className=\"h-8 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-8 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n\n {/* Vendor Image Display Skeleton */}\n <div className=\"mb-2 h-[300px] w-full animate-pulse rounded-lg border border-ods-border bg-ods-card md:h-[400px] lg:h-[500px]\"></div>\n <div className=\"text-center\">\n <div className=\"mx-auto h-4 w-24 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n\n {/* Mobile Sidebar - Show on mobile only, positioned after title */}\n <div className=\"mb-10 lg:hidden\">\n <div className=\"space-y-4\">\n {/* Deploy Button */}\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n\n {/* Voting Buttons */}\n <div className=\"space-y-2\">\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n </div>\n\n {/* GitHub Score Section */}\n <div className=\"overflow-hidden rounded-lg border border-ods-border\">\n {/* Header */}\n <div className=\"flex items-center gap-3 bg-ods-card p-4\">\n <div className=\"h-8 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex flex-col gap-1\">\n <div className=\"h-4 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n\n {/* Stats */}\n <div className=\"space-y-3 p-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3\">\n <div className=\"h-6 w-6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex items-end gap-1\">\n <div className=\"h-4 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Action Buttons */}\n <div className=\"space-y-3\">\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n </div>\n </div>\n </div>\n\n {/* Alternatives Container */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-48 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n\n {/* Open Source Alternatives */}\n <div className=\"flex flex-col gap-4\">\n <div className=\"h-5 w-48 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex flex-wrap gap-2\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div\n key={i}\n className=\"h-10 w-24 animate-pulse rounded-lg border border-ods-border bg-ods-card\"\n ></div>\n ))}\n </div>\n </div>\n\n {/* Commercial Alternatives */}\n <div className=\"flex flex-col gap-4\">\n <div className=\"h-5 w-52 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex flex-wrap gap-2\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div\n key={i}\n className=\"h-10 w-28 animate-pulse rounded-lg border border-ods-border bg-ods-card\"\n ></div>\n ))}\n </div>\n </div>\n </div>\n\n {/* About Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-64 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n\n <div className=\"rounded-lg border border-ods-border bg-ods-card p-6 md:p-8\">\n <div className=\"space-y-4\">\n <div className=\"h-6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-5/6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-4/5 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n\n {/* Key Features Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-72 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n <div className=\"grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"flex gap-4 rounded-lg border border-ods-border bg-ods-card p-4\">\n <div className=\"h-12 w-12 flex-shrink-0 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-3/4 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Pros and Cons Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-80 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n <div className=\"grid grid-cols-1 gap-6 lg:grid-cols-2\">\n {/* Pros Column */}\n <div className=\"flex flex-col gap-6\">\n <div className=\"h-5 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"flex gap-3 rounded-lg border border-ods-border bg-ods-card p-4\">\n <div className=\"h-12 w-12 flex-shrink-0 animate-pulse rounded border border-ods-border bg-ods-bg\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-2/3 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n {/* Cons Column */}\n <div className=\"flex flex-col gap-6\">\n <div className=\"h-5 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"flex gap-3 rounded-lg border border-ods-border bg-ods-card p-4\">\n <div className=\"h-12 w-12 flex-shrink-0 animate-pulse rounded border border-ods-border bg-ods-bg\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-5 w-2/3 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Alternatives Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-72 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n <div className=\"grid grid-cols-1 gap-6 md:grid-cols-2 lg:grid-cols-3\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"space-y-4 rounded-lg border border-ods-border bg-ods-card p-6\">\n {/* Header */}\n <div className=\"flex items-start gap-4\">\n <div className=\"h-16 w-16 flex-shrink-0 animate-pulse rounded-lg border border-ods-border bg-ods-bg\"></div>\n <div className=\"flex-1 space-y-2\">\n <div className=\"h-6 w-3/4 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-1/2 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n <div className=\"h-6 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Description */}\n <div className=\"space-y-2\">\n <div className=\"h-4 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-5/6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-4/5 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Footer */}\n <div className=\"flex items-center justify-between border-t border-ods-border pt-2\">\n <div className=\"h-4 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n {/* Show All Button */}\n <div className=\"h-12 animate-pulse rounded-md bg-ods-skeleton\"></div>\n </div>\n\n {/* Comments Section */}\n <div className=\"mb-20 flex flex-col gap-6\">\n <div className=\"h-8 w-64 animate-pulse rounded bg-ods-skeleton md:h-10\"></div>\n\n {/* Comment Form Skeleton */}\n <div className=\"space-y-3\">\n <div className=\"flex gap-3\">\n <div className=\"flex-1 overflow-hidden rounded-lg border border-ods-border bg-ods-bg\">\n {/* Title Section */}\n <div className=\"border-b border-ods-border p-3\">\n <div className=\"mb-2 h-3 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-full animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Description Section */}\n <div className=\"p-3\">\n <div className=\"mb-2 h-3 w-16 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"space-y-2\">\n <div className=\"h-4 w-full animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-3/4 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-1/2 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n\n {/* Send Button */}\n <div className=\"flex items-stretch\">\n <div className=\"h-[120px] w-20 animate-pulse rounded-lg bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n\n {/* Sample Comment Cards */}\n <div className=\"space-y-4\">\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i} className=\"rounded-lg border border-ods-border bg-ods-card p-4\">\n {/* Comment Header */}\n <div className=\"mb-3 flex flex-col gap-3 min-[420px]:flex-row min-[420px]:items-center min-[420px]:justify-between min-[420px]:gap-2\">\n <div className=\"flex items-center gap-2\">\n {/* User Info */}\n <div className=\"flex items-center gap-2 rounded-lg border border-ods-border bg-ods-card px-3 py-2\">\n <div className=\"h-8 w-8 animate-pulse rounded-lg bg-ods-skeleton\"></div>\n <div className=\"h-4 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Timestamp */}\n <div className=\"h-4 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n\n {/* Comment Content */}\n <div className=\"space-y-2\">\n <div className=\"h-6 w-2/3 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"space-y-1\">\n <div className=\"h-4 w-full animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-4/5 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-4 w-3/5 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n ))}\n </div>\n </div>\n </div>\n\n {/* Right Sidebar - Desktop Only */}\n <div className=\"hidden w-[290px] flex-shrink-0 lg:block\">\n <div className=\"space-y-4\">\n {/* Deploy Button */}\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n\n {/* Voting Buttons */}\n <div className=\"space-y-2\">\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n </div>\n\n {/* GitHub Score Section */}\n <div className=\"overflow-hidden rounded-lg border border-ods-border\">\n {/* Header */}\n <div className=\"flex items-center gap-3 bg-ods-card p-4\">\n <div className=\"h-8 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex flex-col gap-1\">\n <div className=\"h-4 w-20 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-6 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n\n {/* Stats */}\n <div className=\"space-y-3 p-4\">\n {Array.from({ length: 4 }).map((_, i) => (\n <div key={i} className=\"flex items-center gap-3\">\n <div className=\"h-6 w-6 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"flex items-end gap-1\">\n <div className=\"h-4 w-12 animate-pulse rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-8 animate-pulse rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n </div>\n\n {/* Action Buttons */}\n <div className=\"space-y-3\">\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n <div className=\"h-12 animate-pulse rounded-lg border border-ods-border bg-ods-card\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </main>\n );\n}\n\n/**\n * Stats/features section skeleton for homepage\n */\nexport function StatsSectionSkeleton({ className, columns = 3 }: PageLayoutSkeletonProps & { columns?: number }) {\n return (\n <div\n className={cn(\n 'mb-12 grid gap-6 md:mb-16',\n columns === 3 && 'grid-cols-1 md:grid-cols-3',\n columns === 4 && 'grid-cols-1 md:grid-cols-2 lg:grid-cols-4',\n className,\n )}\n >\n {Array.from({ length: columns }).map((_, i) => (\n <div key={i} className=\"rounded-lg border border-ods-border bg-ods-card p-6\">\n <div className=\"space-y-4\">\n <MediaSkeleton.Icon size=\"lg\" />\n <TextSkeleton.Subheading className=\"w-3/4\" />\n <div className=\"space-y-2\">\n <TextSkeleton.Body className=\"w-full\" />\n <TextSkeleton.Body className=\"w-3/4\" />\n </div>\n </div>\n </div>\n ))}\n </div>\n );\n}\n\n/**\n * Blog Card Grid Skeleton - Always displays exactly 4 blog card skeletons\n * Used for consistent blog page layout with predictable height\n */\nexport function BlogCardGridSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn('space-y-6', className)}>\n <CardSkeletonGrid count={4} variant=\"blog\" className=\"grid-cols-1 md:grid-cols-2\" />\n </div>\n );\n}\n\n/**\n * Vendor Grid Skeleton - Always displays exactly 12 vendor card skeletons\n * Used for consistent vendor page layout with predictable height\n */\nexport function VendorGridSkeleton({ className }: PageLayoutSkeletonProps) {\n return (\n <div className={cn('space-y-6', className)}>\n <CardSkeletonGrid count={12} variant=\"vendor\" className=\"grid-cols-1 md:grid-cols-2 lg:grid-cols-3\" />\n </div>\n );\n}\n\n/**\n * Slack Community Section Skeleton\n * Matches SlackCommunity component structure with title, channel list, and chat interface\n */\nexport function SlackCommunitySkeleton() {\n return (\n <section className=\"w-full bg-ods-bg px-4 py-12 md:px-20 md:py-20 lg:px-20\" aria-label=\"Slack Community Loading\">\n {/* Frame 651 Container */}\n <div className=\"flex w-full flex-col gap-4 md:gap-6\">\n {/* Title Skeleton */}\n <div className=\"w-full\">\n <div className=\"h-8 max-w-md animate-pulse rounded-lg bg-ods-skeleton md:h-12 lg:h-14\"></div>\n </div>\n\n {/* Content Area - Channel List + Chat Interface */}\n <div className=\"flex w-full min-w-0 flex-col gap-4 md:gap-6 lg:flex-row lg:items-start lg:justify-end\">\n {/* Channel List Skeleton */}\n <div className=\"flex w-full flex-shrink-0 animate-pulse flex-col overflow-hidden rounded border border-ods-border bg-ods-bg lg:w-[290px] lg:max-w-[290px]\">\n <div className=\"space-y-4 p-4\">\n {/* Header */}\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex items-center justify-between\">\n <div className=\"h-4 w-24 rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-16 rounded bg-ods-skeleton\"></div>\n </div>\n <div className=\"h-3 w-32 rounded bg-ods-skeleton\"></div>\n </div>\n\n {/* Channels */}\n <div className=\"flex flex-col gap-1\">\n <div className=\"mb-2 h-3 w-16 rounded bg-ods-skeleton\"></div>\n {Array.from({ length: 5 }).map((_, i) => (\n <div key={i} className=\"flex min-h-[48px] items-center gap-2 px-2 py-3\">\n <div className=\"h-4 w-4 rounded bg-ods-skeleton\"></div>\n <div className=\"flex-1\">\n <div className=\"mb-1 h-3 w-24 rounded bg-ods-skeleton\"></div>\n <div className=\"h-2 w-16 rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Stats */}\n <div className=\"mt-2 border-t border-ods-border pt-3\">\n <div className=\"space-y-2\">\n <div className=\"flex justify-between\">\n <div className=\"h-3 w-16 rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-8 rounded bg-ods-skeleton\"></div>\n </div>\n <div className=\"flex justify-between\">\n <div className=\"h-3 w-20 rounded bg-ods-skeleton\"></div>\n <div className=\"h-3 w-8 rounded bg-ods-skeleton\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n {/* Chat Interface Skeleton */}\n <div className=\"flex h-[450px] min-h-[450px] min-w-0 flex-1 animate-pulse flex-col rounded-lg border border-ods-border bg-ods-card md:h-[500px] md:min-h-[500px] lg:h-[600px] lg:min-h-[600px]\">\n {/* Header */}\n <div className=\"flex h-[56px] flex-shrink-0 items-center justify-between border-b border-ods-border bg-ods-bg p-4 md:h-[60px] md:p-6\">\n <div className=\"flex items-center gap-2\">\n <div className=\"h-3 w-3 rounded bg-ods-skeleton md:h-4 md:w-4\"></div>\n <div className=\"h-3 w-24 rounded bg-ods-skeleton md:h-4 md:w-32\"></div>\n </div>\n <div className=\"h-3 w-12 rounded bg-ods-skeleton md:w-16\"></div>\n </div>\n\n {/* Messages */}\n <div className=\"min-h-[280px] flex-1 space-y-3 overflow-hidden bg-ods-bg p-4 md:min-h-[320px] md:space-y-4 md:p-6 lg:min-h-[420px]\">\n {Array.from({ length: 6 }).map((_, i) => (\n <div key={i} className=\"flex gap-2 md:gap-3\">\n <div className=\"h-10 w-10 flex-shrink-0 rounded-lg bg-ods-skeleton md:h-12 md:w-12\"></div>\n <div className=\"flex-1\">\n <div className=\"mb-1 flex items-center gap-2\">\n <div className=\"h-3 w-16 rounded bg-ods-skeleton md:w-20\"></div>\n <div className=\"h-3 w-12 rounded bg-ods-skeleton md:w-16\"></div>\n </div>\n <div className=\"h-3 w-full rounded bg-ods-skeleton md:h-4\"></div>\n </div>\n </div>\n ))}\n </div>\n\n {/* Input Area */}\n <div className=\"h-[72px] flex-shrink-0 border-t border-ods-border bg-ods-card p-4 md:h-[80px] md:p-6\">\n <div className=\"flex items-end justify-center gap-3 md:justify-end\">\n <div className=\"h-10 w-28 rounded-lg bg-ods-skeleton md:h-12 md:w-32\"></div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </section>\n );\n}\n","'use client';\n\nimport type { ReactNode } from 'react';\nimport { cn } from '../utils/cn';\n\ninterface PersistentFilterControlsProps {\n /**\n * Whether the filters are currently in a loading state\n */\n isLoading: boolean;\n /**\n * The filter control components (search, sidebar, mobile dropdown)\n */\n children: ReactNode;\n /**\n * Additional CSS classes\n */\n className?: string;\n /**\n * Disabled opacity (0-1)\n */\n disabledOpacity?: number;\n /**\n * Transition duration in milliseconds\n */\n transitionDuration?: number;\n /**\n * Whether to prevent pointer events during loading\n */\n preventInteraction?: boolean;\n}\n\n/**\n * PersistentFilterControls\n *\n * A wrapper component that keeps filter controls visible during loading states\n * but provides visual feedback that they are temporarily disabled.\n *\n * Features:\n * - Reduces opacity and disables pointer events during loading\n * - Maintains layout and accessibility during loading states\n * - Provides smooth transitions between enabled/disabled states\n * - Preserves keyboard navigation and screen reader functionality\n * - Applies consistent disabled styling across all filter types\n *\n * Usage:\n * ```tsx\n * <PersistentFilterControls isLoading={isLoadingVendors}>\n * <SearchContainer />\n * <CategoryFilterSidebar />\n * <MobileDropdown />\n * </PersistentFilterControls>\n * ```\n */\nexport function PersistentFilterControls({\n isLoading,\n children,\n className,\n disabledOpacity = 0.6,\n transitionDuration = 300,\n preventInteraction = true,\n}: PersistentFilterControlsProps) {\n return (\n <div\n className={cn(\n 'relative transition-all ease-in-out',\n isLoading && preventInteraction && 'pointer-events-none',\n className,\n )}\n style={{\n opacity: isLoading ? disabledOpacity : 1,\n transitionDuration: `${transitionDuration}ms`,\n }}\n role=\"region\"\n aria-label=\"Filter controls\"\n aria-busy={isLoading}\n data-loading={isLoading}\n >\n {/* Loading indicator overlay for screen readers */}\n {isLoading && (\n <div className=\"sr-only\" role=\"status\" aria-live=\"polite\">\n Filters temporarily disabled while loading content\n </div>\n )}\n\n {/* Filter controls with disabled state styling */}\n <div\n className={cn('transition-all ease-in-out', isLoading && 'cursor-not-allowed')}\n style={{\n transitionDuration: `${transitionDuration}ms`,\n }}\n >\n {children}\n </div>\n\n {/* Visual loading indicator */}\n {isLoading && (\n <div className=\"absolute right-2 top-2 z-10\" role=\"status\" aria-label=\"Loading filters\">\n <div className=\"h-4 w-4 animate-spin rounded-full border-2 border-ods-border border-t-ods-accent\" />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Enhanced SearchContainer wrapper that integrates with persistent loading\n */\ninterface PersistentSearchContainerProps {\n isLoading: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function PersistentSearchContainer({ isLoading, children, className }: PersistentSearchContainerProps) {\n return (\n <PersistentFilterControls\n isLoading={isLoading}\n preventInteraction={false} // Keep search interactive during loading\n className={className}\n >\n {children}\n </PersistentFilterControls>\n );\n}\n\n/**\n * Enhanced sidebar wrapper for desktop filter controls\n */\ninterface PersistentSidebarProps {\n isLoading: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function PersistentSidebar({ isLoading, children, className }: PersistentSidebarProps) {\n return (\n <PersistentFilterControls\n isLoading={isLoading}\n disabledOpacity={0.5}\n className={cn('lg:sticky lg:top-20', className)}\n >\n {children}\n </PersistentFilterControls>\n );\n}\n\n/**\n * Enhanced mobile dropdown wrapper\n */\ninterface PersistentMobileDropdownProps {\n isLoading: boolean;\n children: ReactNode;\n className?: string;\n}\n\nexport function PersistentMobileDropdown({ isLoading, children, className }: PersistentMobileDropdownProps) {\n return (\n <PersistentFilterControls isLoading={isLoading} disabledOpacity={0.7} className={cn('lg:hidden', className)}>\n {children}\n </PersistentFilterControls>\n );\n}\n","'use client';\n\nimport React, { createContext, useCallback, useContext, useRef, type ReactNode } from 'react';\nimport type { DocNode } from '../../types/doc-source';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface DocNavigator {\n /** Base route this navigator owns (e.g., '/knowledge-base', '/data-room'). */\n baseRoute: string;\n /** Look up a node in the tree by its storage path (e.g., 'openframe-cli/README.md'). */\n findNodeByPath: (path: string) => DocNode | null;\n /** Navigate to a node using the same flow as a sidebar menu click. */\n selectNode: (node: DocNode) => void;\n}\n\ninterface DocNavigationContextValue {\n /**\n * Register a navigator for a `baseRoute`. Returns a cleanup function that\n * removes the navigator from the map IF the slot still owns it (StrictMode-safe).\n * Duplicate registration for the same baseRoute logs a console.warn and replaces.\n */\n register: (nav: DocNavigator) => () => void;\n /**\n * Navigate to a path inside whichever registered navigator owns its baseRoute prefix.\n * Returns true if handled, false otherwise (caller falls back to opening in a new tab).\n */\n navigate: (path: string) => boolean;\n /** Whether ANY navigator is currently mounted. */\n isAvailable: () => boolean;\n}\n\n// =============================================================================\n// Context\n// =============================================================================\n\nconst DocNavigationContext = createContext<DocNavigationContextValue | null>(null);\n\n/**\n * Bridges `useDocumentTree` instances (deep in the page tree) to `GlobalAskAI`\n * (high up near the root) without global events or URL parsing.\n *\n * Multi-navigator design: a `Map<baseRoute, DocNavigator>` lets multiple\n * viewers coexist (nested viewers, dual-pane scenarios). Today only one\n * viewer mounts at a time, so behavior is unchanged.\n */\nexport function DocNavigationProvider({ children }: { children: ReactNode }) {\n const navMap = useRef<Map<string, DocNavigator>>(new Map());\n\n const register = useCallback((nav: DocNavigator) => {\n const existing = navMap.current.get(nav.baseRoute);\n if (existing && existing !== nav) {\n console.warn(`[DocNavigationContext] duplicate registration for ${nav.baseRoute}; replacing`);\n }\n navMap.current.set(nav.baseRoute, nav);\n return () => {\n // StrictMode-safe identity check: only delete if THIS nav still owns the slot.\n if (navMap.current.get(nav.baseRoute) === nav) {\n navMap.current.delete(nav.baseRoute);\n }\n };\n }, []);\n\n const findOwningNavigator = useCallback((path: string): DocNavigator | null => {\n // First try: a navigator whose baseRoute is a prefix of `path`. Walks the\n // longest match first so nested routes win over a parent.\n const baseRoutes = Array.from(navMap.current.keys()).sort((a, b) => b.length - a.length);\n for (const baseRoute of baseRoutes) {\n if (path === baseRoute || path.startsWith(`${baseRoute}/`)) {\n return navMap.current.get(baseRoute) ?? null;\n }\n }\n // Fallback: a single navigator handles any path (existing behavior — chat\n // chips emit storage paths, not absolute URLs, so prefix matching misses).\n if (navMap.current.size === 1) {\n return navMap.current.values().next().value ?? null;\n }\n return null;\n }, []);\n\n const navigate = useCallback(\n (path: string): boolean => {\n const nav = findOwningNavigator(path);\n if (!nav) return false;\n const node = nav.findNodeByPath(path);\n if (!node) return false;\n nav.selectNode(node);\n return true;\n },\n [findOwningNavigator],\n );\n\n const isAvailable = useCallback(() => navMap.current.size > 0, []);\n\n const value = React.useMemo(() => ({ register, navigate, isAvailable }), [register, navigate, isAvailable]);\n\n return <DocNavigationContext.Provider value={value}>{children}</DocNavigationContext.Provider>;\n}\n\n// =============================================================================\n// Hooks\n// =============================================================================\n\nexport function useDocNavigation(): DocNavigationContextValue {\n const ctx = useContext(DocNavigationContext);\n if (!ctx) {\n // Safe no-op fallback when used outside the provider.\n return {\n register: () => () => {},\n navigate: () => false,\n isAvailable: () => false,\n };\n }\n return ctx;\n}\n","import { useCallback } from 'react';\nimport { useChatRuntime } from '../../contexts/chat-runtime-context';\nimport type { ResolveLinkResult } from '../../types/doc-source';\nimport { contentFetch } from '../../utils/embed-content-fetch';\n\n/**\n * `useDocsResolveLink(sourceId, override?)` — POST `/api/docs/resolve-link`\n * (or the override / `ChatRuntime.endpoints.docsResolveLinkUrl`) for a\n * relative href inside a doc body, returning a `ResolveLinkResult`\n * envelope.\n *\n * The endpoint chain (`override ?? runtime.endpoints.docsResolveLinkUrl\n * ?? '/api/docs/resolve-link'`) mirrors `searchEndpoint` resolution in\n * `<DocViewer>` so embedders configure both the same way: per-instance\n * prop OR ambient `ChatRuntimeProvider`.\n *\n * The full fetch + JSON-parse pipeline is wrapped in try/catch so a\n * network throw (DNS / CORS / offline) or a non-JSON response surfaces\n * as `{ success: false, error }` — the markdown renderer's broken-link\n * badge handles that branch instead of swallowing an unhandled rejection\n * past the click handler.\n */\n/**\n * Narrow the endpoint's answer to a `ResolveLinkResult`.\n *\n * `Response.json()` hands back `any`, and the route answers either the bare\n * result or a `{ data: … }` envelope — so the shape is checked instead of\n * trusted: a non-object body, or a field of the wrong primitive type, degrades\n * to `{ success: false }`, which is the \"leave the link alone\" branch the\n * markdown renderer already handles.\n *\n * Mirrors `toResolveLinkResult` in `ui/markdown/rich/rich-markdown-renderer`,\n * which narrows the same wire shape for the renderer's own fetch (that copy\n * cannot be imported here — it would pull the whole rich renderer chunk into\n * this hook).\n */\nfunction toResolveLinkResult(body: unknown): ResolveLinkResult {\n if (typeof body !== 'object' || body === null) {\n return { success: false };\n }\n const envelope: Record<string, unknown> = { ...body };\n const inner = envelope.data;\n const fields: Record<string, unknown> = typeof inner === 'object' && inner !== null ? { ...inner } : envelope;\n const readString = (key: string): string | undefined => (typeof fields[key] === 'string' ? fields[key] : undefined);\n\n return {\n success: fields.success === true,\n resolvedPath: readString('resolvedPath'),\n type: readString('type'),\n action: readString('action'),\n error: readString('error'),\n message: readString('message'),\n };\n}\n\nexport function useDocsResolveLink(sourceId: string, resolveLinkEndpoint?: string | null) {\n const chatRuntime = useChatRuntime();\n const resolvedResolveLinkEndpoint =\n resolveLinkEndpoint ?? chatRuntime?.endpoints.docsResolveLinkUrl ?? '/api/docs/resolve-link';\n\n return useCallback(\n async (href: string, currentPath: string): Promise<ResolveLinkResult> => {\n try {\n const response = await contentFetch(resolvedResolveLinkEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ link: href, currentPath, source: sourceId }),\n });\n if (!response.ok) {\n return { success: false, error: `Resolve failed: ${response.status}` };\n }\n const json: unknown = await response.json();\n return toResolveLinkResult(json);\n } catch (error) {\n return {\n success: false,\n error: error instanceof Error ? error.message : 'Resolve failed',\n };\n }\n },\n [resolvedResolveLinkEndpoint, sourceId],\n );\n}\n","'use client';\n\nimport { useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport type { DocNode, DocContent, DocumentType } from '../../types/doc-source';\nimport { apiErrorMessage } from '../../utils/common';\nimport {\n stripFolderIndexFromPath,\n findDocNodeByPath,\n getDocAncestorNodeIds,\n DEFAULT_FOLDER_INDEX_FILE,\n} from '../../utils/doc-tree-nav';\nimport { contentFetch } from '../../utils/embed-content-fetch';\nimport { navigateSamePageHash, HUB_HEADER_OFFSET_PX } from '../../utils/same-page-hash-nav';\nimport { scrollElementIntoView } from '../../utils/scroll-into-view';\nimport { useDocNavigation } from './doc-navigation-context';\n\nfunction scrollToContent() {\n const article = document.querySelector('article');\n if (article) {\n scrollElementIntoView(article, { headerOffset: HUB_HEADER_OFFSET_PX });\n } else {\n // Same anchoring-proof tween for the no-article fallback — native smooth\n // scrollTo is cancelled by scroll anchoring while the new doc renders in.\n scrollElementIntoView(document.documentElement);\n }\n}\n\n/**\n * First displayable document inside a folder (depth-first): a direct non-Mermaid\n * file, else the first doc found in a subfolder. Used so a folder WITHOUT a\n * README shows its first child's content (mirroring how a README folder shows\n * its README) instead of a blank panel or a redundant in-page listing — the\n * sidebar tree is the directory browser. Returns null for a folder with no docs.\n */\nfunction findFirstDocPath(folder: DocNode): string | null {\n const children = folder.children ?? [];\n for (const child of children) {\n if (child.type === 'file' && !child.path.toLowerCase().endsWith('.mmd')) {\n return child.path;\n }\n }\n for (const child of children) {\n if (child.type === 'folder') {\n const nested = findFirstDocPath(child);\n if (nested) return nested;\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Wire decoding for the two doc-source endpoints\n//\n// `Response.json()` is typed `any`, so every field of both envelopes used to be\n// read off an `any` and the whole tree walk below inherited it. Both endpoints\n// are untrusted input — an embedder's reverse proxy answers them — so the shape\n// is validated once here instead of being trusted at ~30 read sites.\n//\n// Mirrors the narrowing `toResolveLinkResult` does for the resolve-link\n// envelope in `use-docs-resolve-link.ts`; that decoder covers a different\n// endpoint and is not reusable here.\n// ---------------------------------------------------------------------------\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** `Array.isArray` narrows `unknown` to `any[]`; this keeps the elements `unknown`. */\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n return Array.isArray(value);\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nfunction isDocumentType(value: unknown): value is DocumentType {\n return value === 'markdown' || value === 'pdf' || value === 'google_sheet' || value === 'figma' || value === 'file';\n}\n\n/**\n * One tree node, or null when the row cannot be rendered.\n *\n * `id`, `name`, `path` and `type` are load-bearing: the sidebar calls\n * `node.name.endsWith('.md')` and every nav helper keys off `id`/`path`, so a\n * row missing one of them throws while rendering the tree rather than merely\n * looking wrong. Dropping the row keeps the rest of the tree browsable.\n *\n * `slug` is declared on `DocNode` but read nowhere in the lib, and the shared\n * `TreeNodeBase` producer contract does not carry it — so it is derived from\n * the path (the derivation the doc-source DALs use) when the payload omits it.\n */\nfunction toDocNode(value: unknown): DocNode | null {\n if (!isRecord(value)) return null;\n\n const id = readString(value, 'id');\n const name = readString(value, 'name');\n const path = readString(value, 'path');\n const type = value.type;\n if (id === undefined || name === undefined || path === undefined) return null;\n if (type !== 'file' && type !== 'folder') return null;\n\n const sortOrder = value.sortOrder;\n const hasReadme = value.hasReadme;\n\n return {\n id,\n name,\n slug: readString(value, 'slug') ?? path.split('/').pop() ?? '',\n path,\n type,\n hasReadme: typeof hasReadme === 'boolean' ? hasReadme : undefined,\n sortOrder: typeof sortOrder === 'number' ? sortOrder : undefined,\n documentType: isDocumentType(value.documentType) ? value.documentType : undefined,\n children: toDocNodes(value.children) ?? undefined,\n };\n}\n\n/** The tree, or null when the payload is not an array at all (a failed load). */\nfunction toDocNodes(value: unknown): DocNode[] | null {\n if (!isUnknownArray(value)) return null;\n const nodes: DocNode[] = [];\n for (const entry of value) {\n const node = toDocNode(entry);\n if (node) nodes.push(node);\n }\n return nodes;\n}\n\nfunction toDocSections(value: unknown): DocContent['sections'] {\n if (!isUnknownArray(value)) return [];\n const sections: DocContent['sections'] = [];\n for (const entry of value) {\n if (!isRecord(entry)) continue;\n const id = readString(entry, 'id');\n const title = readString(entry, 'title');\n const level = entry.level;\n if (id === undefined || title === undefined || typeof level !== 'number') continue;\n sections.push({ id, title, level });\n }\n return sections;\n}\n\nfunction toStringArray(value: unknown): string[] | undefined {\n if (!isUnknownArray(value)) return undefined;\n return value.filter((entry): entry is string => typeof entry === 'string');\n}\n\n/**\n * One document body, or null when the payload is not an object.\n *\n * `content` is declared required but is empty by nature for the rich document\n * types — a `pdf` / `google_sheet` / `figma` doc renders purely from `fileUrl`\n * / `externalUrl` (see `docs-hub-page.tsx`) — so a missing `content` is\n * normalized to `''` rather than treated as a failed load.\n *\n * `sections` and `path` are also declared required, and both leak when absent:\n * `path` feeds `DocRenderHandlers.currentPath`, which is what every relative\n * link inside the body is resolved against. `requestedPath` is the path this\n * fetch asked for, which is the correct value whenever the server omits it.\n */\nfunction toDocContent(value: unknown, requestedPath: string): DocContent | null {\n if (!isRecord(value)) return null;\n\n const fileSize = value.fileSize;\n\n return {\n content: readString(value, 'content') ?? '',\n sections: toDocSections(value.sections),\n path: readString(value, 'path') ?? requestedPath,\n documentType: isDocumentType(value.documentType) ? value.documentType : undefined,\n brokenLinks: toStringArray(value.brokenLinks),\n fileUrl: readString(value, 'fileUrl'),\n externalUrl: readString(value, 'externalUrl'),\n mimeType: readString(value, 'mimeType'),\n fileName: readString(value, 'fileName'),\n fileSize: typeof fileSize === 'number' ? fileSize : undefined,\n publishedAt: readString(value, 'publishedAt'),\n updatedAt: readString(value, 'updatedAt'),\n };\n}\n\nexport interface UseDocumentTreeConfig {\n /** API endpoint for fetching the document tree structure */\n structureEndpoint: string;\n /** API endpoint for fetching document content */\n contentEndpoint: string;\n /** Base route path for URL navigation (e.g., '/knowledge-base', '/data-room') */\n baseRoute: string;\n /** Folder-index filename (defaults to 'README.md'). */\n folderIndexFile?: string;\n}\n\n/**\n * Generic hook for document tree navigation and content fetching.\n * Drives DocViewer across all doc-source consumers.\n *\n * Client-only: structure + content fetches run in parallel on first mount.\n * No SSR pre-population — the previous SSR path required a Supabase admin\n * client (service role key) and silently fell back to client fetches on\n * envs where the key wasn't set; the parallel client fetches keep behavior\n * uniform across local + prod (latency ~= max(structure, content), not sum).\n */\nexport function useDocumentTree(config: UseDocumentTreeConfig, initialPath?: string) {\n const { structureEndpoint, contentEndpoint, baseRoute } = config;\n const folderIndexFile = config.folderIndexFile ?? DEFAULT_FOLDER_INDEX_FILE;\n\n const cleanInitialPath = stripFolderIndexFromPath(initialPath?.replace(/\\/$/, '') || '', folderIndexFile);\n\n const [structure, setStructure] = useState<DocNode[]>([]);\n const [selectedPath, setSelectedPath] = useState<string>(cleanInitialPath);\n const [content, setContent] = useState<DocContent | null>(null);\n const [isLoadingStructure, setIsLoadingStructure] = useState(true);\n const [isLoadingContent, setIsLoadingContent] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [expandedNodes, setExpandedNodes] = useState<Set<string>>(new Set());\n const lastFetchedPath = useRef<string | null>(null);\n\n const normalizedBaseRoute = baseRoute.replace(/\\/$/, '');\n\n // Refreshed after every commit rather than in the render body: the reader is\n // a popstate listener, which cannot fire before a commit, and a discarded\n // render attempt must not leave a path behind that was never navigated to.\n const selectedPathRef = useRef(selectedPath);\n useEffect(() => {\n selectedPathRef.current = selectedPath;\n });\n\n const docNavigation = useDocNavigation();\n\n useEffect(() => {\n const handlePopState = () => {\n const pathname = window.location.pathname;\n let pathFromUrl = '';\n\n if (pathname === normalizedBaseRoute || pathname === `${normalizedBaseRoute}/`) {\n pathFromUrl = '';\n } else if (pathname.startsWith(`${normalizedBaseRoute}/`)) {\n pathFromUrl = pathname.substring(`${normalizedBaseRoute}/`.length);\n }\n\n pathFromUrl = stripFolderIndexFromPath(pathFromUrl, folderIndexFile);\n\n if (pathFromUrl !== selectedPathRef.current) {\n setSelectedPath(pathFromUrl);\n if (pathFromUrl) {\n const parentPath = pathFromUrl.includes('/')\n ? pathFromUrl.substring(0, pathFromUrl.lastIndexOf('/'))\n : pathFromUrl;\n setExpandedNodes(new Set(getDocAncestorNodeIds(parentPath)));\n }\n setTimeout(() => {\n scrollToContent();\n }, 150);\n }\n };\n\n window.addEventListener('popstate', handlePopState);\n return () => window.removeEventListener('popstate', handlePopState);\n }, [normalizedBaseRoute, folderIndexFile]);\n\n // External-URL → state sync. The popstate listener above catches browser\n // back/forward, but client-side routers (react-router, Next App Router…)\n // change the URL via `history.pushState` which does NOT fire popstate.\n // The host re-renders the viewer with a new `initialPath` prop instead, so\n // we mirror the popstate logic here against the (memoized) `cleanInitialPath`.\n // Without this, a chat-card click that soft-navigates via react-router\n // updates the URL but the viewer stays on the previously-selected doc.\n //\n // Adjusted while rendering — React's documented pattern for a prop-driven\n // sync — rather than from an effect: the viewer renders `selectedPath` and\n // `expandedNodes` in THIS render, so an effect painted a full frame of the\n // previous document (and the previous expanded branch) before swapping. The\n // guard compares against the `selectedPath` STATE rather than the popstate\n // listener's ref, both because a ref must not be read during render and\n // because at this point the two hold the same value.\n const [syncedInitialPath, setSyncedInitialPath] = useState(cleanInitialPath);\n const [externalNavCount, setExternalNavCount] = useState(0);\n if (syncedInitialPath !== cleanInitialPath) {\n setSyncedInitialPath(cleanInitialPath);\n if (cleanInitialPath !== selectedPath) {\n setSelectedPath(cleanInitialPath);\n if (cleanInitialPath) {\n const parentPath = cleanInitialPath.includes('/')\n ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf('/'))\n : cleanInitialPath;\n setExpandedNodes(new Set(getDocAncestorNodeIds(parentPath)));\n }\n // Counter rather than a flag: it makes the scroll below fire once per\n // ACTUAL external navigation, which is exactly when the old effect\n // reached its `setTimeout` — a bare `cleanInitialPath` dep would also\n // fire for the host mirroring a sidebar click back into the URL.\n setExternalNavCount(n => n + 1);\n }\n }\n\n // Match popstate's scroll-to-content delay; the targeted content fetch\n // dispatched by the selectedPath effect lands before this fires.\n useEffect(() => {\n if (externalNavCount === 0) return undefined;\n const timer = setTimeout(scrollToContent, 150);\n return () => clearTimeout(timer);\n }, [externalNavCount]);\n\n const fetchStructure = async () => {\n try {\n setIsLoadingStructure(true);\n setError(null);\n\n const response = await contentFetch(structureEndpoint);\n\n if (!response.ok) {\n throw new Error('Failed to load documentation structure');\n }\n\n const payload: unknown = await response.json();\n // A non-array `data` used to be handed straight to `setStructure`, and\n // every later `findDocNodeByPath` then tried to iterate it — a hard\n // \"nodes is not iterable\" throw instead of the error banner below.\n const nodes = isRecord(payload) && payload.success ? toDocNodes(payload.data) : null;\n\n if (nodes) {\n setStructure(nodes);\n\n if (cleanInitialPath) {\n const pathForExpansion = cleanInitialPath.includes('.')\n ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf('/'))\n : cleanInitialPath;\n if (pathForExpansion) {\n setExpandedNodes(new Set(getDocAncestorNodeIds(pathForExpansion)));\n }\n } else if (nodes.length > 0) {\n const hasRootReadme = nodes.some(node => node.type === 'file' && node.path === folderIndexFile);\n\n if (!hasRootReadme) {\n const firstNode = nodes[0];\n if (firstNode.type === 'folder') {\n setExpandedNodes(new Set([firstNode.id]));\n if (firstNode.hasReadme) {\n setSelectedPath(firstNode.path);\n window.history.replaceState({}, '', `${normalizedBaseRoute}/${firstNode.path}`);\n }\n }\n }\n }\n } else {\n setError(apiErrorMessage(payload, 'Failed to load documentation structure'));\n }\n } catch (err) {\n console.error('Error fetching documentation structure:', err);\n setError(err instanceof Error ? err.message : 'Failed to load documentation');\n } finally {\n setIsLoadingStructure(false);\n }\n };\n\n const fetchContent = async (path: string) => {\n try {\n setIsLoadingContent(true);\n // Don't clear `error` here — if a previous fetch set an error and this\n // is a stale/speculative call that gets superseded, the guard below\n // returns early without writing to state. Clearing error here would\n // briefly flicker the user-visible error message.\n\n const response = await contentFetch(`${contentEndpoint}?path=${encodeURIComponent(path)}`);\n\n // Request-id guard: between awaits, `lastFetchedPath.current` may have\n // been bumped by a newer fetch (the structure-arrives auto-select issues\n // a more-targeted fetch while the speculative one is in flight). Bail\n // BEFORE writing to state — otherwise the late 404 of the speculative\n // fetch overwrites the targeted fetch's good content with null.\n if (path !== lastFetchedPath.current) return;\n\n if (!response.ok) {\n if (response.status === 404) {\n const errorPayload: unknown = await response.json().catch(() => null);\n if (path !== lastFetchedPath.current) return;\n // Landing-page silent fallback: when the user lands on the source's\n // root URL and there's no root `README.md` (knowledge-base case),\n // the speculative fetch 404s — surface an empty state instead of\n // an error banner. The structure-arrives auto-select will fire\n // a targeted fetch for the first-folder README on the next render.\n if (path === folderIndexFile && selectedPath === '') {\n // Superseded by the auto-select fetch the structure effect fires.\n // Null the request id so the `finally` does NOT drop the spinner —\n // otherwise there's a 1-frame gap (isLoadingContent false, content\n // null) where the empty state flashes before the real fetch starts.\n lastFetchedPath.current = null;\n setError(null);\n setContent(null);\n return;\n }\n // No-README FOLDER → stay silent: the content effect resolves it to\n // the folder's first child, so the folder-path 404 from the direct-\n // load speculative fetch is expected, not an error to flash.\n const probe = findDocNodeByPath(stripFolderIndexFromPath(path, folderIndexFile), structure);\n const probeIsNoReadmeFolder = !!probe && probe.type === 'folder' && !probe.hasReadme;\n // Before the structure has loaded to classify the path (`structure`\n // is [] in the speculative call's closure), only silence FOLDER-LIKE\n // paths (no `.md`) — a genuinely missing `*.md` leaf must still error.\n const preStructureFolderLike = structure.length === 0 && !path.endsWith('.md');\n if (probeIsNoReadmeFolder || preStructureFolderLike) {\n // Superseded by the targeted fetch (first-child / reclassified path)\n // the structure effect fires. Null the request id so the `finally`\n // keeps the spinner up instead of flashing an empty state for a\n // frame before that fetch starts.\n lastFetchedPath.current = null;\n setError(null);\n setContent(null);\n return;\n }\n setError(apiErrorMessage(errorPayload, 'Documentation file not found'));\n setContent(null);\n return;\n }\n throw new Error('Failed to load documentation content');\n }\n\n const payload: unknown = await response.json();\n if (path !== lastFetchedPath.current) return;\n setError(null);\n\n const doc = isRecord(payload) && payload.success ? toDocContent(payload.data, path) : null;\n\n if (doc) {\n // `correctPath` is only honored when it is actually a string. The old\n // `!== undefined` check also admitted `null`, which `setSelectedPath`\n // then stored as the selection: the URL became `${baseRoute}/null` and\n // the content effect's `selectedPath === null` guard bailed on every\n // subsequent run, freezing the viewer on that document.\n const correctPath = isRecord(payload) && payload.redirect ? readString(payload, 'correctPath') : undefined;\n if (correctPath !== undefined) {\n setSelectedPath(correctPath);\n window.history.replaceState({}, '', `${normalizedBaseRoute}/${correctPath}`);\n }\n setContent(doc);\n } else {\n setError(apiErrorMessage(payload, 'Failed to load content'));\n setContent(null);\n }\n } catch (err) {\n if (path !== lastFetchedPath.current) return;\n console.error('Error fetching documentation content:', err);\n setError(err instanceof Error ? err.message : 'Failed to load content');\n setContent(null);\n } finally {\n // Only clear loading state if THIS fetch is still the active one — a\n // superseded speculative shouldn't flip the spinner off while the\n // targeted fetch is still in flight.\n if (path === lastFetchedPath.current) setIsLoadingContent(false);\n }\n };\n // `fetchStructure` / `fetchContent` are plain async functions rebuilt on\n // every render, so neither effect below can take them as dependencies: the\n // init effect would re-fire its speculative fetch and the path effect would\n // refetch the same document on every parent render. Read through a ref that\n // is refreshed after every commit, so both always call the current closures.\n const latestRef = useRef({ fetchContent, fetchStructure, cleanInitialPath, folderIndexFile });\n useEffect(() => {\n latestRef.current = { fetchContent, fetchStructure, cleanInitialPath, folderIndexFile };\n });\n\n // Mount-only. The `isInitialized` state this used to latch on was read\n // NOWHERE else: it was a run-once guard that cost a setState in an effect\n // body (and a second, no-op run of this effect) to say what empty deps say\n // directly.\n useEffect(() => {\n // Kick off the speculative content fetch IN PARALLEL with the structure\n // fetch — the two endpoints are independent and most landing pages have\n // a root README (the default folder-index). If the structure ends up\n // pointing at a different path (e.g. knowledge-base falls back to the\n // first-folder README because there's no root README), the content\n // useEffect issues the correct fetch after structure arrives — the\n // speculative result silently no-ops (the content state update gets\n // overwritten by the targeted fetch).\n const latest = latestRef.current;\n const speculativeContentPath = latest.cleanInitialPath || latest.folderIndexFile;\n lastFetchedPath.current = speculativeContentPath;\n // Both settle their own failures into `error` state via try/catch/finally,\n // so neither can reject; they are deliberately NOT awaited so the two\n // independent endpoints stay in parallel.\n void latest.fetchContent(speculativeContentPath);\n void latest.fetchStructure();\n }, []);\n\n useEffect(() => {\n if (selectedPath === null || selectedPath === undefined) return;\n if (!structure || structure.length === 0) return;\n\n let pathToFetch: string | null = null;\n\n if (selectedPath === '') {\n pathToFetch = folderIndexFile;\n } else {\n const node = findDocNodeByPath(selectedPath, structure);\n\n if (node && node.type === 'folder' && !node.hasReadme) {\n // No-README folder has no body of its own — show its FIRST child doc\n // (mirrors a README folder showing its README). selectedPath stays the\n // folder, so the sidebar keeps it highlighted/expanded; the sidebar is\n // the directory browser, so we render NO separate in-page listing.\n const firstDocPath = findFirstDocPath(node);\n if (!firstDocPath) {\n // Genuinely empty folder — nothing to fetch. The empty state itself\n // is DERIVED (see `isEmptyFolder` below); clearing content/error/\n // loading from here would be a second render pass to publish\n // something `structure` + `selectedPath` already say.\n lastFetchedPath.current = null;\n return;\n }\n pathToFetch = firstDocPath;\n } else if (node && node.type === 'folder' && node.hasReadme) {\n // `getContent(folder)` already resolves a README folder to its README,\n // so the initial speculative fetch (which uses the bare folder path)\n // ALREADY loaded this content. Re-fetching the `${folder}/README.md`\n // variant is a redundant 2nd request whose in-flight `isLoadingContent`\n // flashes the skeleton — content → skeleton → content — on first load.\n // Skip it when the folder path was already the (speculatively) fetched\n // path; the result (or its in-flight request) covers the README.\n if (lastFetchedPath.current === selectedPath) {\n return;\n }\n pathToFetch = `${selectedPath}/${folderIndexFile}`;\n } else {\n pathToFetch = selectedPath;\n }\n }\n\n if (pathToFetch === lastFetchedPath.current) {\n return;\n }\n\n if (pathToFetch) {\n lastFetchedPath.current = pathToFetch;\n // Never rejects — settles its own failures into `error` state.\n void latestRef.current.fetchContent(pathToFetch);\n }\n }, [selectedPath, structure, folderIndexFile]);\n\n const toggleNode = useCallback((nodeId: string) => {\n setExpandedNodes(prev => {\n const next = new Set(prev);\n if (next.has(nodeId)) {\n next.delete(nodeId);\n } else {\n next.add(nodeId);\n }\n return next;\n });\n }, []);\n\n // Structural minimum the body uses — `id`, `path`, `type`, `hasReadme`.\n // Widening from `DocNode` lets the navigation components (which carry the\n // narrower `NavigationNode` row shape) pass their own node back without the\n // cross-type `as` cast. Both DocNode and NavigationNode satisfy this.\n const selectNode = useCallback(\n (node: Pick<DocNode, 'id' | 'path' | 'type' | 'hasReadme'>) => {\n // Expansion only: clicking a folder toggles its own subtree; clicking a file\n // reveals its ancestor chain.\n if (node.type === 'folder') {\n setExpandedNodes(prev => {\n if (prev.has(node.id)) {\n const ancestorIds = getDocAncestorNodeIds(node.path);\n ancestorIds.pop(); // collapse self, keep ancestors open\n return new Set(ancestorIds);\n }\n return new Set(getDocAncestorNodeIds(node.path));\n });\n } else {\n const lastSlash = node.path.lastIndexOf('/');\n if (lastSlash > 0) {\n setExpandedNodes(new Set(getDocAncestorNodeIds(node.path.substring(0, lastSlash))));\n }\n }\n\n // Every node is a navigable destination — a file shows its body, a README\n // folder its README, a no-README folder its first child doc (the content\n // effect resolves which). So selection + URL + scroll are identical for all\n // node types; no per-type special-casing.\n lastFetchedPath.current = null;\n setSelectedPath(node.path);\n window.history.pushState({}, '', `${normalizedBaseRoute}/${node.path}`);\n setTimeout(scrollToContent, 150);\n },\n [normalizedBaseRoute],\n );\n\n const navigateToDoc = useCallback(\n (path: string, options?: { expandFolder?: boolean; fromInternalLink?: boolean }) => {\n const hashIndex = path.indexOf('#');\n const anchor = hashIndex !== -1 ? path.substring(hashIndex) : '';\n const cleanPath = path.replace(/\\/$/, '').split('#')[0];\n\n // Same-doc-different-anchor shortcut. Content is already mounted, so we\n // don't need the 300ms \"wait-for-fetch\" bandaid — the canonical helper\n // owns pushState + synthetic `hashchange` (so any in-doc TOC / accordion\n // bound to the URL hash re-renders) + the anchoring-proof tween in one\n // sync call. `headerOffset: HUB_HEADER_OFFSET_PX` matches the cross-doc path below so\n // anchors land BELOW the docs sticky header on every same-doc internal\n // link click. Cross-doc nav (different cleanPath) falls through to the\n // existing fetch-then-scroll path below.\n //\n // We pass the BARE-hash form to the helper rather than reconstructing\n // a full `${normalizedBaseRoute}/${cleanPath}${anchor}` path: the\n // helper's pathname check compares against `window.location.pathname`,\n // which carries the FOLDER-INDEX-STRIPPED form (`/docs/foo` for\n // `foo/README.md`, `/docs` for the root index). Handing it `cleanPath`\n // — the raw resolved path — produces e.g. `/docs/foo/README.md` and\n // the compare fails → helper returns false → silent dead-click. The\n // bare-hash form sidesteps that entirely: the helper reconstructs\n // `pathname + search + hash` from `window.location`, so the compare\n // is trivially equal. Covers bare `#anchor` links (resolve to\n // `cleanPath=''`) AND folder-index links (`foo/README.md` resolving\n // to the current `/docs/foo`).\n // Bare-hash internal links (`[Click](#section)`) come in as\n // `path === '#section'`, so `cleanPath` becomes `''` and the naive\n // strip-then-compare misses the same-doc shortcut on every NON-root\n // doc (selectedPath is e.g. `'foo/bar'`, not `''`). For that case the\n // current doc IS the same-doc target by definition — short-circuit\n // pathForSelection to the current selection so the shortcut fires.\n const pathForSelection =\n anchor && options?.fromInternalLink && cleanPath === ''\n ? selectedPathRef.current\n : stripFolderIndexFromPath(cleanPath, folderIndexFile);\n if (anchor && options?.fromInternalLink && pathForSelection === selectedPathRef.current) {\n navigateSamePageHash(anchor, { headerOffset: HUB_HEADER_OFFSET_PX });\n return;\n }\n\n const scrollAfterNav = () => {\n if (anchor) {\n setTimeout(() => {\n const el = document.getElementById(anchor.substring(1));\n if (el) {\n scrollElementIntoView(el, { headerOffset: HUB_HEADER_OFFSET_PX });\n } else {\n scrollToContent();\n }\n }, 300);\n } else {\n setTimeout(() => {\n scrollToContent();\n }, 150);\n }\n };\n\n if (options?.expandFolder) {\n lastFetchedPath.current = null;\n setSelectedPath(cleanPath);\n window.history.pushState({}, '', `${normalizedBaseRoute}/${cleanPath}${anchor}`);\n\n const pathParts = cleanPath.split('/');\n const nodeIdsToExpand: string[] = [];\n let currentPath = '';\n\n for (const part of pathParts) {\n currentPath = currentPath ? `${currentPath}-${part}` : part;\n nodeIdsToExpand.push(currentPath.toLowerCase());\n }\n\n setExpandedNodes(new Set(nodeIdsToExpand));\n\n if (options?.fromInternalLink) {\n scrollAfterNav();\n }\n\n setError(null);\n return;\n }\n\n // `pathForSelection` was already computed above (inside the\n // same-doc-anchor shortcut check); reuse it here for cross-doc nav.\n const urlPath = pathForSelection;\n\n lastFetchedPath.current = null;\n setSelectedPath(pathForSelection);\n window.history.pushState({}, '', `${normalizedBaseRoute}/${urlPath}${anchor}`);\n\n if (options?.fromInternalLink) {\n scrollAfterNav();\n }\n\n const pathParts = cleanPath.split('/');\n const parentIds: string[] = [];\n let currentPath = '';\n const partsToProcess = cleanPath.includes('.') ? pathParts.slice(0, -1) : pathParts;\n\n for (const part of partsToProcess) {\n currentPath = currentPath ? `${currentPath}-${part}` : part;\n parentIds.push(currentPath.toLowerCase());\n }\n\n if (parentIds.length > 0) {\n setExpandedNodes(new Set(parentIds));\n }\n },\n [normalizedBaseRoute, folderIndexFile],\n );\n\n useEffect(() => {\n return docNavigation.register({\n baseRoute: normalizedBaseRoute,\n findNodeByPath: path => {\n const clean = stripFolderIndexFromPath(path.replace(/\\/$/, '').split('#')[0], folderIndexFile);\n return findDocNodeByPath(clean, structure) ?? null;\n },\n selectNode,\n });\n }, [docNavigation, normalizedBaseRoute, structure, selectNode, folderIndexFile]);\n\n // A folder with no README and no documents anywhere under it has no body at\n // all, and that is a pure fact about `structure` + `selectedPath` — both\n // available while rendering. Derived here rather than written into state from\n // the content effect, which reached the same conclusion one commit later and\n // meanwhile left the PREVIOUS document's body on screen.\n const isEmptyFolder = useMemo(() => {\n if (!selectedPath || structure.length === 0) return false;\n const node = findDocNodeByPath(selectedPath, structure);\n return !!node && node.type === 'folder' && !node.hasReadme && !findFirstDocPath(node);\n }, [selectedPath, structure]);\n\n return {\n structure,\n selectedPath,\n setSelectedPath,\n content: isEmptyFolder ? null : content,\n isLoadingStructure,\n isLoadingContent: isEmptyFolder ? false : isLoadingContent,\n error: isEmptyFolder ? null : error,\n expandedNodes,\n toggleNode,\n selectNode,\n navigateToDoc,\n refetch: fetchStructure,\n setExpandedNodes,\n };\n}\n\nexport type { DocNode, DocContent };\n","'use client';\n\nimport { useState, useRef, useCallback, useEffect, useMemo } from 'react';\nimport { scrollElementIntoView } from '../../utils/scroll-into-view';\n\n// Single source for the sticky-chrome height. Used for BOTH the scroll target\n// offset (where a clicked section lands) AND the active-section detection\n// threshold (where the scroll listener flips highlight). They must match —\n// previously 100 vs 150 caused a 50px window where the indicator jumped to\n// the next section even though that section's top was still below the\n// clicked one's resting offset.\nconst SCROLL_OFFSET = 100;\n\n// Separator for the section-id key. A newline cannot occur inside an HTML id,\n// so the join is unambiguous and the key can be split back apart.\nconst ID_SEPARATOR = '\\n';\n\ninterface ScrollSpySection {\n id: string;\n title?: string;\n level?: number;\n}\n\ninterface UseScrollSpyReturn {\n activeSection: string;\n handleSectionClick: (sectionId: string) => void;\n}\n\n/**\n * Shared scroll spy hook for tracking active section based on scroll position.\n * Used by DocViewer for sticky section navigation.\n */\nexport function useScrollSpy(sections: ScrollSpySection[] | undefined): UseScrollSpyReturn {\n const [activeSection, setActiveSection] = useState('');\n const isScrollingFromClick = useRef(false);\n\n // The scroll listener only ever needs the section IDS, and callers rebuild\n // the `sections` array on every render — so the value-stable joined key IS\n // the input, and the effect unpacks it again. That replaces a ref written\n // during render whose only job was to keep the array's churning identity out\n // of the dependency array.\n const sectionIdsKey = useMemo(() => sections?.map(s => s.id).join(ID_SEPARATOR) ?? '', [sections]);\n\n const handleSectionClick = useCallback((sectionId: string) => {\n const targetElement = document.getElementById(sectionId);\n if (!targetElement) return;\n\n isScrollingFromClick.current = true;\n setActiveSection(sectionId);\n\n scrollElementIntoView(targetElement, { headerOffset: SCROLL_OFFSET });\n\n setTimeout(() => {\n isScrollingFromClick.current = false;\n }, 800);\n }, []);\n\n useEffect(() => {\n const sectionIds = sectionIdsKey === '' ? [] : sectionIdsKey.split(ID_SEPARATOR);\n if (sectionIds.length === 0) return undefined;\n\n const handleScroll = () => {\n if (isScrollingFromClick.current) return;\n\n const scrollPosition = window.scrollY + SCROLL_OFFSET;\n let currentSection = sectionIds[0] ?? '';\n\n for (let i = sectionIds.length - 1; i >= 0; i--) {\n const element = document.getElementById(sectionIds[i]);\n if (element && scrollPosition >= element.offsetTop) {\n currentSection = sectionIds[i];\n break;\n }\n }\n\n setActiveSection(prev => (prev !== currentSection ? currentSection : prev));\n };\n\n let scrollTimer: ReturnType<typeof setTimeout>;\n const throttledScroll = () => {\n clearTimeout(scrollTimer);\n scrollTimer = setTimeout(handleScroll, 100);\n };\n\n window.addEventListener('scroll', throttledScroll);\n handleScroll();\n\n return () => {\n window.removeEventListener('scroll', throttledScroll);\n clearTimeout(scrollTimer);\n };\n }, [sectionIdsKey]);\n\n return { activeSection, handleSectionClick };\n}\n","/**\n * Text-style skeleton — matches a rendered markdown article layout.\n * Used by `<DocsHubPage>` as the default for `markdown` (and unknown\n * document types). Embedders can override via `renderSkeleton`.\n */\nexport function MarkdownSkeleton() {\n return (\n <div className=\"mt-6 space-y-7\">\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-3/4 animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-5/6 animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-2/3 animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"h-[88px] animate-pulse rounded-lg border border-ods-border bg-ods-card\" />\n <div className=\"h-7 w-1/3 animate-pulse rounded bg-ods-border\" />\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-[72%] animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-[58%] animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"h-7 w-2/5 animate-pulse rounded bg-ods-border\" />\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-[90%] animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"space-y-[14px]\">\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-full animate-pulse rounded bg-ods-border\" />\n <div className=\"h-[16px] w-[70%] animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"h-[88px] animate-pulse rounded-lg border border-ods-border bg-ods-card\" />\n </div>\n );\n}\n\n/**\n * Embed-style skeleton — matches the iframe loading state for `pdf`,\n * `google_sheet`, `figma`, and `file` document types. Used by\n * `<DocsHubPage>` as the default for non-markdown documentTypes.\n *\n * The skeleton is documentType-aware so its layout matches the actual\n * viewer that will replace it:\n * - `pdf` → header with title + TWO buttons (Preview, Download)\n * - `google_sheet`/`figma` → header with title + ONE button/toggle\n * - `file` → centered FileDownloadCard-style box\n * - undefined / others → generic (1-button header)\n *\n * IMPORTANT: bars use `bg-ods-border` (NOT `bg-ods-skeleton`). The\n * `--ods-skeleton` token resolves to TRANSPARENT in this build, leaving\n * the skeleton box visually empty — the embed skeleton was the loudest\n * surface affected (a full-height iframe area showing nothing). Same fix\n * the chat-message-row skeleton already documents in its inline comment.\n */\nexport interface EmbedSkeletonProps {\n /** When provided, the header layout matches the eventual viewer's\n * button count + arrangement, so the layout doesn't shift on load. */\n documentType?: 'pdf' | 'google_sheet' | 'figma' | 'file' | string;\n}\n\nexport function EmbedSkeleton({ documentType }: EmbedSkeletonProps = {}) {\n // Centered card shape for the `file` documentType — matches\n // `<FileDownloadCard>`'s `flex flex-col items-center justify-center py-16`\n // + bordered card with icon, name, type/size row, Download button.\n if (documentType === 'file') {\n return (\n <div className=\"flex flex-col items-center justify-center py-16\">\n <div className=\"w-full max-w-md space-y-4 rounded-xl border border-ods-border bg-ods-card p-8 text-center\">\n <div className=\"mx-auto h-16 w-16 animate-pulse rounded bg-ods-border\" />\n <div className=\"space-y-2\">\n <div className=\"mx-auto h-5 w-2/3 animate-pulse rounded bg-ods-border\" />\n <div className=\"mx-auto h-4 w-1/2 animate-pulse rounded bg-ods-border\" />\n </div>\n <div className=\"h-10 w-full animate-pulse rounded bg-ods-border\" />\n </div>\n </div>\n );\n }\n\n // PDF viewer has TWO buttons (Preview + Download); Sheets / Figma\n // render ONE (Open / view-toggle). Default to one for unknown types.\n const buttonCount = documentType === 'pdf' ? 2 : 1;\n\n return (\n <div className=\"space-y-4\">\n {/* Header — matches the actual viewer's\n * `flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between`\n * (mobile-stacked, desktop-row). */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n {/* Left: icon + title */}\n <div className=\"flex min-w-0 flex-1 items-center gap-2\">\n <div className=\"h-5 w-5 shrink-0 animate-pulse rounded bg-ods-border\" />\n <div className=\"h-6 w-2/3 animate-pulse rounded bg-ods-border\" />\n </div>\n {/* Right: 1 or 2 buttons. Mobile = full-width; desktop = auto. */}\n <div className=\"flex w-full items-center gap-2 sm:w-auto\">\n {Array.from({ length: buttonCount }).map((_, i) => (\n <div key={i} className=\"h-10 w-full flex-1 animate-pulse rounded bg-ods-border sm:w-32 sm:flex-initial\" />\n ))}\n </div>\n </div>\n {/* Body — clean iframe-sized rectangle, no fake inner placeholder\n * cruft. Matches the viewer's default `calc(100vh - 250px)` height. */}\n <div\n className=\"w-full animate-pulse rounded-lg border border-ods-border bg-ods-card\"\n style={{ height: 'calc(100vh - 250px)' }}\n />\n </div>\n );\n}\n","import type React from 'react';\nimport type { DocContent, DocRenderHandlers, DocumentType } from '../../types/doc-source';\nimport { FigmaEmbed } from '../embeds/figma-embed';\nimport { FileDownloadCard } from '../embeds/file-download-card';\nimport { GoogleSheetsViewer } from '../embeds/google-sheets-viewer';\nimport { PdfViewer } from '../embeds/pdf-viewer';\nimport { DocViewer, type DocViewerProps } from './doc-viewer';\nimport { MarkdownSkeleton, EmbedSkeleton } from './skeletons';\n\ntype DocRenderer = (content: DocContent, handlers: DocRenderHandlers) => React.ReactNode;\n\n/**\n * Per-document-type renderer map. `markdown` is required (the lib does NOT\n * ship a default markdown renderer — embedders pick their own library +\n * sanitization to avoid an XSS surface in the lib).\n *\n * `pdf` / `google_sheet` / `figma` / `file` are optional — the lib provides\n * defaults from `components/embeds`. Override only when you want different\n * props than the default (e.g. a custom PDF toolbar, embedded credentials).\n */\nexport type DocumentTypeRenderers = { markdown: DocRenderer } & Partial<Record<DocumentType, DocRenderer>>;\n\nexport interface DocsHubPageProps extends Omit<DocViewerProps, 'renderContent' | 'renderSkeleton' | 'showAIChat'> {\n /** Per-document-type renderer map. `markdown` is REQUIRED. */\n documentTypeRenderers: DocumentTypeRenderers;\n\n /** Renderer for unknown / future document types. Defaults to a lib-styled\n * \"Unsupported document type\" message. */\n fallbackRenderer?: DocRenderer;\n\n /** Loading skeleton picker. Defaults: `markdown` / `undefined` →\n * `<MarkdownSkeleton>`, everything else → `<EmbedSkeleton>`. */\n renderSkeleton?: (documentType: DocumentType | undefined) => React.ReactNode;\n\n /** Defaults to `true` (the embeddable wrapper favors the chat-enabled\n * experience). Only mounts the in-source RAG search bar\n * (`<DocSearchBar>`) — does NOT require `ChatRuntimeContext`. */\n showAIChat?: boolean;\n}\n\nconst DEFAULT_TITLE = 'Documents';\n\nconst defaultFallbackRenderer: DocRenderer = () => (\n <div className=\"py-16 text-center\">\n <p className=\"text-ods-text-secondary\">Unsupported document type</p>\n </div>\n);\n\n// When the DAL hasn't populated the URL field for a rich-content type, the\n// embed-viewer components render a broken iframe (empty src). Fall back to\n// the same lib-styled \"Unsupported document type\" panel the explicit\n// fallback uses — the surface is honest about the missing data instead of\n// pretending to load. `FileDownloadCard` handles its own missing-URL state\n// (hides the Download button), so it doesn't need this guard.\nconst defaultPdfRenderer: DocRenderer = (content, handlers) =>\n content.fileUrl ? (\n <PdfViewer src={content.fileUrl} fileName={content.fileName} />\n ) : (\n defaultFallbackRenderer(content, handlers)\n );\n\nconst defaultGoogleSheetRenderer: DocRenderer = (content, handlers) =>\n content.externalUrl ? (\n <GoogleSheetsViewer externalUrl={content.externalUrl} fileName={content.fileName} />\n ) : (\n defaultFallbackRenderer(content, handlers)\n );\n\nconst defaultFigmaRenderer: DocRenderer = (content, handlers) =>\n content.externalUrl ? (\n <FigmaEmbed url={content.externalUrl} title={content.fileName} loading=\"eager\" />\n ) : (\n defaultFallbackRenderer(content, handlers)\n );\n\nconst defaultFileRenderer: DocRenderer = content => (\n <FileDownloadCard\n fileName={content.fileName}\n mimeType={content.mimeType}\n fileSize={content.fileSize}\n fileUrl={content.fileUrl}\n />\n);\n\nconst defaultRenderSkeleton = (documentType: DocumentType | undefined) =>\n !documentType || documentType === 'markdown' ? (\n <MarkdownSkeleton />\n ) : (\n // Forward the documentType so the embed skeleton renders the right\n // shape (PDF=2 buttons, sheets/figma=1 button, file=centered card).\n <EmbedSkeleton documentType={documentType} />\n );\n\n/**\n * Embeddable docs-hub page. Bundles `<DocViewer>` with safe defaults so the\n * minimum embed is a one-line mount (consumer only has to supply\n * `documentTypeRenderers.markdown`).\n *\n * Used by the hub at `/knowledge-base` and `/data-room`, and by third-party\n * React apps that embed the docs experience behind their own proxy. See\n * `docs/EMBEDDING_DOCS_HUB.md` for the embedder setup.\n *\n * SEO note: this component is `'use client'` (via the docs barrel) — server-\n * side SEO is the host's responsibility. The hub's `<DocSeoContent>` is the\n * canonical implementation embedders can reference.\n */\nexport function DocsHubPage({\n title = DEFAULT_TITLE,\n documentTypeRenderers,\n fallbackRenderer = defaultFallbackRenderer,\n renderSkeleton = defaultRenderSkeleton,\n showAIChat = true,\n className = 'min-h-screen',\n sidebarLabel = 'DOCUMENTATION',\n ...docViewerProps\n}: DocsHubPageProps) {\n const resolvedRenderers: DocumentTypeRenderers = {\n markdown: documentTypeRenderers.markdown,\n pdf: documentTypeRenderers.pdf ?? defaultPdfRenderer,\n google_sheet: documentTypeRenderers.google_sheet ?? defaultGoogleSheetRenderer,\n figma: documentTypeRenderers.figma ?? defaultFigmaRenderer,\n file: documentTypeRenderers.file ?? defaultFileRenderer,\n };\n\n const renderContent: DocViewerProps['renderContent'] = (content, handlers) => {\n const type = content.documentType ?? 'markdown';\n const renderer = resolvedRenderers[type] ?? fallbackRenderer;\n return renderer(content, handlers);\n };\n\n return (\n <DocViewer\n {...docViewerProps}\n title={title}\n showAIChat={showAIChat}\n className={className}\n sidebarLabel={sidebarLabel}\n renderContent={renderContent}\n renderSkeleton={renderSkeleton}\n />\n );\n}\n"]}
|