framefields 2.5.1 → 2.5.3
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/audio/index.d.mts +4 -4
- package/dist/audio/index.mjs +10 -10
- package/dist/{audio-C1A4N3Wh.d.mts → audio-2ew5y0Bl.d.mts} +3 -3
- package/dist/{audio-C1A4N3Wh.d.mts.map → audio-2ew5y0Bl.d.mts.map} +1 -1
- package/dist/{audio-CZwVENqB.mjs → audio-DrUskzwZ.mjs} +4 -4
- package/dist/{audio-CZwVENqB.mjs.map → audio-DrUskzwZ.mjs.map} +1 -1
- package/dist/components/index.d.mts +10 -10
- package/dist/components/index.mjs +44 -44
- package/dist/{dist-CQZ5_60A.mjs → dist-BPOi5xb_.mjs} +2 -2
- package/dist/{dist-CQZ5_60A.mjs.map → dist-BPOi5xb_.mjs.map} +1 -1
- package/dist/{dist-DBWkkSvJ.mjs → dist-Bf0H4lp1.mjs} +66 -28
- package/dist/dist-Bf0H4lp1.mjs.map +1 -0
- package/dist/{dist-DFeF-ygz.mjs → dist-D021IFfp.mjs} +3 -1
- package/dist/{dist-DFeF-ygz.mjs.map → dist-D021IFfp.mjs.map} +1 -1
- package/dist/{dist-DHQVWaV2.mjs → dist-DG65RDhc.mjs} +3 -3
- package/dist/{dist-DHQVWaV2.mjs.map → dist-DG65RDhc.mjs.map} +1 -1
- package/dist/effects/index.d.mts +4 -4
- package/dist/effects/index.mjs +35 -35
- package/dist/{effects-DZvq-nWT.mjs → effects-Z-brb-n7.mjs} +3 -3
- package/dist/{effects-DZvq-nWT.mjs.map → effects-Z-brb-n7.mjs.map} +1 -1
- package/dist/fonts/index.mjs +2 -2
- package/dist/{fonts-DcsxFpyU.mjs → fonts-DqYgk_1a.mjs} +2 -2
- package/dist/{fonts-DcsxFpyU.mjs.map → fonts-DqYgk_1a.mjs.map} +1 -1
- package/dist/{gpu-Dq_uI_00.mjs → gpu-CevnCkZC.mjs} +2 -2
- package/dist/{gpu-Dq_uI_00.mjs.map → gpu-CevnCkZC.mjs.map} +1 -1
- package/dist/{index-C2f4Hm-p.d.mts → index-COQTaniR.d.mts} +2 -2
- package/dist/{index-C2f4Hm-p.d.mts.map → index-COQTaniR.d.mts.map} +1 -1
- package/dist/{index-fymoeGNW.d.mts → index-CZK6CFLy.d.mts} +22 -19
- package/dist/index-CZK6CFLy.d.mts.map +1 -0
- package/dist/{index-BpdVOlsV.d.mts → index-CfMylY3O.d.mts} +3 -3
- package/dist/{index-BpdVOlsV.d.mts.map → index-CfMylY3O.d.mts.map} +1 -1
- package/dist/{index-DNKM3to5.d.mts → index-D1C735Wp.d.mts} +3 -3
- package/dist/{index-DNKM3to5.d.mts.map → index-D1C735Wp.d.mts.map} +1 -1
- package/dist/{index-D6-lH1Mx.d.mts → index-DSGFMpwL.d.mts} +4 -4
- package/dist/{index-D6-lH1Mx.d.mts.map → index-DSGFMpwL.d.mts.map} +1 -1
- package/dist/{index-B-KVpJMb.d.mts → index-KqW22NHL.d.mts} +2 -2
- package/dist/{index-B-KVpJMb.d.mts.map → index-KqW22NHL.d.mts.map} +1 -1
- package/dist/{index-DKssQ_wn.d.mts → index-tCKz9Tku.d.mts} +231 -233
- package/dist/index-tCKz9Tku.d.mts.map +1 -0
- package/dist/index.d.mts +11 -11
- package/dist/index.mjs +52 -52
- package/dist/preview/index.d.mts +3 -3
- package/dist/preview/index.mjs +10 -10
- package/dist/{preview-CYI4i4m3.mjs → preview-DVw2jLDh.mjs} +2 -2
- package/dist/{preview-CYI4i4m3.mjs.map → preview-DVw2jLDh.mjs.map} +1 -1
- package/dist/preview-engine/{chunk-3I6B3YPF.mjs → chunk-2WOCQNZX.mjs} +3 -3
- package/dist/preview-engine/{chunk-PDM3TJ45.mjs → chunk-45XGWIOY.mjs} +1 -1
- package/dist/preview-engine/{chunk-XUPZBI3Y.mjs → chunk-5FTCPTOX.mjs} +13 -7
- package/dist/preview-engine/{chunk-Z4TKEI3I.mjs → chunk-B555EGEU.mjs} +1 -1
- package/dist/preview-engine/{chunk-6GMEF2QB.mjs → chunk-FAEWI3LF.mjs} +1 -1
- package/dist/preview-engine/{chunk-W4HIVUUA.mjs → chunk-I3ZTDIVM.mjs} +1 -1
- package/dist/preview-engine/{chunk-CT2WFPYG.mjs → chunk-MCFYUKAJ.mjs} +8 -8
- package/dist/preview-engine/{chunk-AB5VITYM.mjs → chunk-PR37RCNO.mjs} +1 -1
- package/dist/preview-engine/{chunk-NAISBHLR.mjs → chunk-QP24X77K.mjs} +1 -1
- package/dist/preview-engine/chunk-SYP4AJMF.mjs +1 -0
- package/dist/preview-engine/{chunk-SNASF7OM.mjs → chunk-YSXMXD3F.mjs} +1 -1
- package/dist/preview-engine/components.mjs +1 -1
- package/dist/preview-engine/effects.mjs +1 -1
- package/dist/preview-engine/fonts.mjs +1 -1
- package/dist/preview-engine/framefields.mjs +1 -1
- package/dist/preview-engine/player.mjs +1 -1
- package/dist/preview-engine/renderers-BILNWTUC.mjs +228 -0
- package/dist/preview-engine/{renderers-HJ6NO5ZD.mjs → renderers-EJATUIUP.mjs} +1 -1
- package/dist/preview-engine/renderers-SKT7AGGE.mjs +1 -0
- package/dist/{program-BdPDtfVv.mjs → program-4mAVgg3c.mjs} +4 -4
- package/dist/{program-BdPDtfVv.mjs.map → program-4mAVgg3c.mjs.map} +1 -1
- package/dist/registry/captions.json +1 -1
- package/dist/registry/lower-third.json +1 -1
- package/dist/registry/meter.json +1 -1
- package/dist/renderer/index.d.mts +2 -2
- package/dist/renderer/index.mjs +2 -2
- package/dist/renderer-1ymN1FIT.mjs +5 -0
- package/dist/{renderer-VdTs-dqH.mjs → renderer-4P-DCjk2.mjs} +3 -3
- package/dist/{renderer-VdTs-dqH.mjs.map → renderer-4P-DCjk2.mjs.map} +1 -1
- package/dist/{renderer-B4blg067.mjs → renderer-5-9DPis6.mjs} +3 -3
- package/dist/{renderer-B4blg067.mjs.map → renderer-5-9DPis6.mjs.map} +1 -1
- package/dist/{renderer-COXKPofU.d.mts → renderer-92IAZzBU.d.mts} +2 -2
- package/dist/{renderer-COXKPofU.d.mts.map → renderer-92IAZzBU.d.mts.map} +1 -1
- package/dist/{renderer-o_JK4YfO.mjs → renderer-9gjn790a.mjs} +3 -3
- package/dist/{renderer-o_JK4YfO.mjs.map → renderer-9gjn790a.mjs.map} +1 -1
- package/dist/renderer-B-43EuKb.mjs +5 -0
- package/dist/renderer-B-v5ZboU.mjs +5 -0
- package/dist/{renderer-IMg0dK6K.mjs → renderer-B8PNlKEM.mjs} +3 -3
- package/dist/{renderer-IMg0dK6K.mjs.map → renderer-B8PNlKEM.mjs.map} +1 -1
- package/dist/{renderer-Db_wphHH.mjs → renderer-B9AZ6R1U.mjs} +64 -21
- package/dist/renderer-B9AZ6R1U.mjs.map +1 -0
- package/dist/{renderer-7DyjqxEj.mjs → renderer-B9iVE2GG.mjs} +3 -3
- package/dist/{renderer-7DyjqxEj.mjs.map → renderer-B9iVE2GG.mjs.map} +1 -1
- package/dist/{renderer-ChCUad83.mjs → renderer-B9m7xIPo.mjs} +3 -3
- package/dist/{renderer-ChCUad83.mjs.map → renderer-B9m7xIPo.mjs.map} +1 -1
- package/dist/{renderer-BAkPq8qo.mjs → renderer-B9yv3R1y.mjs} +2 -2
- package/dist/{renderer-BAkPq8qo.mjs.map → renderer-B9yv3R1y.mjs.map} +1 -1
- package/dist/{renderer-DuQMzR3v.mjs → renderer-BBA-iCi-.mjs} +8 -417
- package/dist/renderer-BBA-iCi-.mjs.map +1 -0
- package/dist/{renderer-l_U6kA8K.mjs → renderer-BIYJKdwA.mjs} +2 -2
- package/dist/{renderer-l_U6kA8K.mjs.map → renderer-BIYJKdwA.mjs.map} +1 -1
- package/dist/{renderer-DQuKNgIx.mjs → renderer-BIt-At3v.mjs} +4 -4
- package/dist/{renderer-DQuKNgIx.mjs.map → renderer-BIt-At3v.mjs.map} +1 -1
- package/dist/renderer-BKF8Sfbg.mjs +3 -0
- package/dist/{renderer-D2ru2Afi.mjs → renderer-BKayVZhL.mjs} +3 -3
- package/dist/{renderer-D2ru2Afi.mjs.map → renderer-BKayVZhL.mjs.map} +1 -1
- package/dist/renderer-BTB8ifgH.mjs +5 -0
- package/dist/{renderer-fH9SCJC2.mjs → renderer-BVtQzjgT.mjs} +2 -2
- package/dist/{renderer-fH9SCJC2.mjs.map → renderer-BVtQzjgT.mjs.map} +1 -1
- package/dist/{renderer-DBjV6C2u.mjs → renderer-BWqQxZNk.mjs} +2 -2
- package/dist/renderer-BYxzR6Hp.mjs +5 -0
- package/dist/renderer-BcGhO_Mw.mjs +5 -0
- package/dist/{renderer-DK34NH92.mjs → renderer-BcSSAzc4.mjs} +3 -3
- package/dist/{renderer-DK34NH92.mjs.map → renderer-BcSSAzc4.mjs.map} +1 -1
- package/dist/{renderer-BBTkfhL8.mjs → renderer-Bh0dwc8n.mjs} +3 -3
- package/dist/{renderer-BBTkfhL8.mjs.map → renderer-Bh0dwc8n.mjs.map} +1 -1
- package/dist/{renderer-CDX2f82b.mjs → renderer-BjxceNBj.mjs} +3 -3
- package/dist/{renderer-CDX2f82b.mjs.map → renderer-BjxceNBj.mjs.map} +1 -1
- package/dist/renderer-Bk3RgiFg.mjs +5 -0
- package/dist/{renderer-Bmj7AyJP.mjs → renderer-Bk7vVfTu.mjs} +3 -3
- package/dist/{renderer-Bmj7AyJP.mjs.map → renderer-Bk7vVfTu.mjs.map} +1 -1
- package/dist/{renderer-Ch7qwjH9.mjs → renderer-BmX2CmyO.mjs} +3 -3
- package/dist/{renderer-Ch7qwjH9.mjs.map → renderer-BmX2CmyO.mjs.map} +1 -1
- package/dist/{renderer-Bo1PY0zW.mjs → renderer-BpBhvMi1.mjs} +2 -2
- package/dist/{renderer-ZjSP5jw6.mjs → renderer-Bt_j2Zi6.mjs} +2 -2
- package/dist/{renderer-ZjSP5jw6.mjs.map → renderer-Bt_j2Zi6.mjs.map} +1 -1
- package/dist/renderer-BvItCxkw.mjs +5 -0
- package/dist/{renderer-Bw5YP2Db.mjs → renderer-BwRLXhxJ.mjs} +3 -3
- package/dist/{renderer-Bw5YP2Db.mjs.map → renderer-BwRLXhxJ.mjs.map} +1 -1
- package/dist/{renderer-CWSM0IGG.mjs → renderer-C0P8ZRiC.mjs} +2 -2
- package/dist/{renderer-CWSM0IGG.mjs.map → renderer-C0P8ZRiC.mjs.map} +1 -1
- package/dist/{renderer-Z39F9QK5.mjs → renderer-C1sFg34Y.mjs} +3 -3
- package/dist/{renderer-Z39F9QK5.mjs.map → renderer-C1sFg34Y.mjs.map} +1 -1
- package/dist/{renderer-BGNdwdCR.mjs → renderer-C4f_X1Qc.mjs} +3 -3
- package/dist/{renderer-BGNdwdCR.mjs.map → renderer-C4f_X1Qc.mjs.map} +1 -1
- package/dist/{renderer-DyMEAZIm.mjs → renderer-C4pS7aAI.mjs} +4 -4
- package/dist/{renderer-DyMEAZIm.mjs.map → renderer-C4pS7aAI.mjs.map} +1 -1
- package/dist/{renderer-BWa3CLK7.mjs → renderer-C5h2TSjH.mjs} +2 -2
- package/dist/{renderer-BWa3CLK7.mjs.map → renderer-C5h2TSjH.mjs.map} +1 -1
- package/dist/{renderer-BN_n6O6P.mjs → renderer-C7HIJcJK.mjs} +3 -3
- package/dist/{renderer-BN_n6O6P.mjs.map → renderer-C7HIJcJK.mjs.map} +1 -1
- package/dist/{renderer-G3px5ODP.mjs → renderer-C8-NmotX.mjs} +3 -3
- package/dist/{renderer-G3px5ODP.mjs.map → renderer-C8-NmotX.mjs.map} +1 -1
- package/dist/renderer-CCnfPHqk.mjs +5 -0
- package/dist/{renderer-Dx28wTi1.d.mts → renderer-CE9N4EEI.d.mts} +2 -2
- package/dist/{renderer-Dx28wTi1.d.mts.map → renderer-CE9N4EEI.d.mts.map} +1 -1
- package/dist/{renderer-CLO8QHWE.mjs → renderer-CGKKjJ5Q.mjs} +3 -3
- package/dist/{renderer-CLO8QHWE.mjs.map → renderer-CGKKjJ5Q.mjs.map} +1 -1
- package/dist/renderer-CIoWQNaA.mjs +5 -0
- package/dist/{renderer-2bipMLqz.mjs → renderer-CJ2-jEy7.mjs} +2 -2
- package/dist/{renderer-2bipMLqz.mjs.map → renderer-CJ2-jEy7.mjs.map} +1 -1
- package/dist/renderer-CMwQtIIq.mjs +5 -0
- package/dist/renderer-CN8kRB90.mjs +6 -0
- package/dist/renderer-CPKBDif1.mjs +5 -0
- package/dist/{renderer-Cj1giMd0.mjs → renderer-CPl1wOrj.mjs} +2 -2
- package/dist/{renderer-Cj1giMd0.mjs.map → renderer-CPl1wOrj.mjs.map} +1 -1
- package/dist/{renderer-BmoQJUJO.mjs → renderer-CT_3Fegr.mjs} +3 -3
- package/dist/{renderer-BmoQJUJO.mjs.map → renderer-CT_3Fegr.mjs.map} +1 -1
- package/dist/{renderer-BfrzKmRh.mjs → renderer-CTvPNRMH.mjs} +2 -2
- package/dist/{renderer-BfrzKmRh.mjs.map → renderer-CTvPNRMH.mjs.map} +1 -1
- package/dist/{renderer-DWaqF7-6.mjs → renderer-CWnhRMOq.mjs} +3 -3
- package/dist/{renderer-DWaqF7-6.mjs.map → renderer-CWnhRMOq.mjs.map} +1 -1
- package/dist/{renderer-BBTdHKH7.mjs → renderer-CXa6SWXy.mjs} +3 -3
- package/dist/{renderer-BBTdHKH7.mjs.map → renderer-CXa6SWXy.mjs.map} +1 -1
- package/dist/renderer-CamsHxEP.mjs +5 -0
- package/dist/{renderer-CMEqUhrW.mjs → renderer-Cb5uDZm0.mjs} +3 -3
- package/dist/{renderer-CMEqUhrW.mjs.map → renderer-Cb5uDZm0.mjs.map} +1 -1
- package/dist/renderer-CeQormOH.mjs +5 -0
- package/dist/renderer-Cex4eMtv.mjs +5 -0
- package/dist/{renderer-kP5Y282J.mjs → renderer-CgMnZYTM.mjs} +4 -4
- package/dist/{renderer-kP5Y282J.mjs.map → renderer-CgMnZYTM.mjs.map} +1 -1
- package/dist/{renderer-B5YYBBDq.mjs → renderer-CjcrhBB2.mjs} +2 -2
- package/dist/{renderer-B5YYBBDq.mjs.map → renderer-CjcrhBB2.mjs.map} +1 -1
- package/dist/{renderer-CXbXF7Dh.mjs → renderer-Cjw6lyaK.mjs} +3 -3
- package/dist/{renderer-CXbXF7Dh.mjs.map → renderer-Cjw6lyaK.mjs.map} +1 -1
- package/dist/renderer-CjyTrNlr.mjs +5 -0
- package/dist/{renderer-D6p8b6ql.mjs → renderer-CkJXq4bD.mjs} +3 -3
- package/dist/{renderer-D6p8b6ql.mjs.map → renderer-CkJXq4bD.mjs.map} +1 -1
- package/dist/renderer-Clv9TO6C.mjs +5 -0
- package/dist/{renderer-gjT5v76-.mjs → renderer-Cm9fEQsw.mjs} +2 -2
- package/dist/{renderer-gjT5v76-.mjs.map → renderer-Cm9fEQsw.mjs.map} +1 -1
- package/dist/{renderer-CJoa4H2G.mjs → renderer-CuwVuVKG.mjs} +2 -2
- package/dist/{renderer-CJoa4H2G.mjs.map → renderer-CuwVuVKG.mjs.map} +1 -1
- package/dist/{renderer-D5u3GVtD.mjs → renderer-CvYxxS_X.mjs} +71 -71
- package/dist/{renderer-D5u3GVtD.mjs.map → renderer-CvYxxS_X.mjs.map} +1 -1
- package/dist/{renderer-VxVi_0c7.mjs → renderer-CxOaqnho.mjs} +2 -2
- package/dist/{renderer-VxVi_0c7.mjs.map → renderer-CxOaqnho.mjs.map} +1 -1
- package/dist/{renderer-DkPDYtD3.mjs → renderer-CxkUI9s_.mjs} +3 -3
- package/dist/{renderer-DkPDYtD3.mjs.map → renderer-CxkUI9s_.mjs.map} +1 -1
- package/dist/{renderer-BYJS2lhn.mjs → renderer-Cz5SOnJr.mjs} +3 -3
- package/dist/{renderer-BYJS2lhn.mjs.map → renderer-Cz5SOnJr.mjs.map} +1 -1
- package/dist/{renderer-WVrUeiLZ.mjs → renderer-D-FwaV1R.mjs} +2 -2
- package/dist/{renderer-BWnFh7NO.mjs → renderer-D0iSbNza.mjs} +3 -3
- package/dist/{renderer-BWnFh7NO.mjs.map → renderer-D0iSbNza.mjs.map} +1 -1
- package/dist/{renderer-DHqicGgT.mjs → renderer-D22g6ION.mjs} +3 -3
- package/dist/{renderer-DHqicGgT.mjs.map → renderer-D22g6ION.mjs.map} +1 -1
- package/dist/renderer-D4LnJk-M.mjs +5 -0
- package/dist/renderer-D5OfQ8Ut.mjs +5 -0
- package/dist/renderer-DBYssktC.mjs +5 -0
- package/dist/renderer-DC-Aj3me.mjs +5 -0
- package/dist/renderer-DG97_Aoc.mjs +5 -0
- package/dist/{renderer-Cjs1OeBu.mjs → renderer-DPVEs5ME.mjs} +3 -3
- package/dist/{renderer-Cjs1OeBu.mjs.map → renderer-DPVEs5ME.mjs.map} +1 -1
- package/dist/{renderer-COtZ315B.mjs → renderer-DPzair18.mjs} +2 -2
- package/dist/renderer-DS4UGjcS.mjs +5 -0
- package/dist/{renderer-PLoGrFGz.mjs → renderer-DTXAa2pL.mjs} +3 -3
- package/dist/{renderer-PLoGrFGz.mjs.map → renderer-DTXAa2pL.mjs.map} +1 -1
- package/dist/{renderer-ChjkppUz.mjs → renderer-DYdll3xW.mjs} +3 -3
- package/dist/{renderer-ChjkppUz.mjs.map → renderer-DYdll3xW.mjs.map} +1 -1
- package/dist/{renderer-BhhweLr_.mjs → renderer-Datqywo_.mjs} +3 -3
- package/dist/{renderer-BhhweLr_.mjs.map → renderer-Datqywo_.mjs.map} +1 -1
- package/dist/renderer-DdG6ibtM.mjs +5 -0
- package/dist/{renderer-D0X7Eh-T.mjs → renderer-Ddg3dJmu.mjs} +3 -3
- package/dist/{renderer-D0X7Eh-T.mjs.map → renderer-Ddg3dJmu.mjs.map} +1 -1
- package/dist/{renderer-CmKdr2nA.mjs → renderer-De7gno9X.mjs} +3 -3
- package/dist/{renderer-CmKdr2nA.mjs.map → renderer-De7gno9X.mjs.map} +1 -1
- package/dist/renderer-DhZi4f3t.mjs +5 -0
- package/dist/{renderer-BJwOM1Dq.mjs → renderer-Dkt1oh9H.mjs} +3 -3
- package/dist/{renderer-BJwOM1Dq.mjs.map → renderer-Dkt1oh9H.mjs.map} +1 -1
- package/dist/{renderer-DqO9gBAi.mjs → renderer-DnT62IDK.mjs} +3 -3
- package/dist/{renderer-DqO9gBAi.mjs.map → renderer-DnT62IDK.mjs.map} +1 -1
- package/dist/{renderer-vwoyR8pW.mjs → renderer-Du8z4UrN.mjs} +2 -2
- package/dist/{renderer-vwoyR8pW.mjs.map → renderer-Du8z4UrN.mjs.map} +1 -1
- package/dist/{renderer-B8LXXvHi.mjs → renderer-MQPiZQXN.mjs} +3 -3
- package/dist/{renderer-B8LXXvHi.mjs.map → renderer-MQPiZQXN.mjs.map} +1 -1
- package/dist/renderer-MgLekf6d.mjs +5 -0
- package/dist/{renderer-FCqOfN6n.mjs → renderer-NL2IZShC.mjs} +3 -3
- package/dist/{renderer-FCqOfN6n.mjs.map → renderer-NL2IZShC.mjs.map} +1 -1
- package/dist/renderer-Qvs-1lWh.mjs +5 -0
- package/dist/{renderer-B31zMDE8.mjs → renderer-RiWi7y5C.mjs} +4 -4
- package/dist/{renderer-B31zMDE8.mjs.map → renderer-RiWi7y5C.mjs.map} +1 -1
- package/dist/{renderer-Fsdod0yZ.mjs → renderer-T2dd9a2L.mjs} +3 -3
- package/dist/{renderer-Fsdod0yZ.mjs.map → renderer-T2dd9a2L.mjs.map} +1 -1
- package/dist/{renderer-1VKN5j2C.mjs → renderer-Y5JmTL8J.mjs} +3 -3
- package/dist/{renderer-1VKN5j2C.mjs.map → renderer-Y5JmTL8J.mjs.map} +1 -1
- package/dist/{renderer-CdUf7ZDJ.mjs → renderer-_vcT__tW.mjs} +3 -3
- package/dist/{renderer-CdUf7ZDJ.mjs.map → renderer-_vcT__tW.mjs.map} +1 -1
- package/dist/renderer-bN7dOErt.mjs +5 -0
- package/dist/renderer-jWKVmReU.mjs +5 -0
- package/dist/{renderer-kkDfQ56G.mjs → renderer-khVVYoVF.mjs} +3 -3
- package/dist/{renderer-kkDfQ56G.mjs.map → renderer-khVVYoVF.mjs.map} +1 -1
- package/dist/renderer-lk-At57G.mjs +5 -0
- package/dist/renderer-qi8Slwfo.mjs +5 -0
- package/dist/renderer-qxOH4JH1.mjs +5 -0
- package/dist/renderer-t30NpmIl.mjs +5 -0
- package/dist/{renderer-Bo_d7h86.mjs → renderer-vc8t_z-y.mjs} +3 -3
- package/dist/{renderer-Bo_d7h86.mjs.map → renderer-vc8t_z-y.mjs.map} +1 -1
- package/dist/{renderer-0cHsK6xR.mjs → renderer-wEXBnlr1.mjs} +3 -3
- package/dist/{renderer-0cHsK6xR.mjs.map → renderer-wEXBnlr1.mjs.map} +1 -1
- package/dist/{renderer-CNs1vBUy.mjs → renderer-wIsJb2eO.mjs} +2 -2
- package/dist/{renderer-CNs1vBUy.mjs.map → renderer-wIsJb2eO.mjs.map} +1 -1
- package/dist/renderer-wKW4HWRx.mjs +5 -0
- package/dist/renderer-ydjPm8bp.mjs +5 -0
- package/dist/{renderer-DP41Fokp.mjs → renderer-zdpn1Hkk.mjs} +3 -3
- package/dist/{renderer-DP41Fokp.mjs.map → renderer-zdpn1Hkk.mjs.map} +1 -1
- package/dist/{renderers-CP8CFWDs-DiX2Y4Qp.mjs → renderers-CP8CFWDs-BbLkcJTN.mjs} +3 -3
- package/dist/{renderers-CP8CFWDs-DiX2Y4Qp.mjs.map → renderers-CP8CFWDs-BbLkcJTN.mjs.map} +1 -1
- package/dist/signals/index.d.mts +5 -5
- package/dist/signals/index.mjs +6 -6
- package/dist/{signals-Cf9mfNGE.mjs → signals-BrNF9l9s.mjs} +3 -3
- package/dist/{signals-Cf9mfNGE.mjs.map → signals-BrNF9l9s.mjs.map} +1 -1
- package/dist/{src-Ds9IAk5S.mjs → src-WAnxhDYD.mjs} +9 -9
- package/dist/{src-Ds9IAk5S.mjs.map → src-WAnxhDYD.mjs.map} +1 -1
- package/package.json +58 -58
- package/dist/dist-DBWkkSvJ.mjs.map +0 -1
- package/dist/index-DKssQ_wn.d.mts.map +0 -1
- package/dist/index-fymoeGNW.d.mts.map +0 -1
- package/dist/preview-engine/chunk-X6HUH6K4.mjs +0 -1
- package/dist/preview-engine/renderers-KSQM7EM5.mjs +0 -1
- package/dist/preview-engine/renderers-XLBHSQZJ.mjs +0 -228
- package/dist/renderer-B-D_VZg3.mjs +0 -5
- package/dist/renderer-B64dhpDX.mjs +0 -5
- package/dist/renderer-BA2Ko4H1.mjs +0 -3
- package/dist/renderer-BDL4jyrb.mjs +0 -5
- package/dist/renderer-BKCZGKkl.mjs +0 -5
- package/dist/renderer-BKs3jI7-.mjs +0 -5
- package/dist/renderer-BW74Zf-u.mjs +0 -5
- package/dist/renderer-BXuQN61O.mjs +0 -5
- package/dist/renderer-BY1pPvzx.mjs +0 -6
- package/dist/renderer-Bahthqvq.mjs +0 -5
- package/dist/renderer-BbaiQG8h.mjs +0 -5
- package/dist/renderer-BhHF-08y.mjs +0 -5
- package/dist/renderer-BnrMcw6C.mjs +0 -5
- package/dist/renderer-BsS61z5l.mjs +0 -5
- package/dist/renderer-Btuq2Eq8.mjs +0 -5
- package/dist/renderer-BxmsEl2x.mjs +0 -5
- package/dist/renderer-CGOyHdKs.mjs +0 -5
- package/dist/renderer-CJjmpmpu.mjs +0 -5
- package/dist/renderer-CQvDDAIu.mjs +0 -5
- package/dist/renderer-CYxigw7S.mjs +0 -5
- package/dist/renderer-CgDofO5z.mjs +0 -5
- package/dist/renderer-CgmQchab.mjs +0 -5
- package/dist/renderer-CjzrLwW_.mjs +0 -5
- package/dist/renderer-ClnYWADT.mjs +0 -5
- package/dist/renderer-CwAd71C6.mjs +0 -5
- package/dist/renderer-D3Z9t-xT.mjs +0 -5
- package/dist/renderer-DKH1oSlW.mjs +0 -5
- package/dist/renderer-DTkf_hwG.mjs +0 -5
- package/dist/renderer-DU0bmOS7.mjs +0 -5
- package/dist/renderer-D_vJHQt5.mjs +0 -5
- package/dist/renderer-Db_wphHH.mjs.map +0 -1
- package/dist/renderer-Dc5n0M6P.mjs +0 -5
- package/dist/renderer-Ddfn0Dha.mjs +0 -5
- package/dist/renderer-DeSoNQeG.mjs +0 -5
- package/dist/renderer-DuQMzR3v.mjs.map +0 -1
- package/dist/renderer-Dzca9Qba.mjs +0 -5
- package/dist/renderer-HRVvtc9c.mjs +0 -5
- package/dist/renderer-M3Qs4f1K.mjs +0 -5
- package/dist/renderer-NDHJt_Ae.mjs +0 -5
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"files": [
|
|
24
24
|
{
|
|
25
25
|
"path": "captions/index.ts",
|
|
26
|
-
"content": "/**\n * Captions: word-timed captions for talking-head and social video, in the\n * styles people publish, with exact word highlighting. Each phrase is one\n * text node; per-word colour, pops and markers are GPU glyph animators and\n * keyed plates, timed to the word's own frame.\n *\n * Coordinates are the frame's: the root covers the video (default\n * 1920 × 1080 × theme scale, or the given width / height) from 0, 0.\n */\nimport {\n\ttype KeyframeTuple,\n\tLayerAnimation,\n\ttype LayoutNode,\n\ttype TextNode,\n} from \"framefields\";\nimport {\n\ttype Animatable,\n\taudioLevelsSchema,\n\tbindComponent,\n\tconstantExpression,\n\tcontrastRatio,\n\tdefineComponent,\n\ttype GlyphAnimator,\n\tglyphAnimator,\n\ttype MeasureOptions,\n\trangeRects,\n\ttype Rect,\n\trendererChars,\n\tscheduleExpression,\n\tstepExpression,\n\tthemeFrames,\n\twindowExpression,\n\tz,\n} from \"framefields/components\";\nimport { parseTranscript, type Word } from \"./parse.js\";\nimport { buildPhrases, type GroupOptions, type Phrase } from \"./phrases.js\";\n\nexport type { Word } from \"./parse.js\";\n\nexport const CAPTION_STYLES = [\n\t\"clean\",\n\t\"karaoke\",\n\t\"highlight\",\n\t\"pop\",\n\t\"bold\",\n\t\"outline\",\n\t\"pill\",\n\t\"gradient\",\n] as const;\nexport type CaptionStyle = (typeof CAPTION_STYLES)[number];\n\nexport interface CaptionsCues {\n\t[name: string]: ReadonlyArray<{ frame: number }>;\n\t/** Each word, from its first frame to its last. */\n\twords: ReadonlyArray<{\n\t\tframe: number;\n\t\tend: number;\n\t\tindex: number;\n\t\ttext: string;\n\t\temphasised: boolean;\n\t}>;\n\t/** Each phrase on screen. */\n\tphrases: ReadonlyArray<{ frame: number; end: number; text: string }>;\n}\n\n/** Per-style grouping and placement defaults. */\nconst GROUP: Record<CaptionStyle, Partial<GroupOptions>> = {\n\tclean: { maxChars: 36, maxLines: 2 },\n\tkaraoke: {},\n\thighlight: {},\n\tpop: { maxChars: 22 },\n\tbold: { maxChars: 14, maxWords: 2, maxLines: 1 },\n\toutline: { maxChars: 22 },\n\tpill: { maxChars: 24, maxLines: 1 },\n\tgradient: {},\n};\n\nconst BASE_GROUP: GroupOptions = {\n\tmaxChars: 28,\n\tmaxWords: 12,\n\tmaxLines: 2,\n\tbreakOnPunctuation: true,\n\tminGap: 0.6,\n};\n\n/** The area captions stay inside: clear of edges and, in portrait, platform overlays. */\nfunction safeArea(\n\tW: number,\n\tH: number,\n\tarea: \"portrait\" | \"landscape\" | Rect | undefined,\n): Rect {\n\tif (area && typeof area === \"object\") return area;\n\tconst portrait = area ? area === \"portrait\" : H > W;\n\treturn portrait\n\t\t? { x: W * 0.08, y: H * 0.12, width: W * 0.84, height: H * 0.66 }\n\t\t: { x: W * 0.1, y: H * 0.08, width: W * 0.8, height: H * 0.82 };\n}\n\nexport const captions = /* @__PURE__ */ defineComponent({\n\tname: \"Captions\",\n\tprops: () => ({\n\t\t/** `{ text, start, end }` in seconds. Or `transcript`. */\n\t\twords: z\n\t\t\t.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\ttext: z.string(),\n\t\t\t\t\t\tstart: z.number().min(0),\n\t\t\t\t\t\tend: z.number().min(0),\n\t\t\t\t\t})\n\t\t\t\t\t.strict(),\n\t\t\t)\n\t\t\t.optional(),\n\t\t/** SRT, WebVTT, or word-level JSON (`[{ word | text, start, end }]`). */\n\t\ttranscript: z.string().optional(),\n\t\tstyle: z.enum(CAPTION_STYLES).default(\"karaoke\"),\n\t\t/** The voice's levels (`analyzeAudio`): the active word swells with it. Frame 0 = the component's first frame. */\n\t\tpulse: z.union([audioLevelsSchema(), z.literal(false)]).default(false),\n\t\t/** Scale added at full level. */\n\t\tpulseAmount: z.number().min(0).default(0.12),\n\t\tgroup: z\n\t\t\t.object({\n\t\t\t\tmaxChars: z.number().int().positive().optional(),\n\t\t\t\tmaxWords: z.number().int().positive().optional(),\n\t\t\t\tmaxLines: z.number().int().positive().optional(),\n\t\t\t\tbreakOnPunctuation: z.boolean().optional(),\n\t\t\t\tminGap: z.number().min(0).optional(),\n\t\t\t})\n\t\t\t.strict()\n\t\t\t.default({}),\n\t\t/** Words or patterns in the accent colour, or a test per word. */\n\t\temphasis: z\n\t\t\t.union([\n\t\t\t\tz.array(z.union([z.string(), z.instanceof(RegExp)])),\n\t\t\t\tz.custom<(word: string, index: number) => boolean>(\n\t\t\t\t\t(v) => typeof v === \"function\",\n\t\t\t\t),\n\t\t\t])\n\t\t\t.default([]),\n\t\tcase: z.enum([\"as-is\", \"upper\", \"lower\"]).optional(),\n\t\tplacement: z\n\t\t\t.object({\n\t\t\t\tanchor: z.enum([\"bottom\", \"center\", \"top\"]).optional(),\n\t\t\t\tsafeArea: z\n\t\t\t\t\t.union([\n\t\t\t\t\t\tz.enum([\"portrait\", \"landscape\"]),\n\t\t\t\t\t\tz.object({\n\t\t\t\t\t\t\tx: z.number(),\n\t\t\t\t\t\t\ty: z.number(),\n\t\t\t\t\t\t\twidth: z.number(),\n\t\t\t\t\t\t\theight: z.number(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t])\n\t\t\t\t\t.optional(),\n\t\t\t\t/** Extra px from the anchor edge, toward the centre. */\n\t\t\t\toffset: z.number().default(0),\n\t\t\t})\n\t\t\t.strict()\n\t\t\t.default({ offset: 0 }),\n\t\t/** A clip whose person / subject is cut out and drawn over the captions. */\n\t\tbehind: z\n\t\t\t.object({\n\t\t\t\tvideo: z.string(),\n\t\t\t\tmode: z.enum([\"person\", \"subject\"]).default(\"person\"),\n\t\t\t})\n\t\t\t.strict()\n\t\t\t.optional(),\n\t\t/** A backing plate behind each phrase (`clean`, `karaoke`). */\n\t\tplate: z.boolean().default(false),\n\t\tfontFamily: z.string().optional(),\n\t\t/** Default: fits `group.maxChars` across the safe area. */\n\t\tfontSize: z.number().positive().optional(),\n\t\tfontWeight: z.union([z.number(), z.string()]).optional(),\n\t\tfill: z.string().optional(),\n\t\taccent: z.string().optional(),\n\t}),\n\tdefaults: { enter: false, exit: false },\n\tbuild(ctx, props) {\n\t\tconst { host, theme, fps } = ctx;\n\t\tconst W = ctx.requested.width ?? 1920 * theme.scale;\n\t\tconst H = ctx.requested.height ?? 1080 * theme.scale;\n\t\tconst style = props.style;\n\n\t\t// ── Words ─────────────────────────────────────────────────────────\n\t\tlet words: Word[];\n\t\tif (props.words) words = props.words.map((w) => ({ ...w }));\n\t\telse if (props.transcript) words = parseTranscript(props.transcript);\n\t\telse throw new Error(\"Captions: give `words` or a `transcript`.\");\n\t\tconst casing = props.case ?? (style === \"bold\" ? \"upper\" : \"as-is\");\n\t\tif (casing !== \"as-is\")\n\t\t\twords = words.map((w) => ({\n\t\t\t\t...w,\n\t\t\t\ttext: casing === \"upper\" ? w.text.toUpperCase() : w.text.toLowerCase(),\n\t\t\t}));\n\t\tconst emphasised = words.map((w, i) =>\n\t\t\ttypeof props.emphasis === \"function\"\n\t\t\t\t? props.emphasis(w.text, i)\n\t\t\t\t: props.emphasis.some((p) =>\n\t\t\t\t\t\ttypeof p === \"string\"\n\t\t\t\t\t\t\t? w.text.toLowerCase().replace(/[^\\p{L}\\p{N}%]/gu, \"\") ===\n\t\t\t\t\t\t\t\tp.toLowerCase()\n\t\t\t\t\t\t\t: new RegExp(p.source, p.flags.replace(\"g\", \"\")).test(w.text),\n\t\t\t\t\t),\n\t\t);\n\n\t\t// ── Layout ────────────────────────────────────────────────────────\n\t\tconst group: GroupOptions = {\n\t\t\t...BASE_GROUP,\n\t\t\t...GROUP[style],\n\t\t\t...stripUndefined(props.group),\n\t\t};\n\t\tconst safe = safeArea(W, H, props.placement.safeArea);\n\t\tconst fontFamily = props.fontFamily ?? theme.font.display;\n\t\tconst weight = props.fontWeight ?? (style === \"clean\" ? undefined : 800);\n\t\tconst measureAt = (text: string, size: number, width?: number) =>\n\t\t\thost.measure(text, {\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: size,\n\t\t\t\tfontWeight: weight,\n\t\t\t\t...(width !== undefined && { width, align: \"center\" as const }),\n\t\t\t} satisfies MeasureOptions);\n\t\t// Auto size: a line of `maxChars` average glyphs spans the safe width.\n\t\tconst sample = \"nonsense words\".repeat(4).slice(0, group.maxChars);\n\t\tconst fs =\n\t\t\tprops.fontSize ??\n\t\t\tMath.min(\n\t\t\t\t(safe.width / Math.max(1, measureAt(sample, 100).width)) * 100 * 0.94,\n\t\t\t\tsafe.height / (group.maxLines * 1.6),\n\t\t\t);\n\t\tconst fits = (line: string) => measureAt(line, fs).width <= safe.width;\n\t\tconst phrases = buildPhrases(words, group, fits);\n\n\t\tconst ink = props.fill ?? theme.color.ink;\n\t\tconst accent = props.accent ?? theme.color.accent;\n\t\tconst anchor =\n\t\t\tprops.placement.anchor ?? (style === \"bold\" ? \"center\" : \"bottom\");\n\t\tconst frameOf = (seconds: number) => Math.round(seconds * fps);\n\t\tif (props.pulse && Math.abs(props.pulse.fps - fps) > 1e-6)\n\t\t\tthrow new Error(\n\t\t\t\t`Captions: \\`pulse\\` was analysed at ${props.pulse.fps} fps but the component runs at ${fps}. Analyse with { fps: ${fps} }.`,\n\t\t\t);\n\t\tconst fast = themeFrames(theme, \"fast\", fps);\n\t\tconst children: LayoutNode[] = [];\n\t\tconst parts: Record<string, Animatable<LayoutNode>> = {};\n\t\tconst wordCues: Array<CaptionsCues[\"words\"][number]> = [];\n\t\tconst phraseCues: Array<CaptionsCues[\"phrases\"][number]> = [];\n\n\t\tphrases.forEach((phrase, p) => {\n\t\t\tconst layout = phraseLayout(phrase, words);\n\t\t\tconst m = measureAt(layout.text, fs, safe.width);\n\t\t\tconst options: MeasureOptions = {\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: fs,\n\t\t\t\tfontWeight: weight,\n\t\t\t\twidth: safe.width,\n\t\t\t\talign: \"center\",\n\t\t\t};\n\t\t\tconst y =\n\t\t\t\tanchor === \"bottom\"\n\t\t\t\t\t? safe.y + safe.height - m.height - props.placement.offset\n\t\t\t\t\t: anchor === \"top\"\n\t\t\t\t\t\t? safe.y + props.placement.offset\n\t\t\t\t\t\t: safe.y + (safe.height - m.height) / 2;\n\t\t\tconst start = frameOf(phrase.start);\n\t\t\tconst end = Math.max(start + 1, frameOf(phrase.end));\n\t\t\t// Word windows, local to the phrase (its box starts at `start`).\n\t\t\tconst wStart = layout.words.map(\n\t\t\t\t(w) => frameOf((words[w.index] as Word).start) - start,\n\t\t\t);\n\t\t\tconst wEnd = layout.words.map((w, k) =>\n\t\t\t\tk + 1 < layout.words.length\n\t\t\t\t\t? (wStart[k + 1] as number)\n\t\t\t\t\t: Math.max(\n\t\t\t\t\t\t\t(wStart[k] as number) + 1,\n\t\t\t\t\t\t\tframeOf((words[w.index] as Word).end) - start,\n\t\t\t\t\t\t),\n\t\t\t);\n\t\t\tconst rects = layout.words.map(\n\t\t\t\t(w) => rangeRects(m, w.from, w.to, options)[0] as Rect,\n\t\t\t);\n\t\t\tconst animators: GlyphAnimator[] = [];\n\n\t\t\tconst emphasisWeights = layout.words.map((w) =>\n\t\t\t\temphasised[w.index] ? 1 : 0,\n\t\t\t);\n\t\t\tif (emphasisWeights.some(Boolean))\n\t\t\t\tanimators.push(\n\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\"emphasis\",\n\t\t\t\t\t\tconstantExpression(\"word\", emphasisWeights),\n\t\t\t\t\t\t{ fillColor: accent },\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\tconst node: Partial<TextNode> = {\n\t\t\t\tid: ctx.partId(`phrase-${p}`),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: safe.width,\n\t\t\t\talign: \"center\",\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: fs,\n\t\t\t\tfill: ink,\n\t\t\t\t...(weight !== undefined && { fontWeight: weight }),\n\t\t\t};\n\t\t\tconst plates: LayoutNode[] = [];\n\t\t\tconst box = (\n\t\t\t\tid: string,\n\t\t\t\tr: Rect,\n\t\t\t\tpad: number,\n\t\t\t\tbackground: string,\n\t\t\t\tradius: number,\n\t\t\t\textra = {},\n\t\t\t) =>\n\t\t\t\thost.box({\n\t\t\t\t\tid: ctx.partId(`phrase-${p}-${id}`),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: r.x - pad,\n\t\t\t\t\ty: r.y - pad * 0.5,\n\t\t\t\t\twidth: r.width + pad * 2,\n\t\t\t\t\theight: r.height + pad,\n\t\t\t\t\tbackground,\n\t\t\t\t\tborderRadius: radius,\n\t\t\t\t\t...extra,\n\t\t\t\t});\n\t\t\tconst blockRect: Rect = {\n\t\t\t\tx: Math.min(...rects.map((r) => r.x)),\n\t\t\t\ty: 0,\n\t\t\t\twidth:\n\t\t\t\t\tMath.max(...rects.map((r) => r.x + r.width)) -\n\t\t\t\t\tMath.min(...rects.map((r) => r.x)),\n\t\t\t\theight: m.height,\n\t\t\t};\n\n\t\t\tswitch (style) {\n\t\t\t\tcase \"clean\":\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"karaoke\":\n\t\t\t\t\t// Spoken words turn from muted to ink on their exact frame.\n\t\t\t\t\tanimators.push(\n\t\t\t\t\t\tglyphAnimator(\"spoken\", stepExpression(\"word\", wStart), {\n\t\t\t\t\t\t\tfillColor: theme.color.muted,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"highlight\":\n\t\t\t\tcase \"pill\": {\n\t\t\t\t\tconst pad = fs * (style === \"pill\" ? 0.22 : 0.16);\n\t\t\t\t\tconst glide = Math.max(2, Math.round(fast * 0.4));\n\t\t\t\t\tif (style === \"pill\")\n\t\t\t\t\t\tplates.push(\n\t\t\t\t\t\t\tbox(\n\t\t\t\t\t\t\t\t\"plate\",\n\t\t\t\t\t\t\t\tblockRect,\n\t\t\t\t\t\t\t\tfs * 0.5,\n\t\t\t\t\t\t\t\ttheme.color.surface,\n\t\t\t\t\t\t\t\ttheme.radius.pill,\n\t\t\t\t\t\t\t\t{ opacity: 0.92 },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\tconst marker = box(\n\t\t\t\t\t\t\"marker\",\n\t\t\t\t\t\trects[0] as Rect,\n\t\t\t\t\t\tpad,\n\t\t\t\t\t\taccent,\n\t\t\t\t\t\tstyle === \"pill\" ? theme.radius.pill : theme.radius.md,\n\t\t\t\t\t);\n\t\t\t\t\tmarker.animate(markerKeys(rects, wStart, pad, glide));\n\t\t\t\t\tplates.push(marker);\n\t\t\t\t\t// Text on the marker reads in the background colour when ink wouldn't.\n\t\t\t\t\tif ((contrastRatio(ink, accent) ?? 21) < 3)\n\t\t\t\t\t\tanimators.push(\n\t\t\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\t\t\"on-marker\",\n\t\t\t\t\t\t\t\t// Switch as the marker lands under the word.\n\t\t\t\t\t\t\t\twindowExpression(\n\t\t\t\t\t\t\t\t\t\"word\",\n\t\t\t\t\t\t\t\t\twStart.map((f) => f + Math.ceil(glide / 2)),\n\t\t\t\t\t\t\t\t\twEnd.map((f) => f + Math.ceil(glide / 2)),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t{ fillColor: theme.color.bg },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"pop\":\n\t\t\t\t\tanimators.push(\n\t\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\t\"pop\",\n\t\t\t\t\t\t\tscheduleExpression(\n\t\t\t\t\t\t\t\t\"word\",\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstarts: wStart,\n\t\t\t\t\t\t\t\t\tduration: Math.max(3, Math.round(fast * 0.9)),\n\t\t\t\t\t\t\t\t\tease: \"back.out(2.2)\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"enter\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{ opacity: 0, scale: 0.72, y: fs * 0.12 },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bold\":\n\t\t\t\tcase \"outline\":\n\t\t\t\t\tanimators.push(\n\t\t\t\t\t\tglyphAnimator(\"active\", windowExpression(\"word\", wStart, wEnd), {\n\t\t\t\t\t\t\tfillColor: accent,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"gradient\": {\n\t\t\t\t\t// Active word: accent → accent2 across its letters.\n\t\t\t\t\tconst chars = layout.text.length;\n\t\t\t\t\tconst starts = new Array<number>(chars).fill(-1);\n\t\t\t\t\tconst ends = new Array<number>(chars).fill(-1);\n\t\t\t\t\tconst along = new Array<number>(chars).fill(0);\n\t\t\t\t\tlayout.words.forEach((w, k) => {\n\t\t\t\t\t\tfor (let c = w.from; c < w.to; c++) {\n\t\t\t\t\t\t\tstarts[c] = wStart[k] as number;\n\t\t\t\t\t\t\tends[c] = wEnd[k] as number;\n\t\t\t\t\t\t\talong[c] =\n\t\t\t\t\t\t\t\tw.to - w.from > 1 ? (c - w.from) / (w.to - w.from - 1) : 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tanimators.push(\n\t\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\t\"active\",\n\t\t\t\t\t\t\twindowExpression(\n\t\t\t\t\t\t\t\t\"char\",\n\t\t\t\t\t\t\t\trendererChars(layout.text, starts),\n\t\t\t\t\t\t\t\trendererChars(layout.text, ends),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfillColor: accent,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\t\"active-end\",\n\t\t\t\t\t\t\twindowExpression(\n\t\t\t\t\t\t\t\t\"char\",\n\t\t\t\t\t\t\t\trendererChars(layout.text, starts),\n\t\t\t\t\t\t\t\trendererChars(layout.text, ends),\n\t\t\t\t\t\t\t\trendererChars(layout.text, along),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{ fillColor: theme.color.accent2 },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (props.plate && (style === \"clean\" || style === \"karaoke\"))\n\t\t\t\tplates.unshift(\n\t\t\t\t\tbox(\n\t\t\t\t\t\t\"plate\",\n\t\t\t\t\t\tblockRect,\n\t\t\t\t\t\tfs * 0.35,\n\t\t\t\t\t\t\"rgba(0,0,0,0.55)\",\n\t\t\t\t\t\ttheme.radius.md,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\tif (props.pulse) {\n\t\t\t\t// The phrase's slice of the voice level, indexed by its local frame.\n\t\t\t\tconst level = props.pulse.level.slice(start, end);\n\t\t\t\tanimators.push(\n\t\t\t\t\tglyphAnimator(\"pulse\", pulseExpression(wStart, wEnd, level), {\n\t\t\t\t\t\tscale: 1 + props.pulseAmount,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (animators.length > 0) node.animators = animators as never;\n\t\t\tconst text = host.text(layout.text, node);\n\t\t\t// A heavy outline around the glyphs, never over them.\n\t\t\tif (style === \"outline\")\n\t\t\t\thost.style(text, {\n\t\t\t\t\tstroke: { size: fs * 0.08, position: \"outside\", color: \"#000000\" },\n\t\t\t\t});\n\t\t\tconst holder = host.box({\n\t\t\t\tid: ctx.partId(`phrase-${p}-box`),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: safe.x,\n\t\t\t\ty,\n\t\t\t\twidth: safe.width,\n\t\t\t\theight: m.height,\n\t\t\t\tstartFrame: start,\n\t\t\t\tdurationFrames: end - start,\n\t\t\t\tchildren: [...plates, text],\n\t\t\t});\n\t\t\tholder.animate(\n\t\t\t\tphraseMotion(style, p, end - start, fs, fast, { x: safe.x, y }),\n\t\t\t);\n\t\t\tparts[`phrase-${p}`] = holder;\n\t\t\tchildren.push(holder);\n\n\t\t\tphraseCues.push({\n\t\t\t\tframe: start,\n\t\t\t\tend,\n\t\t\t\ttext: layout.text.replace(/\\n/g, \" \"),\n\t\t\t});\n\t\t\tlayout.words.forEach((w, k) => {\n\t\t\t\twordCues.push({\n\t\t\t\t\tframe: start + (wStart[k] as number),\n\t\t\t\t\tend: start + (wEnd[k] as number),\n\t\t\t\t\tindex: w.index,\n\t\t\t\t\ttext: (words[w.index] as Word).text,\n\t\t\t\t\temphasised: emphasised[w.index] ?? false,\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\n\t\tif (props.behind) {\n\t\t\tconst cut = host.cutout(props.behind.video, {\n\t\t\t\tid: ctx.partId(\"behind\"),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: W,\n\t\t\t\theight: H,\n\t\t\t\tfit: \"cover\",\n\t\t\t\tmode: props.behind.mode,\n\t\t\t});\n\t\t\tparts.behind = cut;\n\t\t\tchildren.push(cut);\n\t\t}\n\n\t\tconst last = phraseCues[phraseCues.length - 1]?.end ?? 1;\n\t\treturn {\n\t\t\tchildren,\n\t\t\twidth: W,\n\t\t\theight: H,\n\t\t\tduration: last,\n\t\t\torigin: [0, 0],\n\t\t\tcues: { words: wordCues, phrases: phraseCues } as CaptionsCues,\n\t\t\tparts,\n\t\t};\n\t},\n});\n\n/** A phrase's text and each word's character range in it. */\nfunction phraseLayout(phrase: Phrase, words: readonly Word[]) {\n\tconst out: Array<{ index: number; from: number; to: number }> = [];\n\tlet text = \"\";\n\tphrase.lines.forEach((line, l) => {\n\t\tif (l > 0) text += \"\\n\";\n\t\tline.forEach((index, k) => {\n\t\t\tif (k > 0) text += \" \";\n\t\t\tconst from = text.length;\n\t\t\ttext += (words[index] as Word).text;\n\t\t\tout.push({ index, from, to: text.length });\n\t\t});\n\t});\n\treturn { text, words: out };\n}\n\n/** A marker that glides onto each word, arriving on the frame it is spoken. */\nfunction markerKeys(\n\trects: readonly Rect[],\n\tstarts: readonly number[],\n\tpad: number,\n\tglide: number,\n): LayerAnimation {\n\tconst keyed = (value: (r: Rect) => number) => {\n\t\tconst keys: KeyframeTuple[] = [[0, value(rects[0] as Rect)]];\n\t\trects.forEach((r, k) => {\n\t\t\tconst prev = rects[k - 1];\n\t\t\tif (!prev) return;\n\t\t\tconst at = starts[k] as number;\n\t\t\tconst last = (keys[keys.length - 1] as KeyframeTuple)[0];\n\t\t\t// Leave on the word's first frame and land a few frames in: the\n\t\t\t// marker never runs ahead of the voice.\n\t\t\tkeys.push([Math.max(last + 1, at), value(prev)]);\n\t\t\tkeys.push([Math.max(last + 2, at + glide), value(r), \"power3.out\"]);\n\t\t});\n\t\treturn keys;\n\t};\n\treturn LayerAnimation.create()\n\t\t.keys(\n\t\t\t\"x\",\n\t\t\tkeyed((r) => r.x - pad),\n\t\t)\n\t\t.keys(\n\t\t\t\"y\",\n\t\t\tkeyed((r) => r.y - pad * 0.5),\n\t\t)\n\t\t.keys(\n\t\t\t\"width\",\n\t\t\tkeyed((r) => r.width + pad * 2),\n\t\t)\n\t\t.keys(\n\t\t\t\"height\",\n\t\t\tkeyed((r) => r.height + pad),\n\t\t)\n\t\t.keys(\"opacity\", [\n\t\t\t[0, 0],\n\t\t\t[starts[0] ?? 0, 1, \"hold\"],\n\t\t]);\n}\n\n/** How a phrase arrives and leaves. `bold` alternates sides; the rest fade up. */\nfunction phraseMotion(\n\tstyle: CaptionStyle,\n\tindex: number,\n\tlength: number,\n\tfs: number,\n\tfast: number,\n\tat: { x: number; y: number },\n): LayerAnimation {\n\tconst anim = LayerAnimation.create();\n\tconst inF = Math.min(\n\t\tMath.max(2, Math.round(fast * 0.6)),\n\t\tMath.floor(length / 2),\n\t);\n\tconst out = Math.min(\n\t\tMath.max(2, Math.round(fast * 0.5)),\n\t\tMath.floor(length / 3),\n\t);\n\tanim.keys(\"opacity\", [\n\t\t[0, 0],\n\t\t[inF, 1, \"power2.out\"],\n\t\t[length - out, 1],\n\t\t[length, 0, \"power2.in\"],\n\t]);\n\tif (style === \"bold\") {\n\t\tconst side = index % 2 === 0 ? -1 : 1;\n\t\tanim.keys(\"x\", [\n\t\t\t[0, at.x + side * fs * 0.6],\n\t\t\t[inF + 2, at.x, \"back.out(1.8)\"],\n\t\t]);\n\t\tanim.keys(\"scale\", [\n\t\t\t[0, 0.86],\n\t\t\t[inF + 2, 1, \"back.out(1.8)\"],\n\t\t]);\n\t} else if (style !== \"pop\") {\n\t\tanim.keys(\"y\", [\n\t\t\t[0, at.y + fs * 0.14],\n\t\t\t[inF, at.y, \"power3.out\"],\n\t\t]);\n\t}\n\treturn anim;\n}\n\nfunction stripUndefined<T extends object>(o: T): Partial<T> {\n\treturn Object.fromEntries(\n\t\tObject.entries(o).filter(([, v]) => v !== undefined),\n\t) as Partial<T>;\n}\n\n/** The active word's weight is the voice level on that frame (0 elsewhere). */\nfunction pulseExpression(\n\tstarts: readonly number[],\n\tends: readonly number[],\n\tlevel: readonly number[],\n): string {\n\tconst list = (v: readonly number[]) =>\n\t\t`[${v.map((x) => Math.round(x * 1000) / 1000).join(\",\")}]`;\n\treturn `((S,E,L)=>{const s=S[wordIndex];if(s===undefined||frame<s||frame>=E[wordIndex])return 0;return L[floor(frame)]??0;})(${list(starts)},${list(ends)},${list(level)})`;\n}\n\n/**\n * Word-timed captions from words or a transcript (SRT, WebVTT, word JSON) in\n * eight published styles, highlighting each word on its exact frame, optionally\n * tucked behind the speaker.\n */\nexport const Captions = /* @__PURE__ */ bindComponent(captions);\n"
|
|
26
|
+
"content": "/**\n * Captions: word-timed captions for talking-head and social video, in the\n * styles people publish, with exact word highlighting. Each phrase is one\n * text node; per-word colour, pops and markers are GPU glyph animators and\n * keyed plates, timed to the word's own frame.\n *\n * Coordinates are the frame's: the root covers the video (default\n * 1920 × 1080 × theme scale, or the given width / height) from 0, 0.\n */\nimport {\n\ttype KeyframeTuple,\n\tLayerAnimation,\n\ttype LayoutNode,\n\ttype TextNode,\n} from \"framefields\";\nimport {\n\ttype Animatable,\n\taudioLevelsSchema,\n\tbindComponent,\n\tconstantExpression,\n\tcontrastRatio,\n\tdefineComponent,\n\ttype GlyphAnimator,\n\tglyphAnimator,\n\ttype MeasureOptions,\n\trangeRects,\n\ttype Rect,\n\trendererChars,\n\tscheduleExpression,\n\tstepExpression,\n\tthemeFrames,\n\twindowExpression,\n\tz,\n} from \"framefields/components\";\nimport { parseTranscript, type Word } from \"./parse.js\";\nimport { buildPhrases, type GroupOptions, type Phrase } from \"./phrases.js\";\n\nexport type { Word } from \"./parse.js\";\n\nexport const CAPTION_STYLES = [\n\t\"clean\",\n\t\"karaoke\",\n\t\"highlight\",\n\t\"pop\",\n\t\"bold\",\n\t\"outline\",\n\t\"pill\",\n\t\"gradient\",\n] as const;\nexport type CaptionStyle = (typeof CAPTION_STYLES)[number];\n\nexport interface CaptionsCues {\n\t[name: string]: ReadonlyArray<{ frame: number }>;\n\t/** Each word, from its first frame to its last. */\n\twords: ReadonlyArray<{\n\t\tframe: number;\n\t\tend: number;\n\t\tindex: number;\n\t\ttext: string;\n\t\temphasised: boolean;\n\t}>;\n\t/** Each phrase on screen. */\n\tphrases: ReadonlyArray<{ frame: number; end: number; text: string }>;\n}\n\n/** Per-style grouping and placement defaults. */\nconst GROUP: Record<CaptionStyle, Partial<GroupOptions>> = {\n\tclean: { maxChars: 36, maxLines: 2 },\n\tkaraoke: {},\n\thighlight: {},\n\tpop: { maxChars: 22 },\n\tbold: { maxChars: 14, maxWords: 2, maxLines: 1 },\n\toutline: { maxChars: 22 },\n\tpill: { maxChars: 24, maxLines: 1 },\n\tgradient: {},\n};\n\nconst BASE_GROUP: GroupOptions = {\n\tmaxChars: 28,\n\tmaxWords: 12,\n\tmaxLines: 2,\n\tbreakOnPunctuation: true,\n\tminGap: 0.6,\n};\n\n/** The area captions stay inside: clear of edges and, in portrait, platform overlays. */\nfunction safeArea(\n\tW: number,\n\tH: number,\n\tarea: \"portrait\" | \"landscape\" | Rect | undefined,\n): Rect {\n\tif (area && typeof area === \"object\") return area;\n\tconst portrait = area ? area === \"portrait\" : H > W;\n\treturn portrait\n\t\t? { x: W * 0.08, y: H * 0.12, width: W * 0.84, height: H * 0.66 }\n\t\t: { x: W * 0.1, y: H * 0.08, width: W * 0.8, height: H * 0.82 };\n}\n\nexport const captions = /* @__PURE__ */ defineComponent({\n\tname: \"Captions\",\n\tprops: () => ({\n\t\t/** `{ text, start, end }` in seconds. Or `transcript`. */\n\t\twords: z\n\t\t\t.array(\n\t\t\t\tz\n\t\t\t\t\t.object({\n\t\t\t\t\t\ttext: z.string(),\n\t\t\t\t\t\tstart: z.number().min(0),\n\t\t\t\t\t\tend: z.number().min(0),\n\t\t\t\t\t})\n\t\t\t\t\t.strict(),\n\t\t\t)\n\t\t\t.optional(),\n\t\t/** SRT, WebVTT, or word-level JSON (`[{ word | text, start, end }]`). */\n\t\ttranscript: z.string().optional(),\n\t\tstyle: z.enum(CAPTION_STYLES).default(\"karaoke\"),\n\t\t/** The voice's levels (`analyzeAudio`): the active word swells with it. Frame 0 = the component's first frame. */\n\t\tpulse: z.union([audioLevelsSchema(), z.literal(false)]).default(false),\n\t\t/** Scale added at full level. */\n\t\tpulseAmount: z.number().min(0).default(0.12),\n\t\tgroup: z\n\t\t\t.object({\n\t\t\t\tmaxChars: z.number().int().positive().optional(),\n\t\t\t\tmaxWords: z.number().int().positive().optional(),\n\t\t\t\tmaxLines: z.number().int().positive().optional(),\n\t\t\t\tbreakOnPunctuation: z.boolean().optional(),\n\t\t\t\tminGap: z.number().min(0).optional(),\n\t\t\t})\n\t\t\t.strict()\n\t\t\t.default({}),\n\t\t/** Words or patterns in the accent colour, or a test per word. */\n\t\temphasis: z\n\t\t\t.union([\n\t\t\t\tz.array(z.union([z.string(), z.instanceof(RegExp)])),\n\t\t\t\tz.custom<(word: string, index: number) => boolean>(\n\t\t\t\t\t(v) => typeof v === \"function\",\n\t\t\t\t),\n\t\t\t])\n\t\t\t.default([]),\n\t\tcase: z.enum([\"as-is\", \"upper\", \"lower\"]).optional(),\n\t\tplacement: z\n\t\t\t.object({\n\t\t\t\tanchor: z.enum([\"bottom\", \"center\", \"top\"]).optional(),\n\t\t\t\tsafeArea: z\n\t\t\t\t\t.union([\n\t\t\t\t\t\tz.enum([\"portrait\", \"landscape\"]),\n\t\t\t\t\t\tz.object({\n\t\t\t\t\t\t\tx: z.number(),\n\t\t\t\t\t\t\ty: z.number(),\n\t\t\t\t\t\t\twidth: z.number(),\n\t\t\t\t\t\t\theight: z.number(),\n\t\t\t\t\t\t}),\n\t\t\t\t\t])\n\t\t\t\t\t.optional(),\n\t\t\t\t/** Extra px from the anchor edge, toward the centre. */\n\t\t\t\toffset: z.number().default(0),\n\t\t\t})\n\t\t\t.strict()\n\t\t\t.default({ offset: 0 }),\n\t\t/** A clip whose person / subject is cut out and drawn over the captions. */\n\t\tbehind: z\n\t\t\t.object({\n\t\t\t\tvideo: z.string(),\n\t\t\t\tmode: z.enum([\"person\", \"subject\"]).default(\"person\"),\n\t\t\t})\n\t\t\t.strict()\n\t\t\t.optional(),\n\t\t/** A backing plate behind each phrase (`clean`, `karaoke`). */\n\t\tplate: z.boolean().default(false),\n\t\tfontFamily: z.string().optional(),\n\t\t/** Default: fits `group.maxChars` across the safe area. */\n\t\tfontSize: z.number().positive().optional(),\n\t\tfontWeight: z.union([z.number(), z.string()]).optional(),\n\t\tfill: z.string().optional(),\n\t\taccent: z.string().optional(),\n\t}),\n\tdefaults: { enter: false, exit: false },\n\tbuild(ctx, props) {\n\t\tconst { host, theme, fps } = ctx;\n\t\tconst W = ctx.requested.width ?? 1920 * theme.scale;\n\t\tconst H = ctx.requested.height ?? 1080 * theme.scale;\n\t\tconst style = props.style;\n\n\t\t// ── Words ─────────────────────────────────────────────────────────\n\t\tlet words: Word[];\n\t\tif (props.words) words = props.words.map((w) => ({ ...w }));\n\t\telse if (props.transcript) words = parseTranscript(props.transcript);\n\t\telse throw new Error(\"Captions: give `words` or a `transcript`.\");\n\t\tconst casing = props.case ?? (style === \"bold\" ? \"upper\" : \"as-is\");\n\t\tif (casing !== \"as-is\")\n\t\t\twords = words.map((w) => ({\n\t\t\t\t...w,\n\t\t\t\ttext: casing === \"upper\" ? w.text.toUpperCase() : w.text.toLowerCase(),\n\t\t\t}));\n\t\tconst emphasised = words.map((w, i) =>\n\t\t\ttypeof props.emphasis === \"function\"\n\t\t\t\t? props.emphasis(w.text, i)\n\t\t\t\t: props.emphasis.some((p) =>\n\t\t\t\t\t\ttypeof p === \"string\"\n\t\t\t\t\t\t\t? w.text.toLowerCase().replace(/[^\\p{L}\\p{N}%]/gu, \"\") ===\n\t\t\t\t\t\t\t\tp.toLowerCase()\n\t\t\t\t\t\t\t: new RegExp(p.source, p.flags.replace(\"g\", \"\")).test(w.text),\n\t\t\t\t\t),\n\t\t);\n\n\t\t// ── Layout ────────────────────────────────────────────────────────\n\t\tconst group: GroupOptions = {\n\t\t\t...BASE_GROUP,\n\t\t\t...GROUP[style],\n\t\t\t...stripUndefined(props.group),\n\t\t};\n\t\tconst safe = safeArea(W, H, props.placement.safeArea);\n\t\tconst fontFamily = props.fontFamily ?? theme.font.display;\n\t\tconst weight = props.fontWeight ?? (style === \"clean\" ? undefined : 800);\n\t\tconst measureAt = (text: string, size: number, width?: number) =>\n\t\t\thost.measure(text, {\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: size,\n\t\t\t\tfontWeight: weight,\n\t\t\t\t...(width !== undefined && { width, align: \"center\" as const }),\n\t\t\t} satisfies MeasureOptions);\n\t\t// Auto size: a line of `maxChars` average glyphs spans the safe width.\n\t\tconst sample = \"nonsense words\".repeat(4).slice(0, group.maxChars);\n\t\tconst fs =\n\t\t\tprops.fontSize ??\n\t\t\tMath.min(\n\t\t\t\t(safe.width / Math.max(1, measureAt(sample, 100).width)) * 100 * 0.94,\n\t\t\t\tsafe.height / (group.maxLines * 1.6),\n\t\t\t);\n\t\tconst fits = (line: string) => measureAt(line, fs).width <= safe.width;\n\t\tconst phrases = buildPhrases(words, group, fits);\n\n\t\tconst ink = props.fill ?? theme.color.ink;\n\t\tconst accent = props.accent ?? theme.color.accent;\n\t\tconst anchor =\n\t\t\tprops.placement.anchor ?? (style === \"bold\" ? \"center\" : \"bottom\");\n\t\tconst frameOf = (seconds: number) => Math.round(seconds * fps);\n\t\tif (props.pulse && Math.abs(props.pulse.fps - fps) > 1e-6)\n\t\t\tthrow new Error(\n\t\t\t\t`Captions: \\`pulse\\` was analysed at ${props.pulse.fps} fps but the component runs at ${fps}. Analyse with { fps: ${fps} }.`,\n\t\t\t);\n\t\tconst fast = themeFrames(theme, \"fast\", fps);\n\t\tconst children: LayoutNode[] = [];\n\t\tconst parts: Record<string, Animatable<LayoutNode>> = {};\n\t\tconst wordCues: Array<CaptionsCues[\"words\"][number]> = [];\n\t\tconst phraseCues: Array<CaptionsCues[\"phrases\"][number]> = [];\n\n\t\tphrases.forEach((phrase, p) => {\n\t\t\tconst layout = phraseLayout(phrase, words);\n\t\t\tconst m = measureAt(layout.text, fs, safe.width);\n\t\t\tconst options: MeasureOptions = {\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: fs,\n\t\t\t\tfontWeight: weight,\n\t\t\t\twidth: safe.width,\n\t\t\t\talign: \"center\",\n\t\t\t};\n\t\t\tconst y =\n\t\t\t\tanchor === \"bottom\"\n\t\t\t\t\t? safe.y + safe.height - m.height - props.placement.offset\n\t\t\t\t\t: anchor === \"top\"\n\t\t\t\t\t\t? safe.y + props.placement.offset\n\t\t\t\t\t\t: safe.y + (safe.height - m.height) / 2;\n\t\t\tconst start = frameOf(phrase.start);\n\t\t\tconst end = Math.max(start + 1, frameOf(phrase.end));\n\t\t\t// Word windows, local to the phrase (its box starts at `start`).\n\t\t\tconst wStart = layout.words.map(\n\t\t\t\t(w) => frameOf((words[w.index] as Word).start) - start,\n\t\t\t);\n\t\t\tconst wEnd = layout.words.map((w, k) =>\n\t\t\t\tk + 1 < layout.words.length\n\t\t\t\t\t? (wStart[k + 1] as number)\n\t\t\t\t\t: Math.max(\n\t\t\t\t\t\t\t(wStart[k] as number) + 1,\n\t\t\t\t\t\t\tframeOf((words[w.index] as Word).end) - start,\n\t\t\t\t\t\t),\n\t\t\t);\n\t\t\tconst rects = layout.words.map(\n\t\t\t\t(w) => rangeRects(m, w.from, w.to, options)[0] as Rect,\n\t\t\t);\n\t\t\tconst animators: GlyphAnimator[] = [];\n\n\t\t\tconst emphasisWeights = layout.words.map((w) =>\n\t\t\t\temphasised[w.index] ? 1 : 0,\n\t\t\t);\n\t\t\tif (emphasisWeights.some(Boolean))\n\t\t\t\tanimators.push(\n\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\"emphasis\",\n\t\t\t\t\t\tconstantExpression(\"word\", emphasisWeights),\n\t\t\t\t\t\t{ fillColor: accent },\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\tconst node: Partial<TextNode> = {\n\t\t\t\tid: ctx.partId(`phrase-${p}`),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: safe.width,\n\t\t\t\talign: \"center\",\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: fs,\n\t\t\t\tfill: ink,\n\t\t\t\t...(weight !== undefined && { fontWeight: weight }),\n\t\t\t};\n\t\t\tconst plates: LayoutNode[] = [];\n\t\t\tconst box = (\n\t\t\t\tid: string,\n\t\t\t\tr: Rect,\n\t\t\t\tpad: number,\n\t\t\t\tbackground: string,\n\t\t\t\tradius: number,\n\t\t\t\textra = {},\n\t\t\t) =>\n\t\t\t\thost.box({\n\t\t\t\t\tid: ctx.partId(`phrase-${p}-${id}`),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: r.x - pad,\n\t\t\t\t\ty: r.y - pad * 0.5,\n\t\t\t\t\twidth: r.width + pad * 2,\n\t\t\t\t\theight: r.height + pad,\n\t\t\t\t\tbackground,\n\t\t\t\t\tborderRadius: radius,\n\t\t\t\t\t...extra,\n\t\t\t\t});\n\t\t\tconst blockRect: Rect = {\n\t\t\t\tx: Math.min(...rects.map((r) => r.x)),\n\t\t\t\ty: 0,\n\t\t\t\twidth:\n\t\t\t\t\tMath.max(...rects.map((r) => r.x + r.width)) -\n\t\t\t\t\tMath.min(...rects.map((r) => r.x)),\n\t\t\t\theight: m.height,\n\t\t\t};\n\n\t\t\tswitch (style) {\n\t\t\t\tcase \"clean\":\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"karaoke\":\n\t\t\t\t\t// Spoken words turn from muted to ink on their exact frame.\n\t\t\t\t\tanimators.push(\n\t\t\t\t\t\tglyphAnimator(\"spoken\", stepExpression(\"word\", wStart), {\n\t\t\t\t\t\t\tfillColor: theme.color.muted,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"highlight\":\n\t\t\t\tcase \"pill\": {\n\t\t\t\t\tconst pad = fs * (style === \"pill\" ? 0.22 : 0.16);\n\t\t\t\t\tconst glide = Math.max(2, Math.round(fast * 0.4));\n\t\t\t\t\tif (style === \"pill\")\n\t\t\t\t\t\tplates.push(\n\t\t\t\t\t\t\tbox(\n\t\t\t\t\t\t\t\t\"plate\",\n\t\t\t\t\t\t\t\tblockRect,\n\t\t\t\t\t\t\t\tfs * 0.5,\n\t\t\t\t\t\t\t\ttheme.color.surface,\n\t\t\t\t\t\t\t\ttheme.radius.pill,\n\t\t\t\t\t\t\t\t{ opacity: 0.92 },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\tconst marker = box(\n\t\t\t\t\t\t\"marker\",\n\t\t\t\t\t\trects[0] as Rect,\n\t\t\t\t\t\tpad,\n\t\t\t\t\t\taccent,\n\t\t\t\t\t\tstyle === \"pill\" ? theme.radius.pill : theme.radius.md,\n\t\t\t\t\t);\n\t\t\t\t\tmarker.animate(markerKeys(rects, wStart, pad, glide));\n\t\t\t\t\tplates.push(marker);\n\t\t\t\t\t// Text on the marker must read against it: every word in the\n\t\t\t\t\t// background colour when ink wouldn't, else only the emphasised\n\t\t\t\t\t// ones (accent on the accent marker), in ink.\n\t\t\t\t\tconst inkReads = (contrastRatio(ink, accent) ?? 21) >= 3;\n\t\t\t\t\tif (!inkReads || emphasisWeights.some(Boolean))\n\t\t\t\t\t\tanimators.push(\n\t\t\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\t\t\"on-marker\",\n\t\t\t\t\t\t\t\t// Switch as the marker lands under the word.\n\t\t\t\t\t\t\t\twindowExpression(\n\t\t\t\t\t\t\t\t\t\"word\",\n\t\t\t\t\t\t\t\t\twStart.map((f) => f + Math.ceil(glide / 2)),\n\t\t\t\t\t\t\t\t\twEnd.map((f) => f + Math.ceil(glide / 2)),\n\t\t\t\t\t\t\t\t\tinkReads ? emphasisWeights : undefined,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\t{ fillColor: inkReads ? ink : theme.color.bg },\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t\tcase \"pop\":\n\t\t\t\t\tanimators.push(\n\t\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\t\"pop\",\n\t\t\t\t\t\t\tscheduleExpression(\n\t\t\t\t\t\t\t\t\"word\",\n\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\tstarts: wStart,\n\t\t\t\t\t\t\t\t\tduration: Math.max(3, Math.round(fast * 0.9)),\n\t\t\t\t\t\t\t\t\tease: \"back.out(2.2)\",\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\"enter\",\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{ opacity: 0, scale: 0.72, y: fs * 0.12 },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"bold\":\n\t\t\t\tcase \"outline\":\n\t\t\t\t\tanimators.push(\n\t\t\t\t\t\tglyphAnimator(\"active\", windowExpression(\"word\", wStart, wEnd), {\n\t\t\t\t\t\t\tfillColor: accent,\n\t\t\t\t\t\t}),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"gradient\": {\n\t\t\t\t\t// Active word: accent → accent2 across its letters.\n\t\t\t\t\tconst chars = layout.text.length;\n\t\t\t\t\tconst starts = new Array<number>(chars).fill(-1);\n\t\t\t\t\tconst ends = new Array<number>(chars).fill(-1);\n\t\t\t\t\tconst along = new Array<number>(chars).fill(0);\n\t\t\t\t\tlayout.words.forEach((w, k) => {\n\t\t\t\t\t\tfor (let c = w.from; c < w.to; c++) {\n\t\t\t\t\t\t\tstarts[c] = wStart[k] as number;\n\t\t\t\t\t\t\tends[c] = wEnd[k] as number;\n\t\t\t\t\t\t\talong[c] =\n\t\t\t\t\t\t\t\tw.to - w.from > 1 ? (c - w.from) / (w.to - w.from - 1) : 0;\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tanimators.push(\n\t\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\t\"active\",\n\t\t\t\t\t\t\twindowExpression(\n\t\t\t\t\t\t\t\t\"char\",\n\t\t\t\t\t\t\t\trendererChars(layout.text, starts),\n\t\t\t\t\t\t\t\trendererChars(layout.text, ends),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tfillColor: accent,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t),\n\t\t\t\t\t\tglyphAnimator(\n\t\t\t\t\t\t\t\"active-end\",\n\t\t\t\t\t\t\twindowExpression(\n\t\t\t\t\t\t\t\t\"char\",\n\t\t\t\t\t\t\t\trendererChars(layout.text, starts),\n\t\t\t\t\t\t\t\trendererChars(layout.text, ends),\n\t\t\t\t\t\t\t\trendererChars(layout.text, along),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t{ fillColor: theme.color.accent2 },\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (props.plate && (style === \"clean\" || style === \"karaoke\"))\n\t\t\t\tplates.unshift(\n\t\t\t\t\tbox(\n\t\t\t\t\t\t\"plate\",\n\t\t\t\t\t\tblockRect,\n\t\t\t\t\t\tfs * 0.35,\n\t\t\t\t\t\t\"rgba(0,0,0,0.55)\",\n\t\t\t\t\t\ttheme.radius.md,\n\t\t\t\t\t),\n\t\t\t\t);\n\n\t\t\tif (props.pulse) {\n\t\t\t\t// The phrase's slice of the voice level, indexed by its local frame.\n\t\t\t\tconst level = props.pulse.level.slice(start, end);\n\t\t\t\tanimators.push(\n\t\t\t\t\tglyphAnimator(\"pulse\", pulseExpression(wStart, wEnd, level), {\n\t\t\t\t\t\tscale: 1 + props.pulseAmount,\n\t\t\t\t\t}),\n\t\t\t\t);\n\t\t\t}\n\t\t\tif (animators.length > 0) node.animators = animators as never;\n\t\t\tconst text = host.text(layout.text, node);\n\t\t\t// A heavy outline around the glyphs, never over them.\n\t\t\tif (style === \"outline\")\n\t\t\t\thost.style(text, {\n\t\t\t\t\tstroke: { size: fs * 0.08, position: \"outside\", color: \"#000000\" },\n\t\t\t\t});\n\t\t\tconst holder = host.box({\n\t\t\t\tid: ctx.partId(`phrase-${p}-box`),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: safe.x,\n\t\t\t\ty,\n\t\t\t\twidth: safe.width,\n\t\t\t\theight: m.height,\n\t\t\t\tstartFrame: start,\n\t\t\t\tdurationFrames: end - start,\n\t\t\t\tchildren: [...plates, text],\n\t\t\t});\n\t\t\tholder.animate(\n\t\t\t\tphraseMotion(style, p, end - start, fs, fast, { x: safe.x, y }),\n\t\t\t);\n\t\t\tparts[`phrase-${p}`] = holder;\n\t\t\tchildren.push(holder);\n\n\t\t\tphraseCues.push({\n\t\t\t\tframe: start,\n\t\t\t\tend,\n\t\t\t\ttext: layout.text.replace(/\\n/g, \" \"),\n\t\t\t});\n\t\t\tlayout.words.forEach((w, k) => {\n\t\t\t\twordCues.push({\n\t\t\t\t\tframe: start + (wStart[k] as number),\n\t\t\t\t\tend: start + (wEnd[k] as number),\n\t\t\t\t\tindex: w.index,\n\t\t\t\t\ttext: (words[w.index] as Word).text,\n\t\t\t\t\temphasised: emphasised[w.index] ?? false,\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\n\t\tif (props.behind) {\n\t\t\tconst cut = host.cutout(props.behind.video, {\n\t\t\t\tid: ctx.partId(\"behind\"),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: W,\n\t\t\t\theight: H,\n\t\t\t\tfit: \"cover\",\n\t\t\t\tmode: props.behind.mode,\n\t\t\t});\n\t\t\tparts.behind = cut;\n\t\t\tchildren.push(cut);\n\t\t}\n\n\t\tconst last = phraseCues[phraseCues.length - 1]?.end ?? 1;\n\t\treturn {\n\t\t\tchildren,\n\t\t\twidth: W,\n\t\t\theight: H,\n\t\t\tduration: last,\n\t\t\torigin: [0, 0],\n\t\t\tcues: { words: wordCues, phrases: phraseCues } as CaptionsCues,\n\t\t\tparts,\n\t\t};\n\t},\n});\n\n/** A phrase's text and each word's character range in it. */\nfunction phraseLayout(phrase: Phrase, words: readonly Word[]) {\n\tconst out: Array<{ index: number; from: number; to: number }> = [];\n\tlet text = \"\";\n\tphrase.lines.forEach((line, l) => {\n\t\tif (l > 0) text += \"\\n\";\n\t\tline.forEach((index, k) => {\n\t\t\tif (k > 0) text += \" \";\n\t\t\tconst from = text.length;\n\t\t\ttext += (words[index] as Word).text;\n\t\t\tout.push({ index, from, to: text.length });\n\t\t});\n\t});\n\treturn { text, words: out };\n}\n\n/** A marker that glides onto each word, arriving on the frame it is spoken. */\nfunction markerKeys(\n\trects: readonly Rect[],\n\tstarts: readonly number[],\n\tpad: number,\n\tglide: number,\n): LayerAnimation {\n\tconst keyed = (value: (r: Rect) => number) => {\n\t\tconst keys: KeyframeTuple[] = [[0, value(rects[0] as Rect)]];\n\t\trects.forEach((r, k) => {\n\t\t\tconst prev = rects[k - 1];\n\t\t\tif (!prev) return;\n\t\t\tconst at = starts[k] as number;\n\t\t\tconst last = (keys[keys.length - 1] as KeyframeTuple)[0];\n\t\t\t// Leave on the word's first frame and land a few frames in: the\n\t\t\t// marker never runs ahead of the voice.\n\t\t\tkeys.push([Math.max(last + 1, at), value(prev)]);\n\t\t\tkeys.push([Math.max(last + 2, at + glide), value(r), \"power3.out\"]);\n\t\t});\n\t\treturn keys;\n\t};\n\treturn LayerAnimation.create()\n\t\t.keys(\n\t\t\t\"x\",\n\t\t\tkeyed((r) => r.x - pad),\n\t\t)\n\t\t.keys(\n\t\t\t\"y\",\n\t\t\tkeyed((r) => r.y - pad * 0.5),\n\t\t)\n\t\t.keys(\n\t\t\t\"width\",\n\t\t\tkeyed((r) => r.width + pad * 2),\n\t\t)\n\t\t.keys(\n\t\t\t\"height\",\n\t\t\tkeyed((r) => r.height + pad),\n\t\t)\n\t\t.keys(\"opacity\", [\n\t\t\t[0, 0],\n\t\t\t[starts[0] ?? 0, 1, \"hold\"],\n\t\t]);\n}\n\n/** How a phrase arrives and leaves. `bold` alternates sides; the rest fade up. */\nfunction phraseMotion(\n\tstyle: CaptionStyle,\n\tindex: number,\n\tlength: number,\n\tfs: number,\n\tfast: number,\n\tat: { x: number; y: number },\n): LayerAnimation {\n\tconst anim = LayerAnimation.create();\n\tconst inF = Math.min(\n\t\tMath.max(2, Math.round(fast * 0.6)),\n\t\tMath.floor(length / 2),\n\t);\n\tconst out = Math.min(\n\t\tMath.max(2, Math.round(fast * 0.5)),\n\t\tMath.floor(length / 3),\n\t);\n\tanim.keys(\"opacity\", [\n\t\t[0, 0],\n\t\t[inF, 1, \"power2.out\"],\n\t\t[length - out, 1],\n\t\t[length, 0, \"power2.in\"],\n\t]);\n\tif (style === \"bold\") {\n\t\tconst side = index % 2 === 0 ? -1 : 1;\n\t\tanim.keys(\"x\", [\n\t\t\t[0, at.x + side * fs * 0.6],\n\t\t\t[inF + 2, at.x, \"back.out(1.8)\"],\n\t\t]);\n\t\tanim.keys(\"scale\", [\n\t\t\t[0, 0.86],\n\t\t\t[inF + 2, 1, \"back.out(1.8)\"],\n\t\t]);\n\t} else if (style !== \"pop\") {\n\t\tanim.keys(\"y\", [\n\t\t\t[0, at.y + fs * 0.14],\n\t\t\t[inF, at.y, \"power3.out\"],\n\t\t]);\n\t}\n\treturn anim;\n}\n\nfunction stripUndefined<T extends object>(o: T): Partial<T> {\n\treturn Object.fromEntries(\n\t\tObject.entries(o).filter(([, v]) => v !== undefined),\n\t) as Partial<T>;\n}\n\n/** The active word's weight is the voice level on that frame (0 elsewhere). */\nfunction pulseExpression(\n\tstarts: readonly number[],\n\tends: readonly number[],\n\tlevel: readonly number[],\n): string {\n\tconst list = (v: readonly number[]) =>\n\t\t`[${v.map((x) => Math.round(x * 1000) / 1000).join(\",\")}]`;\n\treturn `((S,E,L)=>{const s=S[wordIndex];if(s===undefined||frame<s||frame>=E[wordIndex])return 0;return L[floor(frame)]??0;})(${list(starts)},${list(ends)},${list(level)})`;\n}\n\n/**\n * Word-timed captions from words or a transcript (SRT, WebVTT, word JSON) in\n * eight published styles, highlighting each word on its exact frame, optionally\n * tucked behind the speaker.\n */\nexport const Captions = /* @__PURE__ */ bindComponent(captions);\n"
|
|
27
27
|
},
|
|
28
28
|
{
|
|
29
29
|
"path": "captions/parse.ts",
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
"files": [
|
|
29
29
|
{
|
|
30
30
|
"path": "lower-third/index.ts",
|
|
31
|
-
"content": "/**\n * LowerThird: the name-and-role bar of interviews and talks. Plates are\n * measured to their text, placed in the safe area's bottom corner, slide in\n * through their own box, and leave on their own after `hold`.\n *\n * Coordinates are the frame's: the root covers the video (default\n * 1920 × 1080 × theme scale, or the given width / height) from 0, 0.\n */\nimport {\n\ttype KeyframeTuple,\n\tLayerAnimation,\n\ttype LayoutNode,\n} from \"framefields\";\nimport {\n\ttype Animatable,\n\tbindComponent,\n\tdefineComponent,\n\ttype Rect,\n\tthemeFrames,\n\tz,\n} from \"framefields/components\";\n\nexport const LOWER_THIRD_LAYOUTS = [\"bar\", \"stacked\", \"tag\", \"line\"] as const;\nexport type LowerThirdLayout = (typeof LOWER_THIRD_LAYOUTS)[number];\n\nexport interface LowerThirdCues {\n\t[name: string]: ReadonlyArray<{ frame: number }>;\n\t/** Fully on screen. */\n\tin: ReadonlyArray<{ frame: number }>;\n\t/** Starts to leave (empty with `stay`). */\n\tout: ReadonlyArray<{ frame: number }>;\n}\n\nfunction safeArea(\n\tW: number,\n\tH: number,\n\tarea: \"portrait\" | \"landscape\" | Rect | undefined,\n): Rect {\n\tif (area && typeof area === \"object\") return area;\n\tconst portrait = area ? area === \"portrait\" : H > W;\n\treturn portrait\n\t\t? { x: W * 0.08, y: H * 0.1, width: W * 0.84, height: H * 0.66 }\n\t\t: { x: W * 0.06, y: H * 0.08, width: W * 0.88, height: H * 0.82 };\n}\n\nexport const lowerThird = /* @__PURE__ */ defineComponent({\n\tname: \"LowerThird\",\n\tprops: () => ({\n\t\tname: z.string().min(1),\n\t\trole: z.string().optional(),\n\t\tlayout: z.enum(LOWER_THIRD_LAYOUTS).default(\"bar\"),\n\t\tside: z.enum([\"left\", \"right\"]).default(\"left\"),\n\t\tsafeArea: z\n\t\t\t.union([\n\t\t\t\tz.enum([\"portrait\", \"landscape\"]),\n\t\t\t\tz.object({\n\t\t\t\t\tx: z.number(),\n\t\t\t\t\ty: z.number(),\n\t\t\t\t\twidth: z.number(),\n\t\t\t\t\theight: z.number(),\n\t\t\t\t}),\n\t\t\t])\n\t\t\t.optional(),\n\t\t/** Stay on screen instead of leaving after `hold`. */\n\t\tstay: z.boolean().default(false),\n\t\taccent: z.string().optional(),\n\t\tplate: z.string().optional(),\n\t\tnameSize: z.number().positive().optional(),\n\t\tfontFamily: z.string().optional(),\n\t}),\n\tdefaults: { enter: false, exit: false, hold: 90 },\n\tbuild(ctx, props) {\n\t\tconst { host, theme, fps } = ctx;\n\t\tconst s = theme.scale;\n\t\tconst W = ctx.requested.width ?? 1920 * s;\n\t\tconst H = ctx.requested.height ?? 1080 * s;\n\t\tconst safe = safeArea(W, H, props.safeArea);\n\t\tconst fontFamily = props.fontFamily ?? theme.font.display;\n\t\tconst nameSize = props.nameSize ?? 44 * s;\n\t\tconst roleSize = nameSize * 0.62;\n\t\tconst accent = props.accent ?? theme.color.accent;\n\t\tconst plateColor = props.plate ?? theme.color.surface;\n\t\tconst right = props.side === \"right\";\n\t\tconst base = themeFrames(theme, \"base\", fps);\n\t\tconst fast = themeFrames(theme, \"fast\", fps);\n\t\tconst enter = theme.motion.ease.enter;\n\n\t\tconst nameM = host.measure(props.name, {\n\t\t\tfontFamily,\n\t\t\tfontSize: nameSize,\n\t\t\tfontWeight: 700,\n\t\t});\n\t\tconst roleM = props.role\n\t\t\t? host.measure(props.role, {\n\t\t\t\t\tfontFamily,\n\t\t\t\t\tfontSize: roleSize,\n\t\t\t\t\tfontWeight: 500,\n\t\t\t\t})\n\t\t\t: null;\n\t\tconst padX = nameSize * 0.55;\n\t\tconst padY = nameSize * 0.36;\n\t\tconst children: LayoutNode[] = [];\n\t\tconst parts: Record<string, Animatable<LayoutNode>> = {};\n\t\t/** Keys per node and prop, merged into one track each at the end. */\n\t\tconst tracks = new Map<\n\t\t\tAnimatable<LayoutNode>,\n\t\t\tMap<string, KeyframeTuple[]>\n\t\t>();\n\t\tconst key = (\n\t\t\tnode: Animatable<LayoutNode>,\n\t\t\tprop: string,\n\t\t\tkeys: KeyframeTuple[],\n\t\t) => {\n\t\t\tconst byProp = tracks.get(node) ?? new Map<string, KeyframeTuple[]>();\n\t\t\ttracks.set(node, byProp);\n\t\t\tbyProp.set(prop, [...(byProp.get(prop) ?? []), ...keys]);\n\t\t};\n\n\t\tconst text = (\n\t\t\tid: string,\n\t\t\tcontent: string,\n\t\t\tsize: number,\n\t\t\tweight: number,\n\t\t\tfill: string,\n\t\t\tx: number,\n\t\t\ty: number,\n\t\t) =>\n\t\t\thost.text(content, {\n\t\t\t\tid: ctx.partId(id),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: size,\n\t\t\t\tfontWeight: weight,\n\t\t\t\tfill,\n\t\t\t});\n\n\t\t/**\n\t\t * A plate that slides in through its own box: the background travels\n\t\t * in from the side, its text rises a beat later. Returns the timing.\n\t\t */\n\t\tconst plate = (\n\t\t\tid: string,\n\t\t\trect: Rect,\n\t\t\tbackground: string,\n\t\t\tradius: number,\n\t\t\tcontent: Array<Animatable<LayoutNode>>,\n\t\t\tat: number,\n\t\t) => {\n\t\t\tconst fill = host.box({\n\t\t\t\tid: ctx.partId(`${id}-fill`),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: rect.width,\n\t\t\t\theight: rect.height,\n\t\t\t\tbackground,\n\t\t\t\tborderRadius: radius,\n\t\t\t});\n\t\t\tconst travel = rect.width * (right ? 1 : -1);\n\t\t\tconst slide = Math.round(base * 1.1);\n\t\t\tkey(fill, \"x\", [\n\t\t\t\t[at, travel],\n\t\t\t\t[at + slide, 0, enter],\n\t\t\t]);\n\t\t\tconst textAt = at + Math.round(fast * 0.7);\n\t\t\tfor (const node of content) {\n\t\t\t\tconst y0 = (node as unknown as { y: number }).y;\n\t\t\t\tkey(node, \"y\", [\n\t\t\t\t\t[textAt, y0 + rect.height * 0.5],\n\t\t\t\t\t[textAt + base, y0, enter],\n\t\t\t\t]);\n\t\t\t\tkey(node, \"opacity\", [\n\t\t\t\t\t[textAt, 0],\n\t\t\t\t\t[textAt + Math.round(base * 0.6), 1, \"power2.out\"],\n\t\t\t\t]);\n\t\t\t}\n\t\t\tconst clip = host.box({\n\t\t\t\tid: ctx.partId(id),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\t...rect,\n\t\t\t\toverflow: \"hidden\",\n\t\t\t\tborderRadius: radius,\n\t\t\t\tchildren: [fill, ...content],\n\t\t\t});\n\t\t\tchildren.push(clip);\n\t\t\tparts[id] = clip;\n\t\t\treturn {\n\t\t\t\tclip,\n\t\t\t\tfill,\n\t\t\t\tcontent,\n\t\t\t\ttravel,\n\t\t\t\tsettled: Math.max(at + slide, textAt + base),\n\t\t\t};\n\t\t};\n\n\t\t// ── Layouts ───────────────────────────────────────────────────────\n\t\tconst bottom = safe.y + safe.height;\n\t\tconst xFor = (width: number) =>\n\t\t\tright ? safe.x + safe.width - width : safe.x;\n\t\tconst plates: Array<ReturnType<typeof plate>> = [];\n\t\tlet accentLine: Animatable<LayoutNode> | null = null;\n\t\tconst lineW = Math.max(3, nameSize * 0.09);\n\n\t\tswitch (props.layout) {\n\t\t\tcase \"bar\": {\n\t\t\t\tconst edge = Math.max(4, nameSize * 0.14);\n\t\t\t\tconst innerW = Math.max(nameM.width, roleM?.width ?? 0);\n\t\t\t\tconst h = padY * 2 + nameM.height + (roleM ? roleM.height * 0.95 : 0);\n\t\t\t\tconst w = innerW + padX * 2 + edge;\n\t\t\t\tconst rect = { x: xFor(w), y: bottom - h, width: w, height: h };\n\t\t\t\tconst tx = right ? padX : padX + edge;\n\t\t\t\tconst content = [\n\t\t\t\t\ttext(\"name\", props.name, nameSize, 700, theme.color.ink, tx, padY),\n\t\t\t\t];\n\t\t\t\tif (props.role && roleM)\n\t\t\t\t\tcontent.push(\n\t\t\t\t\t\ttext(\n\t\t\t\t\t\t\t\"role\",\n\t\t\t\t\t\t\tprops.role,\n\t\t\t\t\t\t\troleSize,\n\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\ttheme.color.muted,\n\t\t\t\t\t\t\ttx,\n\t\t\t\t\t\t\tpadY + nameM.height * 0.95,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\tconst p = plate(\"plate\", rect, plateColor, theme.radius.sm, content, 0);\n\t\t\t\tconst strip = host.box({\n\t\t\t\t\tid: ctx.partId(\"edge\"),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: right ? rect.x + rect.width - edge : rect.x,\n\t\t\t\t\ty: rect.y + rect.height,\n\t\t\t\t\twidth: edge,\n\t\t\t\t\theight: 0,\n\t\t\t\t\tbackground: accent,\n\t\t\t\t\tborderRadius: theme.radius.sm,\n\t\t\t\t});\n\t\t\t\tconst grow = Math.round(base * 0.8);\n\t\t\t\tkey(strip, \"height\", [\n\t\t\t\t\t[0, 0],\n\t\t\t\t\t[grow, rect.height, enter],\n\t\t\t\t]);\n\t\t\t\tkey(strip, \"y\", [\n\t\t\t\t\t[0, rect.y + rect.height],\n\t\t\t\t\t[grow, rect.y, enter],\n\t\t\t\t]);\n\t\t\t\tchildren.push(strip);\n\t\t\t\tparts.edge = strip;\n\t\t\t\tplates.push(p);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"stacked\": {\n\t\t\t\tconst nameW = nameM.width + padX * 2;\n\t\t\t\tconst nameH = nameM.height + padY * 2;\n\t\t\t\tconst roleH = roleM ? roleM.height + padY * 1.2 : 0;\n\t\t\t\tconst roleW = roleM ? roleM.width + padX * 1.6 : 0;\n\t\t\t\tconst inset = nameSize * 0.4;\n\t\t\t\tconst total = nameH + (roleM ? roleH * 0.9 : 0);\n\t\t\t\tconst top = bottom - total;\n\t\t\t\tplates.push(\n\t\t\t\t\tplate(\n\t\t\t\t\t\t\"name-plate\",\n\t\t\t\t\t\t{ x: xFor(nameW), y: top, width: nameW, height: nameH },\n\t\t\t\t\t\tplateColor,\n\t\t\t\t\t\ttheme.radius.sm,\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\ttext(\n\t\t\t\t\t\t\t\t\"name\",\n\t\t\t\t\t\t\t\tprops.name,\n\t\t\t\t\t\t\t\tnameSize,\n\t\t\t\t\t\t\t\t700,\n\t\t\t\t\t\t\t\ttheme.color.ink,\n\t\t\t\t\t\t\t\tpadX,\n\t\t\t\t\t\t\t\tpadY,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t\t0,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tif (props.role && roleM)\n\t\t\t\t\tplates.push(\n\t\t\t\t\t\tplate(\n\t\t\t\t\t\t\t\"role-plate\",\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tx: right ? xFor(roleW) - inset : xFor(roleW) + inset,\n\t\t\t\t\t\t\t\ty: top + nameH - roleH * 0.1,\n\t\t\t\t\t\t\t\twidth: roleW,\n\t\t\t\t\t\t\t\theight: roleH,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\taccent,\n\t\t\t\t\t\t\ttheme.radius.sm,\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\ttext(\n\t\t\t\t\t\t\t\t\t\"role\",\n\t\t\t\t\t\t\t\t\tprops.role,\n\t\t\t\t\t\t\t\t\troleSize,\n\t\t\t\t\t\t\t\t\t600,\n\t\t\t\t\t\t\t\t\ttheme.color.bg,\n\t\t\t\t\t\t\t\t\tpadX * 0.8,\n\t\t\t\t\t\t\t\t\tpadY * 0.6,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tMath.round(fast * 0.8),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"tag\": {\n\t\t\t\t// One line, one node: bold name, a dot, the role a size down.\n\t\t\t\tconst sep = \" · \";\n\t\t\t\tconst sepW = props.role\n\t\t\t\t\t? host.measure(sep, { fontFamily, fontSize: roleSize }).width\n\t\t\t\t\t: 0;\n\t\t\t\tconst h = nameM.height + padY * 1.4;\n\t\t\t\tconst w = nameM.width + (roleM ? sepW + roleM.width : 0) + h;\n\t\t\t\tconst line = host.text(\n\t\t\t\t\tprops.role ? `${props.name}${sep}${props.role}` : props.name,\n\t\t\t\t\t{\n\t\t\t\t\t\tid: ctx.partId(\"name\"),\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\tx: h / 2,\n\t\t\t\t\t\ty: padY * 0.7,\n\t\t\t\t\t\tfontFamily,\n\t\t\t\t\t\tfontSize: nameSize,\n\t\t\t\t\t\tfill: theme.color.ink,\n\t\t\t\t\t\tspans: [\n\t\t\t\t\t\t\t{ text: props.name, fontWeight: 700, fill: theme.color.ink },\n\t\t\t\t\t\t\t...(props.role\n\t\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttext: `${sep}${props.role}`,\n\t\t\t\t\t\t\t\t\t\t\tfontSize: roleSize,\n\t\t\t\t\t\t\t\t\t\t\tfontWeight: 500,\n\t\t\t\t\t\t\t\t\t\t\tfill: theme.color.muted,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tplates.push(\n\t\t\t\t\tplate(\n\t\t\t\t\t\t\"tag\",\n\t\t\t\t\t\t{ x: xFor(w), y: bottom - h, width: w, height: h },\n\t\t\t\t\t\tplateColor,\n\t\t\t\t\t\ttheme.radius.pill,\n\t\t\t\t\t\t[line],\n\t\t\t\t\t\t0,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"line\": {\n\t\t\t\tconst innerW = Math.max(nameM.width, roleM?.width ?? 0);\n\t\t\t\tconst h = nameM.height + (roleM ? roleM.height : 0);\n\t\t\t\tconst gap = nameSize * 0.4;\n\t\t\t\tconst w = innerW + gap + lineW;\n\t\t\t\tconst x = xFor(w);\n\t\t\t\tconst lineX = right ? x + w - lineW : x;\n\t\t\t\tconst tx = right ? 0 : lineW + gap;\n\t\t\t\taccentLine = host.box({\n\t\t\t\t\tid: ctx.partId(\"line\"),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: lineX,\n\t\t\t\t\ty: bottom,\n\t\t\t\t\twidth: lineW,\n\t\t\t\t\theight: 0,\n\t\t\t\t\tbackground: accent,\n\t\t\t\t\tborderRadius: lineW / 2,\n\t\t\t\t});\n\t\t\t\tconst grow = Math.round(base * 1.1);\n\t\t\t\tkey(accentLine, \"height\", [\n\t\t\t\t\t[0, 0],\n\t\t\t\t\t[grow, h, enter],\n\t\t\t\t]);\n\t\t\t\tkey(accentLine, \"y\", [\n\t\t\t\t\t[0, bottom],\n\t\t\t\t\t[grow, bottom - h, enter],\n\t\t\t\t]);\n\t\t\t\tchildren.push(accentLine);\n\t\t\t\tparts.line = accentLine;\n\t\t\t\tconst content = [\n\t\t\t\t\ttext(\"name\", props.name, nameSize, 700, theme.color.ink, tx, 0),\n\t\t\t\t];\n\t\t\t\tif (props.role && roleM)\n\t\t\t\t\tcontent.push(\n\t\t\t\t\t\ttext(\n\t\t\t\t\t\t\t\"role\",\n\t\t\t\t\t\t\tprops.role,\n\t\t\t\t\t\t\troleSize,\n\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\ttheme.color.muted,\n\t\t\t\t\t\t\ttx,\n\t\t\t\t\t\t\tnameM.height,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t// No plate: the text rises through a clear box.\n\t\t\t\tplates.push(\n\t\t\t\t\tplate(\n\t\t\t\t\t\t\"text\",\n\t\t\t\t\t\t{ x, y: bottom - h, width: w, height: h },\n\t\t\t\t\t\t\"rgba(0,0,0,0)\",\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\tMath.round(fast * 0.6),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tconst settled = Math.max(...plates.map((p) => p.settled));\n\t\t// ── Out: text first, then the plates slide back the way they came ──\n\t\tlet afterHold = 0;\n\t\tconst outAt = settled + ctx.hold;\n\t\tif (!props.stay) {\n\t\t\tconst fadeOut = Math.round(fast * 0.9);\n\t\t\tconst slide = Math.round(base * 0.9);\n\t\t\tplates.forEach((p, k) => {\n\t\t\t\tconst at = outAt + k * Math.round(fast * 0.4);\n\t\t\t\tfor (const node of p.content)\n\t\t\t\t\tkey(node, \"opacity\", [\n\t\t\t\t\t\t[at, 1],\n\t\t\t\t\t\t[at + fadeOut, 0, \"power2.in\"],\n\t\t\t\t\t]);\n\t\t\t\tkey(p.fill, \"x\", [\n\t\t\t\t\t[at + fadeOut - 2, 0],\n\t\t\t\t\t[at + fadeOut - 2 + slide, p.travel, theme.motion.ease.exit],\n\t\t\t\t]);\n\t\t\t\tafterHold = Math.max(afterHold, at + fadeOut - 2 + slide - outAt);\n\t\t\t});\n\t\t\tfor (const node of [accentLine, parts.edge])\n\t\t\t\tif (node)\n\t\t\t\t\tkey(node, \"opacity\", [\n\t\t\t\t\t\t[outAt, 1],\n\t\t\t\t\t\t[outAt + afterHold, 0, \"power2.in\"],\n\t\t\t\t\t]);\n\t\t}\n\t\tfor (const [node, byProp] of tracks) {\n\t\t\tconst a = LayerAnimation.create();\n\t\t\tfor (const [prop, keys] of byProp)\n\t\t\t\ta.keys(\n\t\t\t\t\tprop as \"x\",\n\t\t\t\t\t[...keys].sort((x, y) => x[0] - y[0]),\n\t\t\t\t);\n\t\t\tnode.animate(a);\n\t\t}\n\n\t\treturn {\n\t\t\tchildren,\n\t\t\twidth: W,\n\t\t\theight: H,\n\t\t\tduration: settled,\n\t\t\topenEnded: props.stay,\n\t\t\tafterHold,\n\t\t\torigin: [0, 0],\n\t\t\tcues: {\n\t\t\t\tin: [{ frame: settled }],\n\t\t\t\tout: props.stay ? [] : [{ frame: outAt }],\n\t\t\t} as LowerThirdCues,\n\t\t\tparts,\n\t\t};\n\t},\n});\n\n/**\n * The name-and-role bar of interviews and talks: measured plates in the safe\n * area that slide in and leave on their own.\n */\nexport const LowerThird = /* @__PURE__ */ bindComponent(lowerThird);\n"
|
|
31
|
+
"content": "/**\n * LowerThird: the name-and-role bar of interviews and talks. Plates are\n * measured to their text, placed in the safe area's bottom corner, slide in\n * through their own box, and leave on their own after `hold`.\n *\n * Coordinates are the frame's: the root covers the video (default\n * 1920 × 1080 × theme scale, or the given width / height) from 0, 0.\n */\nimport {\n\ttype KeyframeTuple,\n\tLayerAnimation,\n\ttype LayoutNode,\n} from \"framefields\";\nimport {\n\ttype Animatable,\n\tbindComponent,\n\tdefineComponent,\n\ttype Rect,\n\tthemeFrames,\n\tz,\n} from \"framefields/components\";\n\nexport const LOWER_THIRD_LAYOUTS = [\"bar\", \"stacked\", \"tag\", \"line\"] as const;\n\n/** Cap height as a share of the font size (Inter and most UI sans faces). */\nconst CAP_HEIGHT = 0.72;\nexport type LowerThirdLayout = (typeof LOWER_THIRD_LAYOUTS)[number];\n\nexport interface LowerThirdCues {\n\t[name: string]: ReadonlyArray<{ frame: number }>;\n\t/** Fully on screen. */\n\tin: ReadonlyArray<{ frame: number }>;\n\t/** Starts to leave (empty with `stay`). */\n\tout: ReadonlyArray<{ frame: number }>;\n}\n\nfunction safeArea(\n\tW: number,\n\tH: number,\n\tarea: \"portrait\" | \"landscape\" | Rect | undefined,\n): Rect {\n\tif (area && typeof area === \"object\") return area;\n\tconst portrait = area ? area === \"portrait\" : H > W;\n\treturn portrait\n\t\t? { x: W * 0.08, y: H * 0.1, width: W * 0.84, height: H * 0.66 }\n\t\t: { x: W * 0.06, y: H * 0.08, width: W * 0.88, height: H * 0.82 };\n}\n\nexport const lowerThird = /* @__PURE__ */ defineComponent({\n\tname: \"LowerThird\",\n\tprops: () => ({\n\t\tname: z.string().min(1),\n\t\trole: z.string().optional(),\n\t\tlayout: z.enum(LOWER_THIRD_LAYOUTS).default(\"bar\"),\n\t\tside: z.enum([\"left\", \"right\"]).default(\"left\"),\n\t\tsafeArea: z\n\t\t\t.union([\n\t\t\t\tz.enum([\"portrait\", \"landscape\"]),\n\t\t\t\tz.object({\n\t\t\t\t\tx: z.number(),\n\t\t\t\t\ty: z.number(),\n\t\t\t\t\twidth: z.number(),\n\t\t\t\t\theight: z.number(),\n\t\t\t\t}),\n\t\t\t])\n\t\t\t.optional(),\n\t\t/** Stay on screen instead of leaving after `hold`. */\n\t\tstay: z.boolean().default(false),\n\t\taccent: z.string().optional(),\n\t\tplate: z.string().optional(),\n\t\tnameSize: z.number().positive().optional(),\n\t\tfontFamily: z.string().optional(),\n\t}),\n\tdefaults: { enter: false, exit: false, hold: 90 },\n\tbuild(ctx, props) {\n\t\tconst { host, theme, fps } = ctx;\n\t\tconst s = theme.scale;\n\t\tconst W = ctx.requested.width ?? 1920 * s;\n\t\tconst H = ctx.requested.height ?? 1080 * s;\n\t\tconst safe = safeArea(W, H, props.safeArea);\n\t\tconst fontFamily = props.fontFamily ?? theme.font.display;\n\t\tconst nameSize = props.nameSize ?? 44 * s;\n\t\tconst roleSize = nameSize * 0.62;\n\t\tconst accent = props.accent ?? theme.color.accent;\n\t\tconst plateColor = props.plate ?? theme.color.surface;\n\t\tconst right = props.side === \"right\";\n\t\tconst base = themeFrames(theme, \"base\", fps);\n\t\tconst fast = themeFrames(theme, \"fast\", fps);\n\t\tconst enter = theme.motion.ease.enter;\n\n\t\tconst nameM = host.measure(props.name, {\n\t\t\tfontFamily,\n\t\t\tfontSize: nameSize,\n\t\t\tfontWeight: 700,\n\t\t});\n\t\tconst roleM = props.role\n\t\t\t? host.measure(props.role, {\n\t\t\t\t\tfontFamily,\n\t\t\t\t\tfontSize: roleSize,\n\t\t\t\t\tfontWeight: 500,\n\t\t\t\t})\n\t\t\t: null;\n\t\tconst padX = nameSize * 0.55;\n\t\tconst padY = nameSize * 0.36;\n\t\tconst children: LayoutNode[] = [];\n\t\tconst parts: Record<string, Animatable<LayoutNode>> = {};\n\t\t/** Keys per node and prop, merged into one track each at the end. */\n\t\tconst tracks = new Map<\n\t\t\tAnimatable<LayoutNode>,\n\t\t\tMap<string, KeyframeTuple[]>\n\t\t>();\n\t\tconst key = (\n\t\t\tnode: Animatable<LayoutNode>,\n\t\t\tprop: string,\n\t\t\tkeys: KeyframeTuple[],\n\t\t) => {\n\t\t\tconst byProp = tracks.get(node) ?? new Map<string, KeyframeTuple[]>();\n\t\t\ttracks.set(node, byProp);\n\t\t\tbyProp.set(prop, [...(byProp.get(prop) ?? []), ...keys]);\n\t\t};\n\n\t\tconst text = (\n\t\t\tid: string,\n\t\t\tcontent: string,\n\t\t\tsize: number,\n\t\t\tweight: number,\n\t\t\tfill: string,\n\t\t\tx: number,\n\t\t\ty: number,\n\t\t) =>\n\t\t\thost.text(content, {\n\t\t\t\tid: ctx.partId(id),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx,\n\t\t\t\ty,\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: size,\n\t\t\t\tfontWeight: weight,\n\t\t\t\tfill,\n\t\t\t});\n\n\t\t/**\n\t\t * A plate that slides in through its own box: the background travels\n\t\t * in from the side, its text rises a beat later. Returns the timing.\n\t\t */\n\t\tconst plate = (\n\t\t\tid: string,\n\t\t\trect: Rect,\n\t\t\tbackground: string,\n\t\t\tradius: number,\n\t\t\tcontent: Array<Animatable<LayoutNode>>,\n\t\t\tat: number,\n\t\t) => {\n\t\t\tconst fill = host.box({\n\t\t\t\tid: ctx.partId(`${id}-fill`),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: rect.width,\n\t\t\t\theight: rect.height,\n\t\t\t\tbackground,\n\t\t\t\tborderRadius: radius,\n\t\t\t});\n\t\t\tconst travel = rect.width * (right ? 1 : -1);\n\t\t\tconst slide = Math.round(base * 1.1);\n\t\t\tkey(fill, \"x\", [\n\t\t\t\t[at, travel],\n\t\t\t\t[at + slide, 0, enter],\n\t\t\t]);\n\t\t\tconst textAt = at + Math.round(fast * 0.7);\n\t\t\tfor (const node of content) {\n\t\t\t\tconst y0 = (node as unknown as { y: number }).y;\n\t\t\t\tkey(node, \"y\", [\n\t\t\t\t\t[textAt, y0 + rect.height * 0.5],\n\t\t\t\t\t[textAt + base, y0, enter],\n\t\t\t\t]);\n\t\t\t\tkey(node, \"opacity\", [\n\t\t\t\t\t[textAt, 0],\n\t\t\t\t\t[textAt + Math.round(base * 0.6), 1, \"power2.out\"],\n\t\t\t\t]);\n\t\t\t}\n\t\t\tconst clip = host.box({\n\t\t\t\tid: ctx.partId(id),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\t...rect,\n\t\t\t\toverflow: \"hidden\",\n\t\t\t\tborderRadius: radius,\n\t\t\t\tchildren: [fill, ...content],\n\t\t\t});\n\t\t\tchildren.push(clip);\n\t\t\tparts[id] = clip;\n\t\t\treturn {\n\t\t\t\tclip,\n\t\t\t\tfill,\n\t\t\t\tcontent,\n\t\t\t\ttravel,\n\t\t\t\tsettled: Math.max(at + slide, textAt + base),\n\t\t\t};\n\t\t};\n\n\t\t// ── Layouts ───────────────────────────────────────────────────────\n\t\tconst bottom = safe.y + safe.height;\n\t\tconst xFor = (width: number) =>\n\t\t\tright ? safe.x + safe.width - width : safe.x;\n\t\tconst plates: Array<ReturnType<typeof plate>> = [];\n\t\tlet accentLine: Animatable<LayoutNode> | null = null;\n\t\tconst lineW = Math.max(3, nameSize * 0.09);\n\n\t\tswitch (props.layout) {\n\t\t\tcase \"bar\": {\n\t\t\t\tconst edge = Math.max(4, nameSize * 0.14);\n\t\t\t\tconst innerW = Math.max(nameM.width, roleM?.width ?? 0);\n\t\t\t\tconst h = padY * 2 + nameM.height + (roleM ? roleM.height * 0.95 : 0);\n\t\t\t\tconst w = innerW + padX * 2 + edge;\n\t\t\t\tconst rect = { x: xFor(w), y: bottom - h, width: w, height: h };\n\t\t\t\tconst tx = right ? padX : padX + edge;\n\t\t\t\tconst content = [\n\t\t\t\t\ttext(\"name\", props.name, nameSize, 700, theme.color.ink, tx, padY),\n\t\t\t\t];\n\t\t\t\tif (props.role && roleM)\n\t\t\t\t\tcontent.push(\n\t\t\t\t\t\ttext(\n\t\t\t\t\t\t\t\"role\",\n\t\t\t\t\t\t\tprops.role,\n\t\t\t\t\t\t\troleSize,\n\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\ttheme.color.muted,\n\t\t\t\t\t\t\ttx,\n\t\t\t\t\t\t\tpadY + nameM.height * 0.95,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\tconst p = plate(\"plate\", rect, plateColor, theme.radius.sm, content, 0);\n\t\t\t\tconst strip = host.box({\n\t\t\t\t\tid: ctx.partId(\"edge\"),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: right ? rect.x + rect.width - edge : rect.x,\n\t\t\t\t\ty: rect.y + rect.height,\n\t\t\t\t\twidth: edge,\n\t\t\t\t\theight: 0,\n\t\t\t\t\tbackground: accent,\n\t\t\t\t\tborderRadius: theme.radius.sm,\n\t\t\t\t});\n\t\t\t\tconst grow = Math.round(base * 0.8);\n\t\t\t\tkey(strip, \"height\", [\n\t\t\t\t\t[0, 0],\n\t\t\t\t\t[grow, rect.height, enter],\n\t\t\t\t]);\n\t\t\t\tkey(strip, \"y\", [\n\t\t\t\t\t[0, rect.y + rect.height],\n\t\t\t\t\t[grow, rect.y, enter],\n\t\t\t\t]);\n\t\t\t\tchildren.push(strip);\n\t\t\t\tparts.edge = strip;\n\t\t\t\tplates.push(p);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"stacked\": {\n\t\t\t\tconst nameW = nameM.width + padX * 2;\n\t\t\t\tconst nameH = nameM.height + padY * 2;\n\t\t\t\tconst roleH = roleM ? roleM.height + padY * 1.2 : 0;\n\t\t\t\tconst roleW = roleM ? roleM.width + padX * 1.6 : 0;\n\t\t\t\tconst inset = nameSize * 0.4;\n\t\t\t\tconst total = nameH + (roleM ? roleH * 0.9 : 0);\n\t\t\t\tconst top = bottom - total;\n\t\t\t\tplates.push(\n\t\t\t\t\tplate(\n\t\t\t\t\t\t\"name-plate\",\n\t\t\t\t\t\t{ x: xFor(nameW), y: top, width: nameW, height: nameH },\n\t\t\t\t\t\tplateColor,\n\t\t\t\t\t\ttheme.radius.sm,\n\t\t\t\t\t\t[\n\t\t\t\t\t\t\ttext(\n\t\t\t\t\t\t\t\t\"name\",\n\t\t\t\t\t\t\t\tprops.name,\n\t\t\t\t\t\t\t\tnameSize,\n\t\t\t\t\t\t\t\t700,\n\t\t\t\t\t\t\t\ttheme.color.ink,\n\t\t\t\t\t\t\t\tpadX,\n\t\t\t\t\t\t\t\tpadY,\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t],\n\t\t\t\t\t\t0,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tif (props.role && roleM)\n\t\t\t\t\tplates.push(\n\t\t\t\t\t\tplate(\n\t\t\t\t\t\t\t\"role-plate\",\n\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\tx: right ? xFor(roleW) - inset : xFor(roleW) + inset,\n\t\t\t\t\t\t\t\ty: top + nameH - roleH * 0.1,\n\t\t\t\t\t\t\t\twidth: roleW,\n\t\t\t\t\t\t\t\theight: roleH,\n\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\taccent,\n\t\t\t\t\t\t\ttheme.radius.sm,\n\t\t\t\t\t\t\t[\n\t\t\t\t\t\t\t\ttext(\n\t\t\t\t\t\t\t\t\t\"role\",\n\t\t\t\t\t\t\t\t\tprops.role,\n\t\t\t\t\t\t\t\t\troleSize,\n\t\t\t\t\t\t\t\t\t600,\n\t\t\t\t\t\t\t\t\ttheme.color.bg,\n\t\t\t\t\t\t\t\t\tpadX * 0.8,\n\t\t\t\t\t\t\t\t\tpadY * 0.6,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t],\n\t\t\t\t\t\t\tMath.round(fast * 0.8),\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"tag\": {\n\t\t\t\t// One line, one node: bold name, a dot, the role a size down.\n\t\t\t\tconst sep = \" · \";\n\t\t\t\tconst sepW = props.role\n\t\t\t\t\t? host.measure(sep, { fontFamily, fontSize: roleSize }).width\n\t\t\t\t\t: 0;\n\t\t\t\tconst h = nameM.height + padY * 1.4;\n\t\t\t\tconst w = nameM.width + (roleM ? sepW + roleM.width : 0) + h;\n\t\t\t\tconst line = host.text(\n\t\t\t\t\tprops.role ? `${props.name}${sep}${props.role}` : props.name,\n\t\t\t\t\t{\n\t\t\t\t\t\tid: ctx.partId(\"name\"),\n\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\tx: h / 2,\n\t\t\t\t\t\ty: padY * 0.7,\n\t\t\t\t\t\tfontFamily,\n\t\t\t\t\t\tfontSize: nameSize,\n\t\t\t\t\t\tfill: theme.color.ink,\n\t\t\t\t\t\tspans: [\n\t\t\t\t\t\t\t{ text: props.name, fontWeight: 700, fill: theme.color.ink },\n\t\t\t\t\t\t\t...(props.role\n\t\t\t\t\t\t\t\t? [\n\t\t\t\t\t\t\t\t\t\t{\n\t\t\t\t\t\t\t\t\t\t\ttext: `${sep}${props.role}`,\n\t\t\t\t\t\t\t\t\t\t\tfontSize: roleSize,\n\t\t\t\t\t\t\t\t\t\t\tfontWeight: 500,\n\t\t\t\t\t\t\t\t\t\t\tfill: theme.color.muted,\n\t\t\t\t\t\t\t\t\t\t\t// Centred on the name's capitals, not on its baseline: one\n\t\t\t\t\t\t\t\t\t\t\t// line in a pill reads as aligned when the caps share a middle.\n\t\t\t\t\t\t\t\t\t\t\tbaselineShift: -(nameSize - roleSize) * CAP_HEIGHT * 0.5,\n\t\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\t]\n\t\t\t\t\t\t\t\t: []),\n\t\t\t\t\t\t],\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tplates.push(\n\t\t\t\t\tplate(\n\t\t\t\t\t\t\"tag\",\n\t\t\t\t\t\t{ x: xFor(w), y: bottom - h, width: w, height: h },\n\t\t\t\t\t\tplateColor,\n\t\t\t\t\t\ttheme.radius.pill,\n\t\t\t\t\t\t[line],\n\t\t\t\t\t\t0,\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"line\": {\n\t\t\t\tconst innerW = Math.max(nameM.width, roleM?.width ?? 0);\n\t\t\t\tconst h = nameM.height + (roleM ? roleM.height : 0);\n\t\t\t\tconst gap = nameSize * 0.4;\n\t\t\t\tconst w = innerW + gap + lineW;\n\t\t\t\tconst x = xFor(w);\n\t\t\t\tconst lineX = right ? x + w - lineW : x;\n\t\t\t\tconst tx = right ? 0 : lineW + gap;\n\t\t\t\taccentLine = host.box({\n\t\t\t\t\tid: ctx.partId(\"line\"),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: lineX,\n\t\t\t\t\ty: bottom,\n\t\t\t\t\twidth: lineW,\n\t\t\t\t\theight: 0,\n\t\t\t\t\tbackground: accent,\n\t\t\t\t\tborderRadius: lineW / 2,\n\t\t\t\t});\n\t\t\t\tconst grow = Math.round(base * 1.1);\n\t\t\t\tkey(accentLine, \"height\", [\n\t\t\t\t\t[0, 0],\n\t\t\t\t\t[grow, h, enter],\n\t\t\t\t]);\n\t\t\t\tkey(accentLine, \"y\", [\n\t\t\t\t\t[0, bottom],\n\t\t\t\t\t[grow, bottom - h, enter],\n\t\t\t\t]);\n\t\t\t\tchildren.push(accentLine);\n\t\t\t\tparts.line = accentLine;\n\t\t\t\tconst content = [\n\t\t\t\t\ttext(\"name\", props.name, nameSize, 700, theme.color.ink, tx, 0),\n\t\t\t\t];\n\t\t\t\tif (props.role && roleM)\n\t\t\t\t\tcontent.push(\n\t\t\t\t\t\ttext(\n\t\t\t\t\t\t\t\"role\",\n\t\t\t\t\t\t\tprops.role,\n\t\t\t\t\t\t\troleSize,\n\t\t\t\t\t\t\t500,\n\t\t\t\t\t\t\ttheme.color.muted,\n\t\t\t\t\t\t\ttx,\n\t\t\t\t\t\t\tnameM.height,\n\t\t\t\t\t\t),\n\t\t\t\t\t);\n\t\t\t\t// No plate: the text rises through a clear box.\n\t\t\t\tplates.push(\n\t\t\t\t\tplate(\n\t\t\t\t\t\t\"text\",\n\t\t\t\t\t\t{ x, y: bottom - h, width: w, height: h },\n\t\t\t\t\t\t\"rgba(0,0,0,0)\",\n\t\t\t\t\t\t0,\n\t\t\t\t\t\tcontent,\n\t\t\t\t\t\tMath.round(fast * 0.6),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tconst settled = Math.max(...plates.map((p) => p.settled));\n\t\t// ── Out: text first, then the plates slide back the way they came ──\n\t\tlet afterHold = 0;\n\t\tconst outAt = settled + ctx.hold;\n\t\tif (!props.stay) {\n\t\t\tconst fadeOut = Math.round(fast * 0.9);\n\t\t\tconst slide = Math.round(base * 0.9);\n\t\t\tplates.forEach((p, k) => {\n\t\t\t\tconst at = outAt + k * Math.round(fast * 0.4);\n\t\t\t\tfor (const node of p.content)\n\t\t\t\t\tkey(node, \"opacity\", [\n\t\t\t\t\t\t[at, 1],\n\t\t\t\t\t\t[at + fadeOut, 0, \"power2.in\"],\n\t\t\t\t\t]);\n\t\t\t\tkey(p.fill, \"x\", [\n\t\t\t\t\t[at + fadeOut - 2, 0],\n\t\t\t\t\t[at + fadeOut - 2 + slide, p.travel, theme.motion.ease.exit],\n\t\t\t\t]);\n\t\t\t\tafterHold = Math.max(afterHold, at + fadeOut - 2 + slide - outAt);\n\t\t\t});\n\t\t\tfor (const node of [accentLine, parts.edge])\n\t\t\t\tif (node)\n\t\t\t\t\tkey(node, \"opacity\", [\n\t\t\t\t\t\t[outAt, 1],\n\t\t\t\t\t\t[outAt + afterHold, 0, \"power2.in\"],\n\t\t\t\t\t]);\n\t\t}\n\t\tfor (const [node, byProp] of tracks) {\n\t\t\tconst a = LayerAnimation.create();\n\t\t\tfor (const [prop, keys] of byProp)\n\t\t\t\ta.keys(\n\t\t\t\t\tprop as \"x\",\n\t\t\t\t\t[...keys].sort((x, y) => x[0] - y[0]),\n\t\t\t\t);\n\t\t\tnode.animate(a);\n\t\t}\n\n\t\treturn {\n\t\t\tchildren,\n\t\t\twidth: W,\n\t\t\theight: H,\n\t\t\tduration: settled,\n\t\t\topenEnded: props.stay,\n\t\t\tafterHold,\n\t\t\torigin: [0, 0],\n\t\t\tcues: {\n\t\t\t\tin: [{ frame: settled }],\n\t\t\t\tout: props.stay ? [] : [{ frame: outAt }],\n\t\t\t} as LowerThirdCues,\n\t\t\tparts,\n\t\t};\n\t},\n});\n\n/**\n * The name-and-role bar of interviews and talks: measured plates in the safe\n * area that slide in and leave on their own.\n */\nexport const LowerThird = /* @__PURE__ */ bindComponent(lowerThird);\n"
|
|
32
32
|
}
|
|
33
33
|
]
|
|
34
34
|
}
|
package/dist/registry/meter.json
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
{
|
|
31
31
|
"path": "meter/index.ts",
|
|
32
|
-
"content": "/**\n * Meter: progress and scores as a ring, an arc gauge, a bar, lit segments or\n * a star rating, filling on the same value timeline as its `Counter`.\n */\nimport {\n\ttype KeyframeTuple,\n\tLayerAnimation,\n\ttype LayoutNode,\n} from \"framefields\";\nimport {\n\ttype Animatable,\n\tbakeSamples,\n\tbind,\n\tbindComponent,\n\tdefineComponent,\n\tthemeFrames,\n\ttoPathData,\n\ttype Vec2,\n\tz,\n} from \"framefields/components\";\nimport { counter } from \"../counter/index.js\";\nimport { type ValueKey, valueAt } from \"../counter/plan.js\";\n\nexport const METER_KINDS = [\"ring\", \"arc\", \"bar\", \"segments\", \"stars\"] as const;\nexport type MeterKind = (typeof METER_KINDS)[number];\n\nexport interface MeterCues {\n\t[name: string]: ReadonlyArray<{ frame: number }>;\n\t/** The fill (and counter) is at rest. */\n\tsettled: ReadonlyArray<{ frame: number }>;\n\t/** Each segment or star completing (fills only; empties are not cued). */\n\tcrossings: ReadonlyArray<{ frame: number; segment: number }>;\n}\n\n/** Points along a circular arc, angles in degrees (0 = right, clockwise). */\nfunction arcPoints(c: Vec2, r: number, from: number, to: number): Vec2[] {\n\tconst steps = Math.max(8, Math.ceil(Math.abs(to - from) / 6));\n\treturn Array.from({ length: steps + 1 }, (_, k) => {\n\t\tconst a = ((from + ((to - from) * k) / steps) * Math.PI) / 180;\n\t\treturn [c[0] + Math.cos(a) * r, c[1] + Math.sin(a) * r] as Vec2;\n\t});\n}\n\n/** A five-point star inside a `size` box. */\nfunction starPath(size: number): string {\n\tconst c = size / 2;\n\tconst pts: Vec2[] = Array.from({ length: 10 }, (_, k) => {\n\t\tconst r = k % 2 === 0 ? size * 0.5 : size * 0.21;\n\t\tconst a = -Math.PI / 2 + (k * Math.PI) / 5;\n\t\treturn [c + Math.cos(a) * r, c + Math.sin(a) * r * 1.02 + size * 0.03];\n\t});\n\treturn toPathData(pts, true);\n}\n\nexport const meter = /* @__PURE__ */ defineComponent({\n\tname: \"Meter\",\n\tprops: () => ({\n\t\tvalue: z.number().optional(),\n\t\t/** Multi-step values: `[frame, value, ease?]`, frames ascending. */\n\t\tvalues: z\n\t\t\t.array(\n\t\t\t\tz.union([\n\t\t\t\t\tz.tuple([z.number().int().min(0), z.number()]),\n\t\t\t\t\tz.tuple([z.number().int().min(0), z.number(), z.string()]),\n\t\t\t\t]),\n\t\t\t)\n\t\t\t.min(1)\n\t\t\t.optional(),\n\t\tfrom: z.number().default(0),\n\t\t/** Default 100 (5 for stars). */\n\t\tmax: z.number().optional(),\n\t\tkind: z.enum(METER_KINDS).default(\"ring\"),\n\t\t/** Cells for `segments` (default 10), stars for `stars` (default `max`). */\n\t\tsegments: z.number().int().positive().optional(),\n\t\t/** Width (bar, segments, stars) or diameter (ring, arc). */\n\t\tsize: z.number().positive().optional(),\n\t\tthickness: z.number().positive().optional(),\n\t\tcounter: z.boolean().optional(),\n\t\tformat: z.string().default(\",d\"),\n\t\tsuffix: z.string().optional(),\n\t\tlabel: z.string().optional(),\n\t\tcolor: z.string().optional(),\n\t\ttrack: z.string().optional(),\n\t\tcountDuration: z.number().int().positive().optional(),\n\t\tease: z.string().optional(),\n\t\tfontFamily: z.string().optional(),\n\t}),\n\tdefaults: { enter: false, exit: false },\n\tbuild(ctx, props) {\n\t\tconst { host, theme, fps } = ctx;\n\t\tconst s = theme.scale;\n\t\tconst kind = props.kind;\n\t\tconst max = props.max ?? (kind === \"stars\" ? 5 : 100);\n\t\tconst from = props.from;\n\t\tconst ease = props.ease ?? theme.motion.ease.move;\n\t\tconst color = props.color ?? theme.color.accent;\n\t\tconst trackColor = props.track ?? theme.color.line;\n\t\tconst fontFamily = props.fontFamily ?? theme.font.display;\n\t\tif (props.value === undefined && !props.values)\n\t\t\tthrow new Error(\"Meter: give `value` (or `values`).\");\n\t\tconst keys: ValueKey[] = props.values\n\t\t\t? props.values.map((v) => [...v] as unknown as ValueKey)\n\t\t\t: [\n\t\t\t\t\t[0, from],\n\t\t\t\t\t[\n\t\t\t\t\t\tprops.countDuration ?? themeFrames(theme, \"slow\", fps) * 2,\n\t\t\t\t\t\tprops.value as number,\n\t\t\t\t\t\tease,\n\t\t\t\t\t],\n\t\t\t\t];\n\t\tconst last = (keys[keys.length - 1] as ValueKey)[0];\n\t\tconst fraction = (v: number) =>\n\t\t\tMath.max(0, Math.min(1, (v - from) / (max - from || 1)));\n\t\t/** Keys of `f(fraction)`, on exactly the value's keyframes and eases. */\n\t\tconst fillKeys = (f: (x: number) => number): KeyframeTuple[] =>\n\t\t\tkeys.map((k, i) =>\n\t\t\t\ti === 0\n\t\t\t\t\t? [k[0], f(fraction(k[1]))]\n\t\t\t\t\t: [k[0], f(fraction(k[1])), k[2] ?? ease],\n\t\t\t);\n\t\tconst perFrame = Array.from({ length: last + 1 }, (_, t) =>\n\t\t\tvalueAt(keys, t, ease),\n\t\t);\n\n\t\tconst showCounter = props.counter ?? kind !== \"stars\";\n\t\tconst suffix = props.suffix ?? (max === 100 ? \"%\" : \"\");\n\t\tconst makeCounter = bind(host, counter);\n\t\tconst number = (size: number, align: \"start\" | \"center\" | \"end\") =>\n\t\t\tmakeCounter({\n\t\t\t\tid: ctx.partId(\"counter\"),\n\t\t\t\tvalues: keys.map((k) => [...k]) as never,\n\t\t\t\tformat: props.format,\n\t\t\t\tsuffix,\n\t\t\t\taffixScale: 0.6,\n\t\t\t\tdigitStagger: 0,\n\t\t\t\tease,\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: size,\n\t\t\t\tfontWeight: 700,\n\t\t\t\talign,\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\ttheme,\n\t\t\t\tfps,\n\t\t\t});\n\t\tconst label = (text: string, size: number) =>\n\t\t\thost.text(text, {\n\t\t\t\tid: ctx.partId(\"label\"),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: size,\n\t\t\t\tfill: theme.color.muted,\n\t\t\t});\n\n\t\tconst children: LayoutNode[] = [];\n\t\tconst parts: Record<string, Animatable<LayoutNode>> = {};\n\t\tconst crossings: Array<{ frame: number; segment: number }> = [];\n\t\tlet W = 0;\n\t\tlet H = 0;\n\n\t\tconst stroke = (\n\t\t\tid: string,\n\t\t\td: string,\n\t\t\tw: number,\n\t\t\th: number,\n\t\t\tpaint: string,\n\t\t\tthickness: number,\n\t\t) =>\n\t\t\thost.shape(\"path\", {\n\t\t\t\tid: ctx.partId(id),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: w,\n\t\t\t\theight: h,\n\t\t\t\td,\n\t\t\t\tfillType: \"none\",\n\t\t\t\tstrokeColor: paint,\n\t\t\t\tstrokeWidth: thickness,\n\t\t\t\tstrokeLineCap: \"round\",\n\t\t\t});\n\n\t\tswitch (kind) {\n\t\t\tcase \"ring\":\n\t\t\tcase \"arc\": {\n\t\t\t\tconst D = props.size ?? 260 * s;\n\t\t\t\tconst T = props.thickness ?? D * (kind === \"ring\" ? 0.085 : 0.1);\n\t\t\t\tconst r = (D - T) / 2;\n\t\t\t\tconst c: Vec2 = [D / 2, D / 2];\n\t\t\t\t// Ring: a full turn from the top. Arc: 240° from lower left, over the top.\n\t\t\t\tconst [a0, a1] = kind === \"ring\" ? [-90, 269.9] : [150, 390];\n\t\t\t\tconst path = toPathData(arcPoints(c, r, a0, a1));\n\t\t\t\tW = D;\n\t\t\t\tH = kind === \"ring\" ? D : D * 0.86;\n\t\t\t\tconst track = stroke(\"track\", path, W, H, trackColor, T);\n\t\t\t\tconst fill = stroke(\"fill\", path, W, H, color, T);\n\t\t\t\tfill.animate(\n\t\t\t\t\tLayerAnimation.keys(\n\t\t\t\t\t\t\"trimEnd\",\n\t\t\t\t\t\tfillKeys((x) => x),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tchildren.push(track, fill);\n\t\t\t\tparts.track = track;\n\t\t\t\tparts.fill = fill;\n\t\t\t\tif (showCounter) {\n\t\t\t\t\tconst n = number(D * (kind === \"ring\" ? 0.24 : 0.22), \"center\");\n\t\t\t\t\tconst nn = n as unknown as {\n\t\t\t\t\t\tx: number;\n\t\t\t\t\t\ty: number;\n\t\t\t\t\t\twidth: number;\n\t\t\t\t\t\theight: number;\n\t\t\t\t\t};\n\t\t\t\t\tnn.x = (W - nn.width) / 2;\n\t\t\t\t\tnn.y =\n\t\t\t\t\t\t(kind === \"ring\" ? D / 2 : D * 0.48) -\n\t\t\t\t\t\tnn.height / 2 -\n\t\t\t\t\t\t(props.label ? D * 0.04 : 0);\n\t\t\t\t\tchildren.push(n);\n\t\t\t\t\tparts.counter = n;\n\t\t\t\t\tif (props.label) {\n\t\t\t\t\t\tconst size = D * 0.075;\n\t\t\t\t\t\tconst m = host.measure(props.label, { fontFamily, fontSize: size });\n\t\t\t\t\t\tconst l = label(props.label, size);\n\t\t\t\t\t\tObject.assign(l, { x: (W - m.width) / 2, y: nn.y + nn.height });\n\t\t\t\t\t\tchildren.push(l);\n\t\t\t\t\t\tparts.label = l;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"bar\": {\n\t\t\t\tconst w = props.size ?? 640 * s;\n\t\t\t\tconst T = props.thickness ?? 16 * s;\n\t\t\t\tconst top = props.label ? 34 * s : 0;\n\t\t\t\tconst track = host.box({\n\t\t\t\t\tid: ctx.partId(\"track\"),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: 0,\n\t\t\t\t\ty: top,\n\t\t\t\t\twidth: w,\n\t\t\t\t\theight: T,\n\t\t\t\t\tbackground: trackColor,\n\t\t\t\t\tborderRadius: T / 2,\n\t\t\t\t});\n\t\t\t\tconst fill = host.box({\n\t\t\t\t\tid: ctx.partId(\"fill\"),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: 0,\n\t\t\t\t\ty: top,\n\t\t\t\t\twidth: 0,\n\t\t\t\t\theight: T,\n\t\t\t\t\tbackground: color,\n\t\t\t\t\tborderRadius: T / 2,\n\t\t\t\t});\n\t\t\t\tfill.animate(\n\t\t\t\t\tLayerAnimation.keys(\n\t\t\t\t\t\t\"width\",\n\t\t\t\t\t\tfillKeys((x) => x * w),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tchildren.push(track, fill);\n\t\t\t\tparts.track = track;\n\t\t\t\tparts.fill = fill;\n\t\t\t\tW = w;\n\t\t\t\tH = top + T;\n\t\t\t\tif (props.label) {\n\t\t\t\t\tconst l = label(props.label, 24 * s);\n\t\t\t\t\tObject.assign(l, { x: 0, y: 0 });\n\t\t\t\t\tchildren.push(l);\n\t\t\t\t\tparts.label = l;\n\t\t\t\t}\n\t\t\t\tif (showCounter) {\n\t\t\t\t\tconst n = number(Math.max(T * 2.2, 30 * s), \"end\");\n\t\t\t\t\tconst nn = n as unknown as {\n\t\t\t\t\t\tx: number;\n\t\t\t\t\t\ty: number;\n\t\t\t\t\t\twidth: number;\n\t\t\t\t\t\theight: number;\n\t\t\t\t\t};\n\t\t\t\t\tnn.x = w + 18 * s;\n\t\t\t\t\tnn.y = top + T / 2 - nn.height / 2;\n\t\t\t\t\tW = nn.x + nn.width;\n\t\t\t\t\tH = Math.max(H, nn.y + nn.height);\n\t\t\t\t\tchildren.push(n);\n\t\t\t\t\tparts.counter = n;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"segments\":\n\t\t\tcase \"stars\": {\n\t\t\t\tconst n = props.segments ?? (kind === \"stars\" ? Math.round(max) : 10);\n\t\t\t\tconst w = props.size ?? (kind === \"stars\" ? 300 : 640) * s;\n\t\t\t\tconst gap = kind === \"stars\" ? w * 0.03 : w * 0.012;\n\t\t\t\tconst cell = (w - gap * (n - 1)) / n;\n\t\t\t\tconst h = kind === \"stars\" ? cell : (props.thickness ?? 22 * s);\n\t\t\t\t// How full each cell is, per frame.\n\t\t\t\tconst fill = (k: number) =>\n\t\t\t\t\tperFrame.map((v) => Math.max(0, Math.min(1, fraction(v) * n - k)));\n\t\t\t\tfor (let k = 0; k < n; k++) {\n\t\t\t\t\tconst x = k * (cell + gap);\n\t\t\t\t\tconst amount = fill(k);\n\t\t\t\t\tconst full = amount.findIndex((a) => a >= 1);\n\t\t\t\t\tif (full >= 0) crossings.push({ frame: full, segment: k });\n\t\t\t\t\tif (kind === \"segments\") {\n\t\t\t\t\t\tconst base = host.box({\n\t\t\t\t\t\t\tid: ctx.partId(`cell-${k}`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: cell,\n\t\t\t\t\t\t\theight: h,\n\t\t\t\t\t\t\tbackground: trackColor,\n\t\t\t\t\t\t\tborderRadius: Math.min(h, cell) * 0.25,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst lit = host.box({\n\t\t\t\t\t\t\tid: ctx.partId(`lit-${k}`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: cell,\n\t\t\t\t\t\t\theight: h,\n\t\t\t\t\t\t\tbackground: color,\n\t\t\t\t\t\t\tborderRadius: Math.min(h, cell) * 0.25,\n\t\t\t\t\t\t\topacity: 0,\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// Lights on the frame the value reaches the cell, with a small pop.\n\t\t\t\t\t\tconst on = amount.map((a) => (a >= 1 ? 1 : 0));\n\t\t\t\t\t\tconst opacity: KeyframeTuple[] = [[0, on[0] ?? 0]];\n\t\t\t\t\t\tconst scale: KeyframeTuple[] = [[0, 1]];\n\t\t\t\t\t\ton.forEach((v, t) => {\n\t\t\t\t\t\t\tif (t > 0 && v !== on[t - 1]) {\n\t\t\t\t\t\t\t\topacity.push([t, v, \"hold\"]);\n\t\t\t\t\t\t\t\tif (v === 1) scale.push([t, 1.18], [t + 6, 1, \"power2.out\"]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlit.animate(\n\t\t\t\t\t\t\tLayerAnimation.create()\n\t\t\t\t\t\t\t\t.keys(\"opacity\", opacity)\n\t\t\t\t\t\t\t\t.keys(\"scale\", ascending(scale)),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tchildren.push(base, lit);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst d = starPath(cell);\n\t\t\t\t\t\tconst base = host.shape(\"path\", {\n\t\t\t\t\t\t\tid: ctx.partId(`star-${k}`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: cell,\n\t\t\t\t\t\t\theight: cell,\n\t\t\t\t\t\t\td,\n\t\t\t\t\t\t\tfillType: \"solid\",\n\t\t\t\t\t\t\tfillColor: trackColor,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst star = host.shape(\"path\", {\n\t\t\t\t\t\t\tid: ctx.partId(`star-${k}-fill`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: cell,\n\t\t\t\t\t\t\theight: cell,\n\t\t\t\t\t\t\td,\n\t\t\t\t\t\t\tfillType: \"solid\",\n\t\t\t\t\t\t\tfillColor: color,\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// Fractional ratings fill a star partway: a clip that widens.\n\t\t\t\t\t\tconst clip = host.box({\n\t\t\t\t\t\t\tid: ctx.partId(`star-${k}-clip`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: 0,\n\t\t\t\t\t\t\theight: cell,\n\t\t\t\t\t\t\toverflow: \"hidden\",\n\t\t\t\t\t\t\tchildren: [star],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tclip.animate(\n\t\t\t\t\t\t\tLayerAnimation.keys(\n\t\t\t\t\t\t\t\t\"width\",\n\t\t\t\t\t\t\t\tbakeSamples(\n\t\t\t\t\t\t\t\t\tamount.map((a) => a * cell),\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t{ tolerance: 0.2 },\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tchildren.push(base, clip);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tW = w;\n\t\t\t\tH = h;\n\t\t\t\tif (showCounter) {\n\t\t\t\t\tconst c = number(Math.max(h * 1.4, 30 * s), \"end\");\n\t\t\t\t\tconst cc = c as unknown as {\n\t\t\t\t\t\tx: number;\n\t\t\t\t\t\ty: number;\n\t\t\t\t\t\twidth: number;\n\t\t\t\t\t\theight: number;\n\t\t\t\t\t};\n\t\t\t\t\tcc.x = w + 18 * s;\n\t\t\t\t\tcc.y = h / 2 - cc.height / 2;\n\t\t\t\t\tW = cc.x + cc.width;\n\t\t\t\t\tH = Math.max(H, cc.y + cc.height);\n\t\t\t\t\tchildren.push(c);\n\t\t\t\t\tparts.counter = c;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tconst settled = Math.max(\n\t\t\tlast,\n\t\t\t(\n\t\t\t\tparts.counter as unknown as {\n\t\t\t\t\tcues?: { settled: Array<{ frame: number }> };\n\t\t\t\t}\n\t\t\t)?.cues?.settled[0]?.frame ?? 0,\n\t\t);\n\t\treturn {\n\t\t\tchildren,\n\t\t\twidth: W,\n\t\t\theight: H,\n\t\t\tduration: settled + 1,\n\t\t\topenEnded: true,\n\t\t\tcues: { settled: [{ frame: settled }], crossings } as MeterCues,\n\t\t\tparts,\n\t\t};\n\t},\n});\n\nfunction ascending(keys: KeyframeTuple[]): KeyframeTuple[] {\n\tconst out: KeyframeTuple[] = [];\n\tfor (const k of [...keys].sort((a, b) => a[0] - b[0])) {\n\t\twhile (out.length > 0 && (out[out.length - 1] as KeyframeTuple)[0] >= k[0])\n\t\t\tout.pop();\n\t\tout.push(k);\n\t}\n\treturn out;\n}\n\n/**\n * Progress and scores as a ring, arc gauge, bar, lit segments or star rating,\n * filling on the same timeline as its counter.\n */\nexport const Meter = /* @__PURE__ */ bindComponent(meter);\n"
|
|
32
|
+
"content": "/**\n * Meter: progress and scores as a ring, an arc gauge, a bar, lit segments or\n * a star rating, filling on the same value timeline as its `Counter`.\n */\nimport {\n\ttype KeyframeTuple,\n\tLayerAnimation,\n\ttype LayoutNode,\n} from \"framefields\";\nimport {\n\ttype Animatable,\n\tbakeSamples,\n\tbind,\n\tbindComponent,\n\tdefineComponent,\n\tthemeFrames,\n\ttoPathData,\n\ttype Vec2,\n\tz,\n} from \"framefields/components\";\nimport { counter } from \"../counter/index.js\";\nimport { type ValueKey, valueAt } from \"../counter/plan.js\";\n\nexport const METER_KINDS = [\"ring\", \"arc\", \"bar\", \"segments\", \"stars\"] as const;\nexport type MeterKind = (typeof METER_KINDS)[number];\n\nexport interface MeterCues {\n\t[name: string]: ReadonlyArray<{ frame: number }>;\n\t/** The fill (and counter) is at rest. */\n\tsettled: ReadonlyArray<{ frame: number }>;\n\t/** Each segment or star completing (fills only; empties are not cued). */\n\tcrossings: ReadonlyArray<{ frame: number; segment: number }>;\n}\n\n/** Points along a circular arc, angles in degrees (0 = right, clockwise). */\nfunction arcPoints(c: Vec2, r: number, from: number, to: number): Vec2[] {\n\tconst steps = Math.max(8, Math.ceil(Math.abs(to - from) / 6));\n\treturn Array.from({ length: steps + 1 }, (_, k) => {\n\t\tconst a = ((from + ((to - from) * k) / steps) * Math.PI) / 180;\n\t\treturn [c[0] + Math.cos(a) * r, c[1] + Math.sin(a) * r] as Vec2;\n\t});\n}\n\n/** A five-point star inside a `size` box. */\nfunction starPath(size: number): string {\n\tconst c = size / 2;\n\tconst pts: Vec2[] = Array.from({ length: 10 }, (_, k) => {\n\t\tconst r = k % 2 === 0 ? size * 0.5 : size * 0.21;\n\t\tconst a = -Math.PI / 2 + (k * Math.PI) / 5;\n\t\treturn [c + Math.cos(a) * r, c + Math.sin(a) * r * 1.02 + size * 0.03];\n\t});\n\treturn toPathData(pts, true);\n}\n\nexport const meter = /* @__PURE__ */ defineComponent({\n\tname: \"Meter\",\n\tprops: () => ({\n\t\tvalue: z.number().optional(),\n\t\t/** Multi-step values: `[frame, value, ease?]`, frames ascending. */\n\t\tvalues: z\n\t\t\t.array(\n\t\t\t\tz.union([\n\t\t\t\t\tz.tuple([z.number().int().min(0), z.number()]),\n\t\t\t\t\tz.tuple([z.number().int().min(0), z.number(), z.string()]),\n\t\t\t\t]),\n\t\t\t)\n\t\t\t.min(1)\n\t\t\t.optional(),\n\t\tfrom: z.number().default(0),\n\t\t/** Default 100 (5 for stars). */\n\t\tmax: z.number().optional(),\n\t\tkind: z.enum(METER_KINDS).default(\"ring\"),\n\t\t/** Cells for `segments` (default 10), stars for `stars` (default `max`). */\n\t\tsegments: z.number().int().positive().optional(),\n\t\t/** Width (bar, segments, stars) or diameter (ring, arc). */\n\t\tsize: z.number().positive().optional(),\n\t\tthickness: z.number().positive().optional(),\n\t\tcounter: z.boolean().optional(),\n\t\tformat: z.string().default(\",d\"),\n\t\tsuffix: z.string().optional(),\n\t\tlabel: z.string().optional(),\n\t\tcolor: z.string().optional(),\n\t\ttrack: z.string().optional(),\n\t\tcountDuration: z.number().int().positive().optional(),\n\t\tease: z.string().optional(),\n\t\tfontFamily: z.string().optional(),\n\t}),\n\tdefaults: { enter: false, exit: false },\n\tbuild(ctx, props) {\n\t\tconst { host, theme, fps } = ctx;\n\t\tconst s = theme.scale;\n\t\tconst kind = props.kind;\n\t\tconst max = props.max ?? (kind === \"stars\" ? 5 : 100);\n\t\tconst from = props.from;\n\t\tconst ease = props.ease ?? theme.motion.ease.move;\n\t\tconst color = props.color ?? theme.color.accent;\n\t\tconst trackColor = props.track ?? theme.color.line;\n\t\tconst fontFamily = props.fontFamily ?? theme.font.display;\n\t\tif (props.value === undefined && !props.values)\n\t\t\tthrow new Error(\"Meter: give `value` (or `values`).\");\n\t\tconst keys: ValueKey[] = props.values\n\t\t\t? props.values.map((v) => [...v] as unknown as ValueKey)\n\t\t\t: [\n\t\t\t\t\t[0, from],\n\t\t\t\t\t[\n\t\t\t\t\t\tprops.countDuration ?? themeFrames(theme, \"slow\", fps) * 2,\n\t\t\t\t\t\tprops.value as number,\n\t\t\t\t\t\tease,\n\t\t\t\t\t],\n\t\t\t\t];\n\t\tconst last = (keys[keys.length - 1] as ValueKey)[0];\n\t\tconst fraction = (v: number) =>\n\t\t\tMath.max(0, Math.min(1, (v - from) / (max - from || 1)));\n\t\t/** Keys of `f(fraction)`, on exactly the value's keyframes and eases. */\n\t\tconst fillKeys = (f: (x: number) => number): KeyframeTuple[] =>\n\t\t\tkeys.map((k, i) =>\n\t\t\t\ti === 0\n\t\t\t\t\t? [k[0], f(fraction(k[1]))]\n\t\t\t\t\t: [k[0], f(fraction(k[1])), k[2] ?? ease],\n\t\t\t);\n\t\tconst perFrame = Array.from({ length: last + 1 }, (_, t) =>\n\t\t\tvalueAt(keys, t, ease),\n\t\t);\n\n\t\tconst showCounter = props.counter ?? kind !== \"stars\";\n\t\tconst suffix = props.suffix ?? (max === 100 ? \"%\" : \"\");\n\t\tconst makeCounter = bind(host, counter);\n\t\tconst number = (size: number, align: \"start\" | \"center\" | \"end\") =>\n\t\t\tmakeCounter({\n\t\t\t\tid: ctx.partId(\"counter\"),\n\t\t\t\tvalues: keys.map((k) => [...k]) as never,\n\t\t\t\tformat: props.format,\n\t\t\t\tsuffix,\n\t\t\t\taffixScale: 0.6,\n\t\t\t\tdigitStagger: 0,\n\t\t\t\tease,\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: size,\n\t\t\t\tfontWeight: 700,\n\t\t\t\talign,\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\ttheme,\n\t\t\t\tfps,\n\t\t\t});\n\t\tconst label = (text: string, size: number) =>\n\t\t\thost.text(text, {\n\t\t\t\tid: ctx.partId(\"label\"),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tfontFamily,\n\t\t\t\tfontSize: size,\n\t\t\t\tfill: theme.color.muted,\n\t\t\t});\n\n\t\tconst children: LayoutNode[] = [];\n\t\tconst parts: Record<string, Animatable<LayoutNode>> = {};\n\t\tconst crossings: Array<{ frame: number; segment: number }> = [];\n\t\tlet W = 0;\n\t\tlet H = 0;\n\n\t\tconst stroke = (\n\t\t\tid: string,\n\t\t\td: string,\n\t\t\tw: number,\n\t\t\th: number,\n\t\t\tpaint: string,\n\t\t\tthickness: number,\n\t\t) =>\n\t\t\thost.shape(\"path\", {\n\t\t\t\tid: ctx.partId(id),\n\t\t\t\tposition: \"absolute\",\n\t\t\t\tx: 0,\n\t\t\t\ty: 0,\n\t\t\t\twidth: w,\n\t\t\t\theight: h,\n\t\t\t\td,\n\t\t\t\tfillType: \"none\",\n\t\t\t\tstrokeColor: paint,\n\t\t\t\tstrokeWidth: thickness,\n\t\t\t\tstrokeLineCap: \"round\",\n\t\t\t});\n\n\t\tswitch (kind) {\n\t\t\tcase \"ring\":\n\t\t\tcase \"arc\": {\n\t\t\t\tconst D = props.size ?? 260 * s;\n\t\t\t\tconst T = props.thickness ?? D * (kind === \"ring\" ? 0.085 : 0.1);\n\t\t\t\tconst r = (D - T) / 2;\n\t\t\t\tconst c: Vec2 = [D / 2, D / 2];\n\t\t\t\t// Ring: a full turn from the top. Arc: 240° from lower left, over the top.\n\t\t\t\tconst [a0, a1] = kind === \"ring\" ? [-90, 269.9] : [150, 390];\n\t\t\t\tconst path = toPathData(arcPoints(c, r, a0, a1));\n\t\t\t\tW = D;\n\t\t\t\tH = kind === \"ring\" ? D : D * 0.86;\n\t\t\t\tconst track = stroke(\"track\", path, W, H, trackColor, T);\n\t\t\t\tconst fill = stroke(\"fill\", path, W, H, color, T);\n\t\t\t\tfill.animate(\n\t\t\t\t\tLayerAnimation.keys(\n\t\t\t\t\t\t\"trimEnd\",\n\t\t\t\t\t\tfillKeys((x) => x),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tchildren.push(track, fill);\n\t\t\t\tparts.track = track;\n\t\t\t\tparts.fill = fill;\n\t\t\t\tif (showCounter) {\n\t\t\t\t\tconst n = number(D * (kind === \"ring\" ? 0.24 : 0.22), \"center\");\n\t\t\t\t\tconst nn = n as unknown as {\n\t\t\t\t\t\tx: number;\n\t\t\t\t\t\ty: number;\n\t\t\t\t\t\twidth: number;\n\t\t\t\t\t\theight: number;\n\t\t\t\t\t};\n\t\t\t\t\tnn.x = (W - nn.width) / 2;\n\t\t\t\t\tnn.y =\n\t\t\t\t\t\t(kind === \"ring\" ? D / 2 : D * 0.48) -\n\t\t\t\t\t\tnn.height / 2 -\n\t\t\t\t\t\t(props.label ? D * 0.04 : 0);\n\t\t\t\t\tchildren.push(n);\n\t\t\t\t\tparts.counter = n;\n\t\t\t\t\tif (props.label) {\n\t\t\t\t\t\tconst size = D * 0.075;\n\t\t\t\t\t\tconst m = host.measure(props.label, { fontFamily, fontSize: size });\n\t\t\t\t\t\tconst l = label(props.label, size);\n\t\t\t\t\t\tObject.assign(l, { x: (W - m.width) / 2, y: nn.y + nn.height });\n\t\t\t\t\t\tchildren.push(l);\n\t\t\t\t\t\tparts.label = l;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"bar\": {\n\t\t\t\tconst w = props.size ?? 640 * s;\n\t\t\t\tconst T = props.thickness ?? 16 * s;\n\t\t\t\tconst top = props.label ? 34 * s : 0;\n\t\t\t\tconst track = host.box({\n\t\t\t\t\tid: ctx.partId(\"track\"),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: 0,\n\t\t\t\t\ty: top,\n\t\t\t\t\twidth: w,\n\t\t\t\t\theight: T,\n\t\t\t\t\tbackground: trackColor,\n\t\t\t\t\tborderRadius: T / 2,\n\t\t\t\t});\n\t\t\t\tconst fill = host.box({\n\t\t\t\t\tid: ctx.partId(\"fill\"),\n\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\tx: 0,\n\t\t\t\t\ty: top,\n\t\t\t\t\twidth: 0,\n\t\t\t\t\theight: T,\n\t\t\t\t\tbackground: color,\n\t\t\t\t\tborderRadius: T / 2,\n\t\t\t\t});\n\t\t\t\tfill.animate(\n\t\t\t\t\tLayerAnimation.keys(\n\t\t\t\t\t\t\"width\",\n\t\t\t\t\t\tfillKeys((x) => x * w),\n\t\t\t\t\t),\n\t\t\t\t);\n\t\t\t\tchildren.push(track, fill);\n\t\t\t\tparts.track = track;\n\t\t\t\tparts.fill = fill;\n\t\t\t\tW = w;\n\t\t\t\tH = top + T;\n\t\t\t\tif (props.label) {\n\t\t\t\t\tconst l = label(props.label, 24 * s);\n\t\t\t\t\tObject.assign(l, { x: 0, y: 0 });\n\t\t\t\t\tchildren.push(l);\n\t\t\t\t\tparts.label = l;\n\t\t\t\t}\n\t\t\t\tif (showCounter) {\n\t\t\t\t\tconst n = number(Math.max(T * 2.2, 30 * s), \"end\");\n\t\t\t\t\tconst nn = n as unknown as {\n\t\t\t\t\t\tx: number;\n\t\t\t\t\t\ty: number;\n\t\t\t\t\t\twidth: number;\n\t\t\t\t\t\theight: number;\n\t\t\t\t\t};\n\t\t\t\t\tnn.x = w + 18 * s;\n\t\t\t\t\tnn.y = top + T / 2 - nn.height / 2;\n\t\t\t\t\tW = nn.x + nn.width;\n\t\t\t\t\tH = Math.max(H, nn.y + nn.height);\n\t\t\t\t\tchildren.push(n);\n\t\t\t\t\tparts.counter = n;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase \"segments\":\n\t\t\tcase \"stars\": {\n\t\t\t\tconst n = props.segments ?? (kind === \"stars\" ? Math.round(max) : 10);\n\t\t\t\tconst w = props.size ?? (kind === \"stars\" ? 300 : 640) * s;\n\t\t\t\tconst gap = kind === \"stars\" ? w * 0.03 : w * 0.012;\n\t\t\t\tconst cell = (w - gap * (n - 1)) / n;\n\t\t\t\tconst h = kind === \"stars\" ? cell : (props.thickness ?? 22 * s);\n\t\t\t\t// How full each cell is, per frame.\n\t\t\t\tconst fill = (k: number) =>\n\t\t\t\t\tperFrame.map((v) => Math.max(0, Math.min(1, fraction(v) * n - k)));\n\t\t\t\tfor (let k = 0; k < n; k++) {\n\t\t\t\t\tconst x = k * (cell + gap);\n\t\t\t\t\tconst amount = fill(k);\n\t\t\t\t\tconst full = amount.findIndex((a) => a >= 1);\n\t\t\t\t\tif (full >= 0) crossings.push({ frame: full, segment: k });\n\t\t\t\t\tif (kind === \"segments\") {\n\t\t\t\t\t\tconst base = host.box({\n\t\t\t\t\t\t\tid: ctx.partId(`cell-${k}`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: cell,\n\t\t\t\t\t\t\theight: h,\n\t\t\t\t\t\t\tbackground: trackColor,\n\t\t\t\t\t\t\tborderRadius: Math.min(h, cell) * 0.25,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst lit = host.box({\n\t\t\t\t\t\t\tid: ctx.partId(`lit-${k}`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: cell,\n\t\t\t\t\t\t\theight: h,\n\t\t\t\t\t\t\tbackground: color,\n\t\t\t\t\t\t\tborderRadius: Math.min(h, cell) * 0.25,\n\t\t\t\t\t\t\topacity: 0,\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// Lights on the frame the value reaches the cell, with a small pop.\n\t\t\t\t\t\tconst on = amount.map((a) => (a >= 1 ? 1 : 0));\n\t\t\t\t\t\tconst opacity: KeyframeTuple[] = [[0, on[0] ?? 0]];\n\t\t\t\t\t\tconst scale: KeyframeTuple[] = [[0, 1]];\n\t\t\t\t\t\ton.forEach((v, t) => {\n\t\t\t\t\t\t\tif (t > 0 && v !== on[t - 1]) {\n\t\t\t\t\t\t\t\topacity.push([t, v, \"hold\"]);\n\t\t\t\t\t\t\t\tif (v === 1) scale.push([t, 1.18], [t + 6, 1, \"power2.out\"]);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\tlit.animate(\n\t\t\t\t\t\t\tLayerAnimation.create()\n\t\t\t\t\t\t\t\t.keys(\"opacity\", opacity)\n\t\t\t\t\t\t\t\t.keys(\"scale\", ascending(scale)),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tchildren.push(base, lit);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tconst d = starPath(cell);\n\t\t\t\t\t\tconst base = host.shape(\"path\", {\n\t\t\t\t\t\t\tid: ctx.partId(`star-${k}`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: cell,\n\t\t\t\t\t\t\theight: cell,\n\t\t\t\t\t\t\td,\n\t\t\t\t\t\t\tfillType: \"solid\",\n\t\t\t\t\t\t\tfillColor: trackColor,\n\t\t\t\t\t\t});\n\t\t\t\t\t\tconst star = host.shape(\"path\", {\n\t\t\t\t\t\t\tid: ctx.partId(`star-${k}-fill`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx: 0,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: cell,\n\t\t\t\t\t\t\theight: cell,\n\t\t\t\t\t\t\td,\n\t\t\t\t\t\t\tfillType: \"solid\",\n\t\t\t\t\t\t\tfillColor: color,\n\t\t\t\t\t\t});\n\t\t\t\t\t\t// Fractional ratings fill a star partway: a clip that widens.\n\t\t\t\t\t\tconst clip = host.box({\n\t\t\t\t\t\t\tid: ctx.partId(`star-${k}-clip`),\n\t\t\t\t\t\t\tposition: \"absolute\",\n\t\t\t\t\t\t\tx,\n\t\t\t\t\t\t\ty: 0,\n\t\t\t\t\t\t\twidth: 0,\n\t\t\t\t\t\t\theight: cell,\n\t\t\t\t\t\t\toverflow: \"hidden\",\n\t\t\t\t\t\t\tchildren: [star],\n\t\t\t\t\t\t});\n\t\t\t\t\t\tclip.animate(\n\t\t\t\t\t\t\tLayerAnimation.keys(\n\t\t\t\t\t\t\t\t\"width\",\n\t\t\t\t\t\t\t\tbakeSamples(\n\t\t\t\t\t\t\t\t\tamount.map((a) => a * cell),\n\t\t\t\t\t\t\t\t\t0,\n\t\t\t\t\t\t\t\t\t{ tolerance: 0.2 },\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t);\n\t\t\t\t\t\tchildren.push(base, clip);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tW = w;\n\t\t\t\tH = h;\n\t\t\t\tif (showCounter) {\n\t\t\t\t\tconst c = number(Math.max(h * 1.4, 30 * s), \"end\");\n\t\t\t\t\tconst cc = c as unknown as {\n\t\t\t\t\t\tx: number;\n\t\t\t\t\t\ty: number;\n\t\t\t\t\t\twidth: number;\n\t\t\t\t\t\theight: number;\n\t\t\t\t\t};\n\t\t\t\t\tcc.x = w + 18 * s;\n\t\t\t\t\tcc.y = h / 2 - cc.height / 2;\n\t\t\t\t\tW = cc.x + cc.width;\n\t\t\t\t\tH = Math.max(H, cc.y + cc.height);\n\t\t\t\t\tchildren.push(c);\n\t\t\t\t\tparts.counter = c;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\t// Centring the counter on a shorter row can put it above the top; the\n\t\t// root box clips what's outside it, so move everything down instead.\n\t\tconst top = Math.min(\n\t\t\t0,\n\t\t\t...children.map((n) => (n as unknown as { y?: number }).y ?? 0),\n\t\t);\n\t\tif (top < 0) {\n\t\t\tfor (const n of children) {\n\t\t\t\tconst node = n as unknown as { y?: number };\n\t\t\t\tnode.y = (node.y ?? 0) - top;\n\t\t\t}\n\t\t\tH -= top;\n\t\t}\n\n\t\tconst settled = Math.max(\n\t\t\tlast,\n\t\t\t(\n\t\t\t\tparts.counter as unknown as {\n\t\t\t\t\tcues?: { settled: Array<{ frame: number }> };\n\t\t\t\t}\n\t\t\t)?.cues?.settled[0]?.frame ?? 0,\n\t\t);\n\t\treturn {\n\t\t\tchildren,\n\t\t\twidth: W,\n\t\t\theight: H,\n\t\t\tduration: settled + 1,\n\t\t\topenEnded: true,\n\t\t\tcues: { settled: [{ frame: settled }], crossings } as MeterCues,\n\t\t\tparts,\n\t\t};\n\t},\n});\n\nfunction ascending(keys: KeyframeTuple[]): KeyframeTuple[] {\n\tconst out: KeyframeTuple[] = [];\n\tfor (const k of [...keys].sort((a, b) => a[0] - b[0])) {\n\t\twhile (out.length > 0 && (out[out.length - 1] as KeyframeTuple)[0] >= k[0])\n\t\t\tout.pop();\n\t\tout.push(k);\n\t}\n\treturn out;\n}\n\n/**\n * Progress and scores as a ring, arc gauge, bar, lit segments or star rating,\n * filling on the same timeline as its counter.\n */\nexport const Meter = /* @__PURE__ */ bindComponent(meter);\n"
|
|
33
33
|
}
|
|
34
34
|
]
|
|
35
35
|
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
import { F as describeGpu, L as initHeadlessWebGPU, p as GpuAdapterDescription } from "../index-
|
|
2
|
-
import { _ as renderSemaphore, a as renderFrameGrid, c as HeadlessMediaRenderer, d as QaSegment, f as RenderProgress, g as formatQaReport, h as VideoQaStats, i as computeGridLayout, l as HeadlessWebGPURenderer, m as VideoQaReport, n as FrameGridRenderable, o as resolveSamplePoints, p as VideoQaOptions, r as FrameSamplePoint, s as AudioQaStats, t as FrameGridOptions, u as QaIssue, v as resetRenderCaches } from "../index-
|
|
1
|
+
import { F as describeGpu, L as initHeadlessWebGPU, p as GpuAdapterDescription } from "../index-CZK6CFLy.mjs";
|
|
2
|
+
import { _ as renderSemaphore, a as renderFrameGrid, c as HeadlessMediaRenderer, d as QaSegment, f as RenderProgress, g as formatQaReport, h as VideoQaStats, i as computeGridLayout, l as HeadlessWebGPURenderer, m as VideoQaReport, n as FrameGridRenderable, o as resolveSamplePoints, p as VideoQaOptions, r as FrameSamplePoint, s as AudioQaStats, t as FrameGridOptions, u as QaIssue, v as resetRenderCaches } from "../index-KqW22NHL.mjs";
|
|
3
3
|
export { AudioQaStats, FrameGridOptions, FrameGridRenderable, FrameSamplePoint, GpuAdapterDescription, HeadlessMediaRenderer, HeadlessWebGPURenderer, QaIssue, QaSegment, RenderProgress, VideoQaOptions, VideoQaReport, VideoQaStats, computeGridLayout, describeGpu, formatQaReport, initHeadlessWebGPU, renderFrameGrid, renderSemaphore, resetRenderCaches, resolveSamplePoints };
|
package/dist/renderer/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import "../chunk-D7gtBK74.mjs";
|
|
2
|
-
import { L as initHeadlessWebGPU, x as describeGpu } from "../dist-
|
|
3
|
-
import { a as HeadlessMediaRenderer, c as renderSemaphore, i as resetRenderCaches, n as renderFrameGrid, o as HeadlessWebGPURenderer, r as resolveSamplePoints, s as formatQaReport, t as computeGridLayout } from "../renderer-
|
|
2
|
+
import { L as initHeadlessWebGPU, x as describeGpu } from "../dist-Bf0H4lp1.mjs";
|
|
3
|
+
import { a as HeadlessMediaRenderer, c as renderSemaphore, i as resetRenderCaches, n as renderFrameGrid, o as HeadlessWebGPURenderer, r as resolveSamplePoints, s as formatQaReport, t as computeGridLayout } from "../renderer-CvYxxS_X.mjs";
|
|
4
4
|
|
|
5
5
|
export { HeadlessMediaRenderer, HeadlessWebGPURenderer, computeGridLayout, describeGpu, formatQaReport, initHeadlessWebGPU, renderFrameGrid, renderSemaphore, resetRenderCaches, resolveSamplePoints };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as defineRenderer } from "./renderer-C9r0-KY4.mjs";
|
|
2
|
-
import { Fn as signalRegistry } from "./dist-
|
|
3
|
-
import { a as createFullscreenPipeline, c as drawTextureToTarget, f as signalTiming, l as fragmentBindGroupLayout, o as drawChildToTexture, p as texturesLayout, s as drawFullscreenPass, t as FULLSCREEN_QUAD_WGSL, u as perDevice } from "./gpu-
|
|
2
|
+
import { Fn as signalRegistry } from "./dist-Bf0H4lp1.mjs";
|
|
3
|
+
import { a as createFullscreenPipeline, c as drawTextureToTarget, f as signalTiming, l as fragmentBindGroupLayout, o as drawChildToTexture, p as texturesLayout, s as drawFullscreenPass, t as FULLSCREEN_QUAD_WGSL, u as perDevice } from "./gpu-CevnCkZC.mjs";
|
|
4
4
|
|
|
5
5
|
//#region ../../nodes/node-displacement-map/dist/renderers-DzMCAYTg.mjs
|
|
6
6
|
const STRENGTH_MIN = 0;
|
|
@@ -249,4 +249,4 @@ var renderers_default = defineRenderer({ WebGPURenderer: DisplacementMapWebGPURe
|
|
|
249
249
|
|
|
250
250
|
//#endregion
|
|
251
251
|
export { renderers_default as t };
|
|
252
|
-
//# sourceMappingURL=renderer-
|
|
252
|
+
//# sourceMappingURL=renderer-4P-DCjk2.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer-VdTs-dqH.mjs","names":[],"sources":["../../../nodes/node-displacement-map/dist/renderers-DzMCAYTg.mjs"],"sourcesContent":["import { defineRenderer } from \"@framefields/node-sdk/renderer\";\nimport { FULLSCREEN_QUAD_WGSL, createFullscreenPipeline, drawChildToTexture, drawFullscreenPass, drawTextureToTarget, fragmentBindGroupLayout, perDevice, signalTiming, texturesLayout } from \"@framefields/node-sdk/gpu\";\nimport { signalRegistry } from \"@framefields/webgpu-renderers\";\n\n//#region src/renderers/webgpu-renderer.ts\nconst STRENGTH_MIN = 0;\nconst STRENGTH_MAX = 500;\nfunction clamp(value, min, max) {\n\treturn Math.min(max, Math.max(min, value));\n}\nconst DISPLACEMENT_MAP_SHADER = `\nstruct DisplacementUniforms {\n strengthX : f32,\n strengthY : f32,\n xChannel : f32, // 0=R, 1=G, 2=B, 3=A, 4=Luma\n yChannel : f32, // 0=R, 1=G, 2=B, 3=A, 4=Luma\n\n wrapMode : f32, // 0=Clamp, 1=Repeat, 2=Mirror\n hasSignalX : f32,\n hasSignalY : f32,\n _pad : f32,\n};\n\n@group(0) @binding(0) var<uniform> u : DisplacementUniforms;\n@group(1) @binding(0) var srcTex : texture_2d<f32>;\n@group(1) @binding(1) var srcSamp : sampler;\n@group(2) @binding(0) var mapTex : texture_2d<f32>;\n@group(2) @binding(1) var mapSamp : sampler;\n@group(3) @binding(0) var sigXTex : texture_2d<f32>;\n@group(3) @binding(1) var sigYTex : texture_2d<f32>;\n@group(3) @binding(2) var sigSamp : sampler;\n${FULLSCREEN_QUAD_WGSL}\n\nfn extractChannel(color: vec4<f32>, channel: f32) -> f32 {\n if (channel < 0.5) { return color.r; }\n if (channel < 1.5) { return color.g; }\n if (channel < 2.5) { return color.b; }\n if (channel < 3.5) { return color.a; }\n // Luminance\n return dot(color.rgb, vec3<f32>(0.299, 0.587, 0.114));\n}\n\nfn applyWrap(coord: f32, mode: f32) -> f32 {\n if (mode < 0.5) {\n // Clamp\n return clamp(coord, 0.0, 1.0);\n }\n if (mode < 1.5) {\n // Repeat\n return fract(coord);\n }\n // Mirror\n let t = fract(coord * 0.5) * 2.0;\n if (t > 1.0) {\n return 2.0 - t;\n }\n return t;\n}\n\n@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {\n let mapColor = textureSampleLevel(mapTex, mapSamp, in.uv, 0.0);\n\n // Extract displacement values from chosen channels, centered around 0.5 and gated by alpha\n // NOTE (P3, not changed here): this yields a max offset of strength/2 px at\n // either extreme (peak-to-peak swing == strength). Confirm with design whether\n // \"strength in pixels\" should mean peak amplitude instead — see review doc.\n let dispX = (extractChannel(mapColor, u.xChannel) - 0.5) * mapColor.a;\n let dispY = (extractChannel(mapColor, u.yChannel) - 0.5) * mapColor.a;\n\n // Get strength, potentially from signal textures\n var sX = u.strengthX;\n var sY = u.strengthY;\n if (u.hasSignalX > 0.5) {\n sX = textureSampleLevel(sigXTex, sigSamp, in.uv, 0.0).r;\n }\n if (u.hasSignalY > 0.5) {\n sY = textureSampleLevel(sigYTex, sigSamp, in.uv, 0.0).r;\n }\n\n // Compute displacement in UV space (strength is in pixels, convert to UV)\n let srcDims = vec2<f32>(textureDimensions(srcTex));\n let offset = vec2<f32>(\n dispX * sX / srcDims.x,\n dispY * sY / srcDims.y,\n );\n\n let displaced = vec2<f32>(\n applyWrap(in.uv.x + offset.x, u.wrapMode),\n applyWrap(in.uv.y + offset.y, u.wrapMode),\n );\n\n return textureSampleLevel(srcTex, srcSamp, displaced, 0.0);\n}\n`;\nconst uniformData = new Float32Array(8);\nconst getDeviceLayouts = perDevice((device) => ({\n\tuniformLayout: fragmentBindGroupLayout(device, [\"uniform\"]),\n\tsrcTextureLayout: fragmentBindGroupLayout(device, [\"texture\", \"sampler\"]),\n\tmapTextureLayout: fragmentBindGroupLayout(device, [\"texture\", \"sampler\"]),\n\tsignalTextureLayout: texturesLayout(device, 2),\n\tpipelineCache: /* @__PURE__ */ new Map()\n}));\nfunction getDisplacementResources(device, format) {\n\tconst layouts = getDeviceLayouts(device);\n\tconst cacheKey = `displacement_${format}`;\n\tlet pipeline = layouts.pipelineCache.get(cacheKey);\n\tif (!pipeline) {\n\t\tconst module = device.createShaderModule({\n\t\t\tlabel: `displacement_${format}.wgsl`,\n\t\t\tcode: DISPLACEMENT_MAP_SHADER\n\t\t});\n\t\tpipeline = createFullscreenPipeline(device, {\n\t\t\tlabel: `DisplacementMapPipeline_${format}`,\n\t\t\tmodule,\n\t\t\tlayouts: [\n\t\t\t\tlayouts.uniformLayout,\n\t\t\t\tlayouts.srcTextureLayout,\n\t\t\t\tlayouts.mapTextureLayout,\n\t\t\t\tlayouts.signalTextureLayout\n\t\t\t],\n\t\t\tformat\n\t\t});\n\t\tlayouts.pipelineCache.set(cacheKey, pipeline);\n\t}\n\treturn {\n\t\tpipeline,\n\t\tuniformLayout: layouts.uniformLayout,\n\t\tsrcTextureLayout: layouts.srcTextureLayout,\n\t\tmapTextureLayout: layouts.mapTextureLayout,\n\t\tsignalTextureLayout: layouts.signalTextureLayout\n\t};\n}\nfunction channelToFloat(ch) {\n\tswitch (ch) {\n\t\tcase \"Green\": return 1;\n\t\tcase \"Blue\": return 2;\n\t\tcase \"Alpha\": return 3;\n\t\tcase \"Luminance\": return 4;\n\t\tdefault: return 0;\n\t}\n}\nfunction wrapModeToFloat(mode) {\n\tswitch (mode) {\n\t\tcase \"Repeat\": return 1;\n\t\tcase \"Mirror\": return 2;\n\t\tdefault: return 0;\n\t}\n}\nfunction resolveSignalField(op, fieldName, defaultValue) {\n\tconst handleId = op[`${fieldName}HandleId`];\n\tconst signalInput = handleId ? op.inputs?.[handleId] : null;\n\tconst hasSignal = !!(signalInput?.connectionValid && signalInput.outputItem?.type === \"Signal\");\n\tconst sd = hasSignal ? signalInput?.outputItem?.data : null;\n\tlet value = defaultValue;\n\tif (!hasSignal) if (signalInput?.connectionValid && signalInput.outputItem?.type === \"Number\") value = clamp(Number(signalInput.outputItem.data ?? defaultValue), STRENGTH_MIN, STRENGTH_MAX);\n\telse value = Number(op[fieldName] ?? defaultValue);\n\telse if (sd && typeof sd === \"object\" && \"offset\" in sd) value = Number(sd.offset ?? defaultValue);\n\treturn {\n\t\thasSignal,\n\t\tsd,\n\t\tvalue\n\t};\n}\nconst DisplacementMapWebGPURenderer = async (args) => {\n\tconst { ctx, encoder, pass, targetTexture, targetWidth, targetHeight, props } = args;\n\tconst op = props.virtualMedia?.operation;\n\tif (op?.op !== \"DisplacementMap\" || !op) return;\n\tconst sourceMedia = props.virtualMedia.children?.[0];\n\tconst mapMedia = op.mapMedia;\n\tif (!sourceMedia || !mapMedia) return;\n\tpass.end();\n\tconst width = targetWidth;\n\tconst height = targetHeight;\n\tconst frame = props.frame ?? 0;\n\tconst fps = props.fps || 30;\n\tconst strengthXInfo = resolveSignalField(op, \"strengthX\", 50);\n\tconst strengthYInfo = resolveSignalField(op, \"strengthY\", 50);\n\tconst { texture: srcTex } = await drawChildToTexture(args, sourceMedia, { props: {\n\t\t...props,\n\t\tvirtualMedia: sourceMedia,\n\t\trenderId: `${props.renderId}-c0`\n\t} });\n\targs.pass.end();\n\tconst { texture: mapTex } = await drawChildToTexture(args, mapMedia, {\n\t\tprops: {\n\t\t\t...props,\n\t\t\tvirtualMedia: mapMedia,\n\t\t\trenderId: `${props.renderId}-c1`,\n\t\t\texcludeTextures: [...props.excludeTextures || [], srcTex]\n\t\t},\n\t\texclude: [srcTex]\n\t});\n\targs.pass.end();\n\tconst { pipeline, uniformLayout: uLayout, srcTextureLayout: srcLayout, mapTextureLayout: mapLayout, signalTextureLayout: sigLayout } = getDisplacementResources(ctx.device, ctx.renderer.format);\n\tconst sampler = ctx.renderer.samplerCache.getSampler(ctx.device);\n\tlet sigXView;\n\tlet sigYView;\n\tconst { elapsedSeconds, durationSeconds } = signalTiming(props, frame, fps);\n\tif (strengthXInfo.hasSignal && strengthXInfo.sd) sigXView = signalRegistry.getOrCreate2DTextureView(ctx.device, encoder, strengthXInfo.sd.nodeId ?? \"displace_sx_sig\", elapsedSeconds, durationSeconds, strengthXInfo.sd, width, height, props.renderId, frame, fps);\n\telse sigXView = signalRegistry.getDummy1x1TextureView(ctx.device);\n\tif (strengthYInfo.hasSignal && strengthYInfo.sd) sigYView = signalRegistry.getOrCreate2DTextureView(ctx.device, encoder, strengthYInfo.sd.nodeId ?? \"displace_sy_sig\", elapsedSeconds, durationSeconds, strengthYInfo.sd, width, height, props.renderId, frame, fps);\n\telse sigYView = signalRegistry.getDummy1x1TextureView(ctx.device);\n\tuniformData[0] = strengthXInfo.value;\n\tuniformData[1] = strengthYInfo.value;\n\tuniformData[2] = channelToFloat(op.xChannel);\n\tuniformData[3] = channelToFloat(op.yChannel);\n\tuniformData[4] = wrapModeToFloat(op.wrapMode);\n\tuniformData[5] = strengthXInfo.hasSignal ? 1 : 0;\n\tuniformData[6] = strengthYInfo.hasSignal ? 1 : 0;\n\tuniformData[7] = 0;\n\tconst uniformBuffer = ctx.renderer.getTemporaryBuffer(uniformData);\n\tconst outTex = ctx.renderer.getTemporaryTexture(width, height, [\n\t\tsrcTex,\n\t\tmapTex,\n\t\ttargetTexture,\n\t\t...props.excludeTextures || []\n\t]);\n\tdrawFullscreenPass(encoder, outTex.createView(), pipeline, [\n\t\tctx.device.createBindGroup({\n\t\t\tlayout: uLayout,\n\t\t\tentries: [{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: { buffer: uniformBuffer }\n\t\t\t}]\n\t\t}),\n\t\tctx.renderer.bindGroupCache.getBindGroup(ctx.device, srcLayout, srcTex, sampler),\n\t\tctx.renderer.bindGroupCache.getBindGroup(ctx.device, mapLayout, mapTex, sampler),\n\t\tctx.device.createBindGroup({\n\t\t\tlayout: sigLayout,\n\t\t\tentries: [\n\t\t\t\t{\n\t\t\t\t\tbinding: 0,\n\t\t\t\t\tresource: sigXView\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tbinding: 1,\n\t\t\t\t\tresource: sigYView\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tbinding: 2,\n\t\t\t\t\tresource: sampler\n\t\t\t\t}\n\t\t\t]\n\t\t})\n\t]);\n\tdrawTextureToTarget(args, outTex, op.opacity ?? 1);\n};\n\n//#endregion\n//#region src/renderers/index.ts\nvar renderers_default = defineRenderer({ WebGPURenderer: DisplacementMapWebGPURenderer });\n\n//#endregion\nexport { renderers_default as t };\n//# sourceMappingURL=renderers-DzMCAYTg.mjs.map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"renderer-4P-DCjk2.mjs","names":[],"sources":["../../../nodes/node-displacement-map/dist/renderers-DzMCAYTg.mjs"],"sourcesContent":["import { defineRenderer } from \"@framefields/node-sdk/renderer\";\nimport { FULLSCREEN_QUAD_WGSL, createFullscreenPipeline, drawChildToTexture, drawFullscreenPass, drawTextureToTarget, fragmentBindGroupLayout, perDevice, signalTiming, texturesLayout } from \"@framefields/node-sdk/gpu\";\nimport { signalRegistry } from \"@framefields/webgpu-renderers\";\n\n//#region src/renderers/webgpu-renderer.ts\nconst STRENGTH_MIN = 0;\nconst STRENGTH_MAX = 500;\nfunction clamp(value, min, max) {\n\treturn Math.min(max, Math.max(min, value));\n}\nconst DISPLACEMENT_MAP_SHADER = `\nstruct DisplacementUniforms {\n strengthX : f32,\n strengthY : f32,\n xChannel : f32, // 0=R, 1=G, 2=B, 3=A, 4=Luma\n yChannel : f32, // 0=R, 1=G, 2=B, 3=A, 4=Luma\n\n wrapMode : f32, // 0=Clamp, 1=Repeat, 2=Mirror\n hasSignalX : f32,\n hasSignalY : f32,\n _pad : f32,\n};\n\n@group(0) @binding(0) var<uniform> u : DisplacementUniforms;\n@group(1) @binding(0) var srcTex : texture_2d<f32>;\n@group(1) @binding(1) var srcSamp : sampler;\n@group(2) @binding(0) var mapTex : texture_2d<f32>;\n@group(2) @binding(1) var mapSamp : sampler;\n@group(3) @binding(0) var sigXTex : texture_2d<f32>;\n@group(3) @binding(1) var sigYTex : texture_2d<f32>;\n@group(3) @binding(2) var sigSamp : sampler;\n${FULLSCREEN_QUAD_WGSL}\n\nfn extractChannel(color: vec4<f32>, channel: f32) -> f32 {\n if (channel < 0.5) { return color.r; }\n if (channel < 1.5) { return color.g; }\n if (channel < 2.5) { return color.b; }\n if (channel < 3.5) { return color.a; }\n // Luminance\n return dot(color.rgb, vec3<f32>(0.299, 0.587, 0.114));\n}\n\nfn applyWrap(coord: f32, mode: f32) -> f32 {\n if (mode < 0.5) {\n // Clamp\n return clamp(coord, 0.0, 1.0);\n }\n if (mode < 1.5) {\n // Repeat\n return fract(coord);\n }\n // Mirror\n let t = fract(coord * 0.5) * 2.0;\n if (t > 1.0) {\n return 2.0 - t;\n }\n return t;\n}\n\n@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {\n let mapColor = textureSampleLevel(mapTex, mapSamp, in.uv, 0.0);\n\n // Extract displacement values from chosen channels, centered around 0.5 and gated by alpha\n // NOTE (P3, not changed here): this yields a max offset of strength/2 px at\n // either extreme (peak-to-peak swing == strength). Confirm with design whether\n // \"strength in pixels\" should mean peak amplitude instead — see review doc.\n let dispX = (extractChannel(mapColor, u.xChannel) - 0.5) * mapColor.a;\n let dispY = (extractChannel(mapColor, u.yChannel) - 0.5) * mapColor.a;\n\n // Get strength, potentially from signal textures\n var sX = u.strengthX;\n var sY = u.strengthY;\n if (u.hasSignalX > 0.5) {\n sX = textureSampleLevel(sigXTex, sigSamp, in.uv, 0.0).r;\n }\n if (u.hasSignalY > 0.5) {\n sY = textureSampleLevel(sigYTex, sigSamp, in.uv, 0.0).r;\n }\n\n // Compute displacement in UV space (strength is in pixels, convert to UV)\n let srcDims = vec2<f32>(textureDimensions(srcTex));\n let offset = vec2<f32>(\n dispX * sX / srcDims.x,\n dispY * sY / srcDims.y,\n );\n\n let displaced = vec2<f32>(\n applyWrap(in.uv.x + offset.x, u.wrapMode),\n applyWrap(in.uv.y + offset.y, u.wrapMode),\n );\n\n return textureSampleLevel(srcTex, srcSamp, displaced, 0.0);\n}\n`;\nconst uniformData = new Float32Array(8);\nconst getDeviceLayouts = perDevice((device) => ({\n\tuniformLayout: fragmentBindGroupLayout(device, [\"uniform\"]),\n\tsrcTextureLayout: fragmentBindGroupLayout(device, [\"texture\", \"sampler\"]),\n\tmapTextureLayout: fragmentBindGroupLayout(device, [\"texture\", \"sampler\"]),\n\tsignalTextureLayout: texturesLayout(device, 2),\n\tpipelineCache: /* @__PURE__ */ new Map()\n}));\nfunction getDisplacementResources(device, format) {\n\tconst layouts = getDeviceLayouts(device);\n\tconst cacheKey = `displacement_${format}`;\n\tlet pipeline = layouts.pipelineCache.get(cacheKey);\n\tif (!pipeline) {\n\t\tconst module = device.createShaderModule({\n\t\t\tlabel: `displacement_${format}.wgsl`,\n\t\t\tcode: DISPLACEMENT_MAP_SHADER\n\t\t});\n\t\tpipeline = createFullscreenPipeline(device, {\n\t\t\tlabel: `DisplacementMapPipeline_${format}`,\n\t\t\tmodule,\n\t\t\tlayouts: [\n\t\t\t\tlayouts.uniformLayout,\n\t\t\t\tlayouts.srcTextureLayout,\n\t\t\t\tlayouts.mapTextureLayout,\n\t\t\t\tlayouts.signalTextureLayout\n\t\t\t],\n\t\t\tformat\n\t\t});\n\t\tlayouts.pipelineCache.set(cacheKey, pipeline);\n\t}\n\treturn {\n\t\tpipeline,\n\t\tuniformLayout: layouts.uniformLayout,\n\t\tsrcTextureLayout: layouts.srcTextureLayout,\n\t\tmapTextureLayout: layouts.mapTextureLayout,\n\t\tsignalTextureLayout: layouts.signalTextureLayout\n\t};\n}\nfunction channelToFloat(ch) {\n\tswitch (ch) {\n\t\tcase \"Green\": return 1;\n\t\tcase \"Blue\": return 2;\n\t\tcase \"Alpha\": return 3;\n\t\tcase \"Luminance\": return 4;\n\t\tdefault: return 0;\n\t}\n}\nfunction wrapModeToFloat(mode) {\n\tswitch (mode) {\n\t\tcase \"Repeat\": return 1;\n\t\tcase \"Mirror\": return 2;\n\t\tdefault: return 0;\n\t}\n}\nfunction resolveSignalField(op, fieldName, defaultValue) {\n\tconst handleId = op[`${fieldName}HandleId`];\n\tconst signalInput = handleId ? op.inputs?.[handleId] : null;\n\tconst hasSignal = !!(signalInput?.connectionValid && signalInput.outputItem?.type === \"Signal\");\n\tconst sd = hasSignal ? signalInput?.outputItem?.data : null;\n\tlet value = defaultValue;\n\tif (!hasSignal) if (signalInput?.connectionValid && signalInput.outputItem?.type === \"Number\") value = clamp(Number(signalInput.outputItem.data ?? defaultValue), STRENGTH_MIN, STRENGTH_MAX);\n\telse value = Number(op[fieldName] ?? defaultValue);\n\telse if (sd && typeof sd === \"object\" && \"offset\" in sd) value = Number(sd.offset ?? defaultValue);\n\treturn {\n\t\thasSignal,\n\t\tsd,\n\t\tvalue\n\t};\n}\nconst DisplacementMapWebGPURenderer = async (args) => {\n\tconst { ctx, encoder, pass, targetTexture, targetWidth, targetHeight, props } = args;\n\tconst op = props.virtualMedia?.operation;\n\tif (op?.op !== \"DisplacementMap\" || !op) return;\n\tconst sourceMedia = props.virtualMedia.children?.[0];\n\tconst mapMedia = op.mapMedia;\n\tif (!sourceMedia || !mapMedia) return;\n\tpass.end();\n\tconst width = targetWidth;\n\tconst height = targetHeight;\n\tconst frame = props.frame ?? 0;\n\tconst fps = props.fps || 30;\n\tconst strengthXInfo = resolveSignalField(op, \"strengthX\", 50);\n\tconst strengthYInfo = resolveSignalField(op, \"strengthY\", 50);\n\tconst { texture: srcTex } = await drawChildToTexture(args, sourceMedia, { props: {\n\t\t...props,\n\t\tvirtualMedia: sourceMedia,\n\t\trenderId: `${props.renderId}-c0`\n\t} });\n\targs.pass.end();\n\tconst { texture: mapTex } = await drawChildToTexture(args, mapMedia, {\n\t\tprops: {\n\t\t\t...props,\n\t\t\tvirtualMedia: mapMedia,\n\t\t\trenderId: `${props.renderId}-c1`,\n\t\t\texcludeTextures: [...props.excludeTextures || [], srcTex]\n\t\t},\n\t\texclude: [srcTex]\n\t});\n\targs.pass.end();\n\tconst { pipeline, uniformLayout: uLayout, srcTextureLayout: srcLayout, mapTextureLayout: mapLayout, signalTextureLayout: sigLayout } = getDisplacementResources(ctx.device, ctx.renderer.format);\n\tconst sampler = ctx.renderer.samplerCache.getSampler(ctx.device);\n\tlet sigXView;\n\tlet sigYView;\n\tconst { elapsedSeconds, durationSeconds } = signalTiming(props, frame, fps);\n\tif (strengthXInfo.hasSignal && strengthXInfo.sd) sigXView = signalRegistry.getOrCreate2DTextureView(ctx.device, encoder, strengthXInfo.sd.nodeId ?? \"displace_sx_sig\", elapsedSeconds, durationSeconds, strengthXInfo.sd, width, height, props.renderId, frame, fps);\n\telse sigXView = signalRegistry.getDummy1x1TextureView(ctx.device);\n\tif (strengthYInfo.hasSignal && strengthYInfo.sd) sigYView = signalRegistry.getOrCreate2DTextureView(ctx.device, encoder, strengthYInfo.sd.nodeId ?? \"displace_sy_sig\", elapsedSeconds, durationSeconds, strengthYInfo.sd, width, height, props.renderId, frame, fps);\n\telse sigYView = signalRegistry.getDummy1x1TextureView(ctx.device);\n\tuniformData[0] = strengthXInfo.value;\n\tuniformData[1] = strengthYInfo.value;\n\tuniformData[2] = channelToFloat(op.xChannel);\n\tuniformData[3] = channelToFloat(op.yChannel);\n\tuniformData[4] = wrapModeToFloat(op.wrapMode);\n\tuniformData[5] = strengthXInfo.hasSignal ? 1 : 0;\n\tuniformData[6] = strengthYInfo.hasSignal ? 1 : 0;\n\tuniformData[7] = 0;\n\tconst uniformBuffer = ctx.renderer.getTemporaryBuffer(uniformData);\n\tconst outTex = ctx.renderer.getTemporaryTexture(width, height, [\n\t\tsrcTex,\n\t\tmapTex,\n\t\ttargetTexture,\n\t\t...props.excludeTextures || []\n\t]);\n\tdrawFullscreenPass(encoder, outTex.createView(), pipeline, [\n\t\tctx.device.createBindGroup({\n\t\t\tlayout: uLayout,\n\t\t\tentries: [{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: { buffer: uniformBuffer }\n\t\t\t}]\n\t\t}),\n\t\tctx.renderer.bindGroupCache.getBindGroup(ctx.device, srcLayout, srcTex, sampler),\n\t\tctx.renderer.bindGroupCache.getBindGroup(ctx.device, mapLayout, mapTex, sampler),\n\t\tctx.device.createBindGroup({\n\t\t\tlayout: sigLayout,\n\t\t\tentries: [\n\t\t\t\t{\n\t\t\t\t\tbinding: 0,\n\t\t\t\t\tresource: sigXView\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tbinding: 1,\n\t\t\t\t\tresource: sigYView\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tbinding: 2,\n\t\t\t\t\tresource: sampler\n\t\t\t\t}\n\t\t\t]\n\t\t})\n\t]);\n\tdrawTextureToTarget(args, outTex, op.opacity ?? 1);\n};\n\n//#endregion\n//#region src/renderers/index.ts\nvar renderers_default = defineRenderer({ WebGPURenderer: DisplacementMapWebGPURenderer });\n\n//#endregion\nexport { renderers_default as t };\n//# sourceMappingURL=renderers-DzMCAYTg.mjs.map"],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { n as defineRenderer, o as resolveHandleParam } from "./renderer-C9r0-KY4.mjs";
|
|
2
|
-
import { a as configBuilder, i as VideoResultSchema, n as ImageResultSchema } from "./dist-
|
|
3
|
-
import { a as createFullscreenPipeline, c as drawTextureToTarget, d as signalTextureView, f as signalTiming, l as fragmentBindGroupLayout, o as drawChildToTexture, p as texturesLayout, s as drawFullscreenPass, t as FULLSCREEN_QUAD_WGSL } from "./gpu-
|
|
2
|
+
import { a as configBuilder, i as VideoResultSchema, n as ImageResultSchema } from "./dist-BPOi5xb_.mjs";
|
|
3
|
+
import { a as createFullscreenPipeline, c as drawTextureToTarget, d as signalTextureView, f as signalTiming, l as fragmentBindGroupLayout, o as drawChildToTexture, p as texturesLayout, s as drawFullscreenPass, t as FULLSCREEN_QUAD_WGSL } from "./gpu-CevnCkZC.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
|
|
6
6
|
//#region ../../nodes/node-high-pass/dist/renderers-DUL8Peaw.mjs
|
|
@@ -253,4 +253,4 @@ var renderers_default = defineRenderer({ WebGPURenderer });
|
|
|
253
253
|
|
|
254
254
|
//#endregion
|
|
255
255
|
export { renderers_default as t };
|
|
256
|
-
//# sourceMappingURL=renderer-
|
|
256
|
+
//# sourceMappingURL=renderer-5-9DPis6.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"renderer-B4blg067.mjs","names":[],"sources":["../../../nodes/node-high-pass/dist/renderers-DUL8Peaw.mjs"],"sourcesContent":["import { defineRenderer } from \"@framefields/node-sdk/renderer\";\nimport { ImageResultSchema, VideoResultSchema, configBuilder, resolveHandleParam } from \"@framefields/node-sdk\";\nimport { FULLSCREEN_QUAD_WGSL, createFullscreenPipeline, drawChildToTexture, drawFullscreenPass, drawTextureToTarget, fragmentBindGroupLayout, signalTextureView, signalTiming, texturesLayout } from \"@framefields/node-sdk/gpu\";\nimport { z } from \"zod\";\n\n//#region src/shared/config.ts\nconst MAX_RADIUS = 250;\nconst highPassConfig = configBuilder().field(\"radius\", z.number().min(.1).max(MAX_RADIUS).default(3), {\n\tbindable: true,\n\tdataTypes: [\"Number\", \"Signal\"],\n\tlabel: \"Radius Signal\",\n\tdescription: \"The cutoff radius in pixels. Frequencies smaller than this radius are preserved.\"\n}).field(\"contrastBoost\", z.number().multipleOf(.01).min(1).max(10).default(1), {\n\tbindable: true,\n\tdataTypes: [\"Number\", \"Signal\"],\n\tlabel: \"Contrast Boost Signal\",\n\tdescription: \"Multiplier applied to the extracted high-frequency detail delta.\"\n}).field(\"monochrome\", z.boolean().default(true), { description: \"When enabled, high-pass delta is converted to luminance before centering on 50% neutral gray.\" }).build();\nconst HighPassNodeConfigSchema = highPassConfig.schema;\nconst HighPassResultSchema = z.union([ImageResultSchema, VideoResultSchema]);\nconst HIGH_PASS_OUTPUT_TYPE_MAP = {\n\tVideo: \"Video\",\n\tLottie: \"Video\",\n\tGIF: \"GIF\",\n\tImage: \"Image\",\n\tSVG: \"Image\"\n};\n\n//#endregion\n//#region src/renderers/webgpu-renderer.ts\nconst WGSL_HIGHPASS_UNIFORMS = `\nstruct HighPassUniforms {\n\tdirX : f32,\n\tdirY : f32,\n\tradius : f32,\n\tcontrastBoost : f32,\n\n\tmonochrome : f32,\n\thasRadiusSig : f32,\n\thasContrastSig : f32,\n\tisSecondPass : f32,\n};\n\n@group(0) @binding(0) var<uniform> u : HighPassUniforms;\n@group(1) @binding(0) var tex : texture_2d<f32>;\n@group(1) @binding(1) var samp : sampler;\n@group(1) @binding(2) var origTex : texture_2d<f32>;\n\n@group(2) @binding(0) var radiusSigTex : texture_2d<f32>;\n@group(2) @binding(1) var contrastSigTex : texture_2d<f32>;\n@group(2) @binding(2) var signalSamp : sampler;\n${FULLSCREEN_QUAD_WGSL}\n\n@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {\n\tlet dimensions = vec2<f32>(textureDimensions(tex));\n\tvar radius = u.radius;\n\tif (u.hasRadiusSig > 0.5) {\n\t\tradius = textureSampleLevel(radiusSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;\n\t}\n\tvar contrastBoost = u.contrastBoost;\n\tif (u.hasContrastSig > 0.5) {\n\t\tcontrastBoost = textureSampleLevel(contrastSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;\n\t}\n\n\tlet origCol = textureSampleLevel(origTex, samp, in.uv, 0.0);\n\tif (radius <= 0.0) {\n\t\tif (u.isSecondPass > 0.5) {\n\t\t\treturn vec4<f32>(vec3<f32>(0.5) * origCol.a, origCol.a);\n\t\t}\n\t\treturn origCol;\n\t}\n\n\tlet texelSize = 1.0 / dimensions;\n\tlet spatial_sigma = radius;\n\tlet stepScale = max(1.0, spatial_sigma / 20.0);\n\tlet effectiveSigma = spatial_sigma / stepScale;\n\tlet sampleRadius = min(i32(ceil(3.0 * effectiveSigma)), 64);\n\tlet direction = vec2<f32>(u.dirX, u.dirY);\n\n\tvar blurred = vec4<f32>(0.0);\n\tvar totalWeight = 0.0;\n\n\tfor (var i = -sampleRadius; i <= sampleRadius; i++) {\n\t\tlet offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;\n\t\tlet x = f32(i) * stepScale;\n\t\tlet weight = exp(-0.5 * (x / spatial_sigma) * (x / spatial_sigma));\n\t\tblurred += textureSampleLevel(tex, samp, in.uv + offset, 0.0) * weight;\n\t\ttotalWeight += weight;\n\t}\n\n\tlet blurResult = blurred / max(totalWeight, 1e-5);\n\n\tif (u.isSecondPass <= 0.5) {\n\t\t// Pass 1: Intermediate horizontal blur\n\t\treturn blurResult;\n\t}\n\n\t// Pass 2: Vertical blur complete -> Calculate High Pass difference against original\n\tif (origCol.a < 1e-5) {\n\t\treturn vec4<f32>(0.0, 0.0, 0.0, 0.0);\n\t}\n\n\tlet origUnpremult = origCol.rgb / origCol.a;\n\tlet blurUnpremult = blurResult.rgb / max(blurResult.a, 1e-5);\n\tlet diff = origUnpremult - blurUnpremult;\n\n\tvar highPassRgb : vec3<f32>;\n\tif (u.monochrome > 0.5) {\n\t\tlet lumaDiff = dot(diff, vec3<f32>(0.2126, 0.7152, 0.0722));\n\t\thighPassRgb = vec3<f32>(0.5) + vec3<f32>(lumaDiff * contrastBoost);\n\t} else {\n\t\thighPassRgb = vec3<f32>(0.5) + diff * contrastBoost;\n\t}\n\n\tlet clampedRgb = clamp(highPassRgb, vec3<f32>(0.0), vec3<f32>(1.0));\n\treturn vec4<f32>(clampedRgb * origCol.a, origCol.a);\n}\n`;\nconst deviceResourceCache = /* @__PURE__ */ new WeakMap();\nconst highPassUniformData = new Float32Array(8);\nfunction getHighPassResources(device, targetFormat) {\n\tconst cached = deviceResourceCache.get(device);\n\tif (cached) return cached;\n\tconst highPassUniformLayout = fragmentBindGroupLayout(device, [\"uniform\"]);\n\tconst singleTextureLayout = fragmentBindGroupLayout(device, [\n\t\t\"texture\",\n\t\t\"sampler\",\n\t\t\"texture\"\n\t]);\n\tconst signalTextureLayout = texturesLayout(device, 2);\n\tconst res = {\n\t\thighPassUniformLayout,\n\t\tsingleTextureLayout,\n\t\tsignalTextureLayout,\n\t\thighPassPipeline: createFullscreenPipeline(device, {\n\t\t\tmodule: device.createShaderModule({ code: WGSL_HIGHPASS_UNIFORMS }),\n\t\t\tlayouts: device.createPipelineLayout({ bindGroupLayouts: [\n\t\t\t\thighPassUniformLayout,\n\t\t\t\tsingleTextureLayout,\n\t\t\t\tsignalTextureLayout\n\t\t\t] }),\n\t\t\tformat: targetFormat\n\t\t})\n\t};\n\tdeviceResourceCache.set(device, res);\n\treturn res;\n}\nconst WebGPURenderer = async (args) => {\n\tconst { ctx, encoder, pass, targetTexture, targetWidth, targetHeight, props } = args;\n\tconst op = props.virtualMedia?.operation;\n\tif (op?.op !== \"HighPass\" || !op) return;\n\tconst childMedia = props.virtualMedia.children?.[0];\n\tif (!childMedia) return;\n\tpass.end();\n\tconst frame = props.frame ?? 0;\n\tconst fps = props.fps || 30;\n\tconst width = targetWidth;\n\tconst height = targetHeight;\n\tconst nodeId = op.inputs ? Object.keys(op.inputs)[0] : \"highpass_node\";\n\tconst resolveField = (handleId, fallbackVal, clampMin = 0, clampMax = 1e3) => resolveHandleParam(op.inputs, handleId, fallbackVal, clampMin, clampMax);\n\tconst radiusRes = resolveField(op.radiusHandleId, Number(op.radius ?? 3), .1, MAX_RADIUS);\n\tconst contrastRes = resolveField(op.contrastBoostHandleId, Number(op.contrastBoost ?? 1), 1, 10);\n\tconst { texture: tmpTex } = await drawChildToTexture(args, childMedia);\n\tconst { highPassPipeline: pipeline, highPassUniformLayout: uLayout, singleTextureLayout: tLayout, signalTextureLayout: sigLayout } = getHighPassResources(ctx.device, ctx.renderer.format);\n\tconst sampler = ctx.renderer.samplerCache.getSampler(ctx.device);\n\tconst { elapsedSeconds, durationSeconds } = signalTiming(props, frame, fps);\n\tconst signalContext = {\n\t\targs,\n\t\twidth,\n\t\theight,\n\t\tframe,\n\t\tfps,\n\t\telapsedSeconds,\n\t\tdurationSeconds\n\t};\n\tconst getSignalView = (res, suffix) => signalTextureView(signalContext, res, `${nodeId}_${suffix}`);\n\tconst radiusView = getSignalView(radiusRes, \"radius_sig\");\n\tconst contrastView = getSignalView(contrastRes, \"contrast_sig\");\n\tconst createHighPassBindGroup1 = (inputTex, origTexture) => ctx.device.createBindGroup({\n\t\tlayout: tLayout,\n\t\tentries: [\n\t\t\t{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: inputTex.createView()\n\t\t\t},\n\t\t\t{\n\t\t\t\tbinding: 1,\n\t\t\t\tresource: sampler\n\t\t\t},\n\t\t\t{\n\t\t\t\tbinding: 2,\n\t\t\t\tresource: origTexture.createView()\n\t\t\t}\n\t\t]\n\t});\n\thighPassUniformData[2] = radiusRes.val;\n\thighPassUniformData[3] = contrastRes.val;\n\thighPassUniformData[4] = op.monochrome ?? true ? 1 : 0;\n\thighPassUniformData[5] = radiusRes.hasStaticSig ? 1 : 0;\n\thighPassUniformData[6] = contrastRes.hasStaticSig ? 1 : 0;\n\thighPassUniformData[7] = 0;\n\tconst outTex1 = ctx.renderer.getTemporaryTexture(width, height, [\n\t\ttmpTex,\n\t\ttargetTexture,\n\t\t...props.excludeTextures || []\n\t]);\n\tconst outTex2 = ctx.renderer.getTemporaryTexture(width, height, [\n\t\ttmpTex,\n\t\toutTex1,\n\t\ttargetTexture,\n\t\t...props.excludeTextures || []\n\t]);\n\tconst sigBindGroup = ctx.device.createBindGroup({\n\t\tlayout: sigLayout,\n\t\tentries: [\n\t\t\t{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: radiusView\n\t\t\t},\n\t\t\t{\n\t\t\t\tbinding: 1,\n\t\t\t\tresource: contrastView\n\t\t\t},\n\t\t\t{\n\t\t\t\tbinding: 2,\n\t\t\t\tresource: sampler\n\t\t\t}\n\t\t]\n\t});\n\thighPassUniformData[0] = 1;\n\thighPassUniformData[1] = 0;\n\thighPassUniformData[7] = 0;\n\tconst hBuffer = ctx.renderer.getTemporaryBuffer(highPassUniformData);\n\tdrawFullscreenPass(encoder, outTex1.createView(), pipeline, [\n\t\tctx.device.createBindGroup({\n\t\t\tlayout: uLayout,\n\t\t\tentries: [{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: { buffer: hBuffer }\n\t\t\t}]\n\t\t}),\n\t\tcreateHighPassBindGroup1(tmpTex, tmpTex),\n\t\tsigBindGroup\n\t]);\n\thighPassUniformData[0] = 0;\n\thighPassUniformData[1] = 1;\n\thighPassUniformData[7] = 1;\n\tconst vBuffer = ctx.renderer.getTemporaryBuffer(highPassUniformData);\n\tdrawFullscreenPass(encoder, outTex2.createView(), pipeline, [\n\t\tctx.device.createBindGroup({\n\t\t\tlayout: uLayout,\n\t\t\tentries: [{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: { buffer: vBuffer }\n\t\t\t}]\n\t\t}),\n\t\tcreateHighPassBindGroup1(outTex1, tmpTex),\n\t\tsigBindGroup\n\t]);\n\tdrawTextureToTarget(args, outTex2, op.opacity ?? 1);\n};\n\n//#endregion\n//#region src/renderers/index.ts\nvar renderers_default = defineRenderer({ WebGPURenderer });\n\n//#endregion\nexport { MAX_RADIUS as a, HighPassResultSchema as i, HIGH_PASS_OUTPUT_TYPE_MAP as n, highPassConfig as o, HighPassNodeConfigSchema as r, renderers_default as t };\n//# sourceMappingURL=renderers-DUL8Peaw.mjs.map"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"renderer-5-9DPis6.mjs","names":[],"sources":["../../../nodes/node-high-pass/dist/renderers-DUL8Peaw.mjs"],"sourcesContent":["import { defineRenderer } from \"@framefields/node-sdk/renderer\";\nimport { ImageResultSchema, VideoResultSchema, configBuilder, resolveHandleParam } from \"@framefields/node-sdk\";\nimport { FULLSCREEN_QUAD_WGSL, createFullscreenPipeline, drawChildToTexture, drawFullscreenPass, drawTextureToTarget, fragmentBindGroupLayout, signalTextureView, signalTiming, texturesLayout } from \"@framefields/node-sdk/gpu\";\nimport { z } from \"zod\";\n\n//#region src/shared/config.ts\nconst MAX_RADIUS = 250;\nconst highPassConfig = configBuilder().field(\"radius\", z.number().min(.1).max(MAX_RADIUS).default(3), {\n\tbindable: true,\n\tdataTypes: [\"Number\", \"Signal\"],\n\tlabel: \"Radius Signal\",\n\tdescription: \"The cutoff radius in pixels. Frequencies smaller than this radius are preserved.\"\n}).field(\"contrastBoost\", z.number().multipleOf(.01).min(1).max(10).default(1), {\n\tbindable: true,\n\tdataTypes: [\"Number\", \"Signal\"],\n\tlabel: \"Contrast Boost Signal\",\n\tdescription: \"Multiplier applied to the extracted high-frequency detail delta.\"\n}).field(\"monochrome\", z.boolean().default(true), { description: \"When enabled, high-pass delta is converted to luminance before centering on 50% neutral gray.\" }).build();\nconst HighPassNodeConfigSchema = highPassConfig.schema;\nconst HighPassResultSchema = z.union([ImageResultSchema, VideoResultSchema]);\nconst HIGH_PASS_OUTPUT_TYPE_MAP = {\n\tVideo: \"Video\",\n\tLottie: \"Video\",\n\tGIF: \"GIF\",\n\tImage: \"Image\",\n\tSVG: \"Image\"\n};\n\n//#endregion\n//#region src/renderers/webgpu-renderer.ts\nconst WGSL_HIGHPASS_UNIFORMS = `\nstruct HighPassUniforms {\n\tdirX : f32,\n\tdirY : f32,\n\tradius : f32,\n\tcontrastBoost : f32,\n\n\tmonochrome : f32,\n\thasRadiusSig : f32,\n\thasContrastSig : f32,\n\tisSecondPass : f32,\n};\n\n@group(0) @binding(0) var<uniform> u : HighPassUniforms;\n@group(1) @binding(0) var tex : texture_2d<f32>;\n@group(1) @binding(1) var samp : sampler;\n@group(1) @binding(2) var origTex : texture_2d<f32>;\n\n@group(2) @binding(0) var radiusSigTex : texture_2d<f32>;\n@group(2) @binding(1) var contrastSigTex : texture_2d<f32>;\n@group(2) @binding(2) var signalSamp : sampler;\n${FULLSCREEN_QUAD_WGSL}\n\n@fragment fn fs(in : VSOut) -> @location(0) vec4<f32> {\n\tlet dimensions = vec2<f32>(textureDimensions(tex));\n\tvar radius = u.radius;\n\tif (u.hasRadiusSig > 0.5) {\n\t\tradius = textureSampleLevel(radiusSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;\n\t}\n\tvar contrastBoost = u.contrastBoost;\n\tif (u.hasContrastSig > 0.5) {\n\t\tcontrastBoost = textureSampleLevel(contrastSigTex, signalSamp, vec2<f32>(0.5, 0.5), 0.0).r;\n\t}\n\n\tlet origCol = textureSampleLevel(origTex, samp, in.uv, 0.0);\n\tif (radius <= 0.0) {\n\t\tif (u.isSecondPass > 0.5) {\n\t\t\treturn vec4<f32>(vec3<f32>(0.5) * origCol.a, origCol.a);\n\t\t}\n\t\treturn origCol;\n\t}\n\n\tlet texelSize = 1.0 / dimensions;\n\tlet spatial_sigma = radius;\n\tlet stepScale = max(1.0, spatial_sigma / 20.0);\n\tlet effectiveSigma = spatial_sigma / stepScale;\n\tlet sampleRadius = min(i32(ceil(3.0 * effectiveSigma)), 64);\n\tlet direction = vec2<f32>(u.dirX, u.dirY);\n\n\tvar blurred = vec4<f32>(0.0);\n\tvar totalWeight = 0.0;\n\n\tfor (var i = -sampleRadius; i <= sampleRadius; i++) {\n\t\tlet offset = vec2<f32>(f32(i) * stepScale) * direction * texelSize;\n\t\tlet x = f32(i) * stepScale;\n\t\tlet weight = exp(-0.5 * (x / spatial_sigma) * (x / spatial_sigma));\n\t\tblurred += textureSampleLevel(tex, samp, in.uv + offset, 0.0) * weight;\n\t\ttotalWeight += weight;\n\t}\n\n\tlet blurResult = blurred / max(totalWeight, 1e-5);\n\n\tif (u.isSecondPass <= 0.5) {\n\t\t// Pass 1: Intermediate horizontal blur\n\t\treturn blurResult;\n\t}\n\n\t// Pass 2: Vertical blur complete -> Calculate High Pass difference against original\n\tif (origCol.a < 1e-5) {\n\t\treturn vec4<f32>(0.0, 0.0, 0.0, 0.0);\n\t}\n\n\tlet origUnpremult = origCol.rgb / origCol.a;\n\tlet blurUnpremult = blurResult.rgb / max(blurResult.a, 1e-5);\n\tlet diff = origUnpremult - blurUnpremult;\n\n\tvar highPassRgb : vec3<f32>;\n\tif (u.monochrome > 0.5) {\n\t\tlet lumaDiff = dot(diff, vec3<f32>(0.2126, 0.7152, 0.0722));\n\t\thighPassRgb = vec3<f32>(0.5) + vec3<f32>(lumaDiff * contrastBoost);\n\t} else {\n\t\thighPassRgb = vec3<f32>(0.5) + diff * contrastBoost;\n\t}\n\n\tlet clampedRgb = clamp(highPassRgb, vec3<f32>(0.0), vec3<f32>(1.0));\n\treturn vec4<f32>(clampedRgb * origCol.a, origCol.a);\n}\n`;\nconst deviceResourceCache = /* @__PURE__ */ new WeakMap();\nconst highPassUniformData = new Float32Array(8);\nfunction getHighPassResources(device, targetFormat) {\n\tconst cached = deviceResourceCache.get(device);\n\tif (cached) return cached;\n\tconst highPassUniformLayout = fragmentBindGroupLayout(device, [\"uniform\"]);\n\tconst singleTextureLayout = fragmentBindGroupLayout(device, [\n\t\t\"texture\",\n\t\t\"sampler\",\n\t\t\"texture\"\n\t]);\n\tconst signalTextureLayout = texturesLayout(device, 2);\n\tconst res = {\n\t\thighPassUniformLayout,\n\t\tsingleTextureLayout,\n\t\tsignalTextureLayout,\n\t\thighPassPipeline: createFullscreenPipeline(device, {\n\t\t\tmodule: device.createShaderModule({ code: WGSL_HIGHPASS_UNIFORMS }),\n\t\t\tlayouts: device.createPipelineLayout({ bindGroupLayouts: [\n\t\t\t\thighPassUniformLayout,\n\t\t\t\tsingleTextureLayout,\n\t\t\t\tsignalTextureLayout\n\t\t\t] }),\n\t\t\tformat: targetFormat\n\t\t})\n\t};\n\tdeviceResourceCache.set(device, res);\n\treturn res;\n}\nconst WebGPURenderer = async (args) => {\n\tconst { ctx, encoder, pass, targetTexture, targetWidth, targetHeight, props } = args;\n\tconst op = props.virtualMedia?.operation;\n\tif (op?.op !== \"HighPass\" || !op) return;\n\tconst childMedia = props.virtualMedia.children?.[0];\n\tif (!childMedia) return;\n\tpass.end();\n\tconst frame = props.frame ?? 0;\n\tconst fps = props.fps || 30;\n\tconst width = targetWidth;\n\tconst height = targetHeight;\n\tconst nodeId = op.inputs ? Object.keys(op.inputs)[0] : \"highpass_node\";\n\tconst resolveField = (handleId, fallbackVal, clampMin = 0, clampMax = 1e3) => resolveHandleParam(op.inputs, handleId, fallbackVal, clampMin, clampMax);\n\tconst radiusRes = resolveField(op.radiusHandleId, Number(op.radius ?? 3), .1, MAX_RADIUS);\n\tconst contrastRes = resolveField(op.contrastBoostHandleId, Number(op.contrastBoost ?? 1), 1, 10);\n\tconst { texture: tmpTex } = await drawChildToTexture(args, childMedia);\n\tconst { highPassPipeline: pipeline, highPassUniformLayout: uLayout, singleTextureLayout: tLayout, signalTextureLayout: sigLayout } = getHighPassResources(ctx.device, ctx.renderer.format);\n\tconst sampler = ctx.renderer.samplerCache.getSampler(ctx.device);\n\tconst { elapsedSeconds, durationSeconds } = signalTiming(props, frame, fps);\n\tconst signalContext = {\n\t\targs,\n\t\twidth,\n\t\theight,\n\t\tframe,\n\t\tfps,\n\t\telapsedSeconds,\n\t\tdurationSeconds\n\t};\n\tconst getSignalView = (res, suffix) => signalTextureView(signalContext, res, `${nodeId}_${suffix}`);\n\tconst radiusView = getSignalView(radiusRes, \"radius_sig\");\n\tconst contrastView = getSignalView(contrastRes, \"contrast_sig\");\n\tconst createHighPassBindGroup1 = (inputTex, origTexture) => ctx.device.createBindGroup({\n\t\tlayout: tLayout,\n\t\tentries: [\n\t\t\t{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: inputTex.createView()\n\t\t\t},\n\t\t\t{\n\t\t\t\tbinding: 1,\n\t\t\t\tresource: sampler\n\t\t\t},\n\t\t\t{\n\t\t\t\tbinding: 2,\n\t\t\t\tresource: origTexture.createView()\n\t\t\t}\n\t\t]\n\t});\n\thighPassUniformData[2] = radiusRes.val;\n\thighPassUniformData[3] = contrastRes.val;\n\thighPassUniformData[4] = op.monochrome ?? true ? 1 : 0;\n\thighPassUniformData[5] = radiusRes.hasStaticSig ? 1 : 0;\n\thighPassUniformData[6] = contrastRes.hasStaticSig ? 1 : 0;\n\thighPassUniformData[7] = 0;\n\tconst outTex1 = ctx.renderer.getTemporaryTexture(width, height, [\n\t\ttmpTex,\n\t\ttargetTexture,\n\t\t...props.excludeTextures || []\n\t]);\n\tconst outTex2 = ctx.renderer.getTemporaryTexture(width, height, [\n\t\ttmpTex,\n\t\toutTex1,\n\t\ttargetTexture,\n\t\t...props.excludeTextures || []\n\t]);\n\tconst sigBindGroup = ctx.device.createBindGroup({\n\t\tlayout: sigLayout,\n\t\tentries: [\n\t\t\t{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: radiusView\n\t\t\t},\n\t\t\t{\n\t\t\t\tbinding: 1,\n\t\t\t\tresource: contrastView\n\t\t\t},\n\t\t\t{\n\t\t\t\tbinding: 2,\n\t\t\t\tresource: sampler\n\t\t\t}\n\t\t]\n\t});\n\thighPassUniformData[0] = 1;\n\thighPassUniformData[1] = 0;\n\thighPassUniformData[7] = 0;\n\tconst hBuffer = ctx.renderer.getTemporaryBuffer(highPassUniformData);\n\tdrawFullscreenPass(encoder, outTex1.createView(), pipeline, [\n\t\tctx.device.createBindGroup({\n\t\t\tlayout: uLayout,\n\t\t\tentries: [{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: { buffer: hBuffer }\n\t\t\t}]\n\t\t}),\n\t\tcreateHighPassBindGroup1(tmpTex, tmpTex),\n\t\tsigBindGroup\n\t]);\n\thighPassUniformData[0] = 0;\n\thighPassUniformData[1] = 1;\n\thighPassUniformData[7] = 1;\n\tconst vBuffer = ctx.renderer.getTemporaryBuffer(highPassUniformData);\n\tdrawFullscreenPass(encoder, outTex2.createView(), pipeline, [\n\t\tctx.device.createBindGroup({\n\t\t\tlayout: uLayout,\n\t\t\tentries: [{\n\t\t\t\tbinding: 0,\n\t\t\t\tresource: { buffer: vBuffer }\n\t\t\t}]\n\t\t}),\n\t\tcreateHighPassBindGroup1(outTex1, tmpTex),\n\t\tsigBindGroup\n\t]);\n\tdrawTextureToTarget(args, outTex2, op.opacity ?? 1);\n};\n\n//#endregion\n//#region src/renderers/index.ts\nvar renderers_default = defineRenderer({ WebGPURenderer });\n\n//#endregion\nexport { MAX_RADIUS as a, HighPassResultSchema as i, HIGH_PASS_OUTPUT_TYPE_MAP as n, highPassConfig as o, HighPassNodeConfigSchema as r, renderers_default as t };\n//# sourceMappingURL=renderers-DUL8Peaw.mjs.map"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Ea as OutputItem,
|
|
1
|
+
import { Ea as OutputItem, Yi as MediaOperation, ko as VirtualMediaData, x as RenderContextValue } from "./index-CZK6CFLy.mjs";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
|
|
4
4
|
//#region ../node-sdk/dist/index-Dgzr2Wnb.d.mts
|
|
@@ -79,4 +79,4 @@ declare const SignalResultSchema: z.ZodObject<{
|
|
|
79
79
|
}, z.core.$strip>;
|
|
80
80
|
//#endregion
|
|
81
81
|
export { NodeRendererPlugin as n, WebGPUNodeRenderer as r, SignalResultSchema as t };
|
|
82
|
-
//# sourceMappingURL=renderer-
|
|
82
|
+
//# sourceMappingURL=renderer-92IAZzBU.d.mts.map
|