@hadoku/watchparty-ui 0.44.20 → 0.45.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/chunks/{AdminPanel-CYbxabgY.js → AdminPanel-BGVD6Qlb.js} +4 -4
  2. package/dist/chunks/{AdminPanel-CYbxabgY.js.map → AdminPanel-BGVD6Qlb.js.map} +1 -1
  3. package/dist/chunks/{GameModule-Df1rYrJo.js → GameModule-Dx3GHtRk.js} +244 -247
  4. package/dist/chunks/GameModule-Dx3GHtRk.js.map +1 -0
  5. package/dist/chunks/{MapEditor-DgbOaCT2.js → MapEditor-yvJbXXgw.js} +319 -325
  6. package/dist/chunks/MapEditor-yvJbXXgw.js.map +1 -0
  7. package/dist/chunks/{MediaDrawer-Da6-fLeV.js → MediaDrawer-DLFj0-te.js} +293 -297
  8. package/dist/chunks/MediaDrawer-DLFj0-te.js.map +1 -0
  9. package/dist/chunks/{PlaylistPanel-fy0YV4nH.js → PlaylistPanel-B0SWGlFy.js} +124 -126
  10. package/dist/chunks/PlaylistPanel-B0SWGlFy.js.map +1 -0
  11. package/dist/chunks/Room-BVyJK3zM.js +6165 -0
  12. package/dist/chunks/Room-BVyJK3zM.js.map +1 -0
  13. package/dist/chunks/{index-B92W398r.js → index-DOLbMWw6.js} +1626 -1618
  14. package/dist/chunks/index-DOLbMWw6.js.map +1 -0
  15. package/dist/chunks/{submissionsApi-CHcpF-It.js → submissionsApi-CXZf_xlm.js} +2 -2
  16. package/dist/chunks/{submissionsApi-CHcpF-It.js.map → submissionsApi-CXZf_xlm.js.map} +1 -1
  17. package/dist/index.d.ts +11 -2
  18. package/dist/index.js +1 -1
  19. package/dist/room-chrome.js +3 -2
  20. package/dist/room-chrome.js.map +1 -1
  21. package/dist/watchparty-ui.css +1 -1
  22. package/package.json +2 -2
  23. package/dist/chunks/GameModule-Df1rYrJo.js.map +0 -1
  24. package/dist/chunks/MapEditor-DgbOaCT2.js.map +0 -1
  25. package/dist/chunks/MediaDrawer-Da6-fLeV.js.map +0 -1
  26. package/dist/chunks/PlaylistPanel-fy0YV4nH.js.map +0 -1
  27. package/dist/chunks/Room-Ci6DN1Oi.js +0 -6137
  28. package/dist/chunks/Room-Ci6DN1Oi.js.map +0 -1
  29. package/dist/chunks/index-B92W398r.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"MapEditor-DgbOaCT2.js","sources":["../../src/assets/games/bomberman/tiles/floor.png","../../src/assets/games/bomberman/tiles/soft_block.png","../../src/assets/games/bomberman/tiles/wall_cap_e.png","../../src/assets/games/bomberman/tiles/wall_cap_n.png","../../src/assets/games/bomberman/tiles/wall_cap_s.png","../../src/assets/games/bomberman/tiles/wall_cap_w.png","../../src/assets/games/bomberman/tiles/wall_corner_ne.png","../../src/assets/games/bomberman/tiles/wall_corner_nw.png","../../src/assets/games/bomberman/tiles/wall_corner_se.png","../../src/assets/games/bomberman/tiles/wall_corner_sw.png","../../src/assets/games/bomberman/tiles/wall_cross.png","../../src/assets/games/bomberman/tiles/wall_h.png","../../src/assets/games/bomberman/tiles/wall_iso.png","../../src/assets/games/bomberman/tiles/wall_t_e.png","../../src/assets/games/bomberman/tiles/wall_t_n.png","../../src/assets/games/bomberman/tiles/wall_t_s.png","../../src/assets/games/bomberman/tiles/wall_t_w.png","../../src/assets/games/bomberman/tiles/wall_v.png","../../src/lib/mapEditorGrid.ts","../../src/routes/MapEditor.tsx"],"sourcesContent":["export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAA4ElEQVR4nO3bvQrCMAAAYRUnwcFn7PvjILjq4JalCDYf0ru5P8eRhNCf47Isr8NEno/7V8dfrreNTD6cNr36H1AALaA5z77hOKfHNWHrOT+y+xFQAC2gKYAW0BRAC2gKoAU0BdACmgJoAU0BtICmAFpAUwAtoCmAFtAUQAtoCqAFNAXQAprp7wZHZr8LHNn9CCiAFtAUQAtoCqAFNAXQApoCaAFNAbSApgBaQMOfB/StMKYAWkBTAC2gKYAW0EzfB6z9Nzh7X7D7EVAALaDZfA349l/hX5+/xu5HQAG0gOYNTj4XivrBP34AAAAASUVORK5CYII=\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAIGNIUk0AAHomAACAhAAA+gAAAIDoAAB1MAAA6mAAADqYAAAXcJy6UTwAAAAVUExURWY5MXlWQkUoPJVoVKx3USYjOv///88sd78AAAABYktHRAZhZrh9AAAAB3RJTUUH6gUWFB4FeZ3ViwAAAHVJREFUSMflk8kNwCAMBLcFWnALtEAL6b+VgMyKCHG8skHKfMBoHsYHgJABrIPvkAhxwZ+EE3qRHnhqLf5CoEJJJZzRi83ASIS5ohHMwoJcMIkAeKvGp0bwZAockRYphavS35UCl8XXZfqL1wWWiLFawACZcANvCQaAiP6VCQAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyNi0wNS0yMlQyMDoyMTo0MSswMDowMGeGF3AAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjYtMDUtMjJUMjA6MjE6NDErMDA6MDAW26/MAAAAKHRFWHRkYXRlOnRpbWVzdGFtcAAyMDI2LTA1LTIyVDIwOjMwOjA1KzAwOjAwH4cQBAAAAABJRU5ErkJggg==\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABXUlEQVR4nO2awUrDUBBFrcxKUOjChboSBIXuBBcFv7q/IeJKMSC4aiMILWgo7ux6RrA8THvsm3t2MwRyc7mZvCRvcH07/t6riK4dFB2/vyEdO4MMoAXQ2OXpiWscHA5dfX52sU09vfP4fOfqZta6On0CZAAtgMZqu+cjo6sbVzeziavTJ0AG0AJoZAAtgEYG0AJorLbnfinpEyADaAE0MoAWQCMDaAE09jp9cY1s64L0CZABtAAaW34uXKO2mRCvJ5I+ATKAFkBjsRFnQvy39t+J/znWkT4BMoAWQPNjBuw6cYbFmRD3EKVPgAygBdBUNwMiD81T6GgGOGQALYDG3j8Wvx5wfFS2ti6l7/PHfYDrSJ8AGUALoLFN3+Ol/FVP3PscuW/fXJ0+ATKAFkCz9XeB+Nyn1xnpEyADaAE01X8PmHdfoaPvAQ4ZQAugsdL3576Zd+z50ydABtACaFan50Bii49pkgAAAABJRU5ErkJggg==\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABV0lEQVR4nO2aPWsCQRRFY9gqJIKFhbGQfBDBLm3+f5ciVYKCKCk2FhaBKLZavzvFMmziyTL3dE+W5bzLm3HY3d7Tw8vxIoPrUdblZ2e/6WVdf/lHHp3BAdACNJWu6efpLNR348dz+vw663oZ6rfFR6iLnwAHQAvQVPpD19e8ov14DxAcAC1A4wBoARoHQAvQOABagMYB0AI0DoAWoHEAtACNA6AFaBwALUBT6bs0fY7e9WeE2o/2W/wEOABagCZ5L3DYfYf6ff6adcOrm0E7owbUry3FT4ADoAVoku8DlO1PXHOT8X2o6XOC/s9/1qtQD/txT9J+i58AB0AL0CTngMXXJtTT21Go6TWvqI+eE7QfpfgJcAC0AE2yB+ia7zraj+4JxU+AA6AFaBwALUDjAGgBGgdAC9A4AFqAxgHQAjQOgBagSZ4HNPHfvh9Qn1yKnwAHQAvQnAB0ZD6cfejf/wAAAABJRU5ErkJggg==\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABgUlEQVR4nO2aMUsDQRCFL5JCRIUIFmolCBHsBAvBX+3fELFSPBCszAlCAhqCnbbOLGQZvLvvwr6vm2RZ3j5m5vb2dnR5c/1T/WF6fFRF2NmbhMa3zeprERpfzxoTb7UpZhORAbQAmnFuwMX5VR86OuPx+W7t/8VngAygBdAkPWDTa97j11PPbk1cfAbIAFoAjQygBdDIAFoAjQygBdDIAFoAjQygBdDIAFoAjQygBdDIAFoATXIm+Pr2YuLTk7PexHSBX4+n+AyQAbQAmuy3QV9D0e/xfRO9r1B8BsgAWgDNeNmM7C9TGw695j1er+8Jfr3FZ4AMoAXQJPuAh/rJxNF7g0PDr6eq1AMMMoAWQJN9F/B3a9vuCX5+z8HutokP99fv9XPzeYrPABlAC6BJekDfz/1ojefI6b9v3k1cfAbIAFoATXYf0DYfn8M6Xyg+A2QALYAm6QG+Rv/7XM7R9fy5nlN8BsgAWgBN0gPmy28Xx96vo3Q9f4rOBA0ygBZA8wuEsEaUV+FlQgAAAABJRU5ErkJggg==\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABUklEQVR4nO2bv4vCQBBGL0cKCxEULNRKlFOwvub+/95OEAQrtRAUPJHrznpmi7D442nme90ESR4fs5vNxhRfo5//jwyavayfvzyftACNAqAFaEo/pif9nqln0+9n+jyc9WZl6vAdoABoAZrSH6jbmPcMB2NTh+8ABUAL0CgAWoBGAdACNAqAFqBRALQAjQKgBWgUAC1AowBoARoFQAvQJHuCft/c76G9O3ov4FAAtABNMgd4/Ji5/B4fJkMQvgMUAC1AU553hT0ysWXdxrwnfAcoAFqAJlkHzJcLU/v/DNWN8B2gAGgBmspngeV2Z+pOs2Hqbqt9k8D+lLfOyL1e1fnDd4ACoAVokjmAvu/fOqfkni98BygAWoAmmQP8ffPeY/LVCN8BCoAWoEnmgMP5z9Td1n0v6J8t6HVH+A5QALQATeG/Ha7bt8FVhO8ABUAL0FwBfpsww2cW0LwAAAAASUVORK5CYII=\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABh0lEQVR4nO2asUvDQBTGL9JBpBYUOrROojSCm+Cg+P8vDk6KhUIn7SBU0CJudfXeYY/wkvxM3vtt1+Saj4/vLu8uV1zd3WzDL8rpJGg4ODxS9c/x9fmu6j9/XUXtPdW/9QA3gBZAM8jdcHlx3YaOxli+LKJ2OY2vm0+AG0ALoEnmgK6PecnpyXnUlnOC+QS4AbQAGjeAFkDjBtACaLJrgb4h6wLzCXADaAE0bgAtgMYNoAXQJHWAXC/L92bX8f0AgRtAC6DJrgW6PidI/fLbovkEuAG0AJrK+wGPz/dN6MAwnwA3gBZAM9isiviXMm5qz+TkkGd2tGeUqmI+AW4ALYAmqQMe5k9Ru+0x2facYD4BbgAtgKaYnd1GZ4WHk+1f94YQQjge7qseuN58q/pXJafXfALcAFoATVIHyDEzHtV7/n882n397WP32qNuPeYT4AbQAmj+/Rmhuse8xHwC3ABaAE0yB6S1evxebnpMyufn6gYt5hPgBtACaCrvB/QN8wlwA2gBND+5n0Dr51yy0gAAAABJRU5ErkJggg==\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABdUlEQVR4nO2bPUvEQBCGc7KV4IHFFWolCB7YCRbC/er7GxZWigHByrtCOEGDaHW2NxNwCfl4vMz7dLNhN29eZiebTTK5Xtxuix0uT0+KNhweHbfqn+Pr871V/3K1NvFBq9FGgAygBdAkP+f9HD4/uxhST+c8PN39eTx8BsgAWgBNGtuc91zNb0xcrpYmDp8BMoAWQCMDaAE0MoAWQJPGdt9vSvgMkAG0ABoZQAugkQG0AJr08vpsGsa2LvDX5wmfATKAFkCT/Lu2fa8JuTnvCZ8BMoAWQJN8g68JuXdr/42m3yeEzwAZQAugqdWAfcfXMF8TqvXExOEzQAbQAmjS24edM7Npv9/5Dc19+ehaVAMMMoAWQFNbB/ia4GlaI3LjdT3+pvpu1D98BsgAWgBN6vu+78fvet3h+8+m9rg/X1X8mDh8BsgAWgCNDKAF0MgAWgBN73uC9H5D/dlA+wEGGUALoEn+X9q+2VTDni9H+AyQAbQAml8slkzMIBNZowAAAABJRU5ErkJggg==\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABQ0lEQVR4nO2asQ4BQRRFka2EQolKiE10Wv/fKVTEJhIVSgXR0po7iY2sONbc071NVs7cvJnZjGlOx4t744lOPyj/nhYtQOMAaAGaTOf8PJ8F9Wg4+abPx9kfdkF9u5yDOvkOcAC0AE2mD+o+5xUdj64JyXeAA6AFaBwALUDjAGgBGgdAC9A4AFqAxgHQAjQOgBagic4D/h2fCQoOgBagya6nZvBAz8zqfkao41GS7wAHQAvQRN8Buk+ut8u3frDd7VUzKkH9qpJ8BzgAWoAmuh+gFMdTUP/a/QHd51fFJqjzQf/l+8l3gAOgBWii7wCd8zqH6DmvqI9+J+h4lOQ7wAHQAjTRGlC2b9YNHY+uCcl3gAOgBWgcAC1A4wBoARoHQAvQOABagMYB0AI0DoAWoHn7jtCv3R8o+/+/jOQ7wAHQAjQPMsI9FqsxS7oAAAAASUVORK5CYII=\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABQUlEQVR4nO2aoQoCQRRFVSaJBuNqEgTBpk3w/4thk+KCYFKjQbFqfneDCItHnXva27CcubwZhplpL1bLRysjbud2qDuQx9fgAGgBmjQdFuFDtz8I9Xg0+aRP4xyO+1CXrW2os+8AB0AL0KR/m/OKjqesvAYEHAAtQOMAaAEaB0AL0DgAWoDGAdACNA6AFqBxALQATbpfL7QDSvYd4ABoAZqkH/Qc/dfPCHU8vhsUHAAtQFNbA3RfsNmt3/qh3jM0TdP7luw7wAHQAjS1NUCpTudQz6ezUNP7hNr9v9z96fuHXhGfRGXfAQ6AFqBJOscVnUP0nFfUR/cJr8aXfQc4AFqAprYP0Dn/6+h4dE3IvgMcAC1A4wBoARoHQAvQOABagMYB0AI0DoAWoHEAtADNy3sB5dveD6jPu2TfAQ6AFqB5AmmUO6FIXs0fAAAAAElFTkSuQmCC\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABRklEQVR4nO2asWoCQRRFTZhKsEipqYSAEisDFoH8f5PCKkFBsDKWFoKttpm7xTDE7Fl993SzLMuZy3vD7O48vH28n3u/mIyGvRr6g6eq+6/N6Xioun/9s8/Gj9eUuUUcAC1Ak7TntafHzy9t+vw7/cEmG4evAAdAC9Cke+95RecXvgIcAC1Ak+6950uErwAHQAvQOABagMYB0AI0DoAWoHEAtABN2u7yb2TR3g3CV4ADoAVokv5bi7YmhK8AB0AL0CS9oGvC1+qz6oGz6eJvRgVqfUqErwAHQAvQNNYARc/UzCev2bjtfYKuMbpvWa6/s3HpzFP4CnAAtABN0h5XtIe69m6gPrqPKc0vfAU4AFqAprEPqD0r3HV0Pj4rLDgAWoDGAdACNA6AFqBxALQAjQOgBWgcAC1A4wBoARoHQAvQOABagOYCVgAynQlZGFoAAAAASUVORK5CYII=\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABOElEQVR4nO2bwQsBQRSH0RwkFLUHnJSi3JSD8v9fnBWlnHBQFJIb53lbatrsl97vu83s1n79evt2ZtutzpaLd8UxNVqARgHQAjRh3O9FE9PJHFIph/1hF43dV4ACoAVoQqPVoR1KZTgYRWP3FaAAaAGaYO8Jb7ivAAVAC9AoAFqARgHQAjQKgBagUQC0AE2w78i87Q3cV4ACoAVowvN+jSa89QT3FaAAaAGaYCdsT1hvVqXJELivAAVAC9AoAFqARgHQAjThfIuf+1nb1/cC7itAAdACNLm9gO0JlsvjlXSBbrP+9Xhqz9keT0nnW6yP+wpQALQATUi9B7N2PC57HWG/bS56ffcVoABoAZrcOuDfKNpz3FeAAqAFaJJ7gH3u2r2BXSf8Gq0DCqIAaAGaqv4ddo4CoAVoPpR0Me34gHFfAAAAAElFTkSuQmCC\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABeklEQVR4nO2asUoDQRRFN7KVaMAiRbQSxQjWNn62vyFipbggpDIrCAoaxE7ruVMMg7N7XOae7oUlOXN583ay7Ozs5OqnyWBvmXX56Gz7Wdb1OwN5TAYHQAvQtLqnV4fLoL44vxzTpzj3jzdB3W36oK6+AxwALUDT6gdT3/OKrqfbXAd19R3gAGgBGgdAC9A4AFqAxgHQAjQOgBagcQC0AI0DoAVooucB6+enoD4+Oh1NZgh0PUr1HeAAaAEaB0AL0DgAWoAmOgcoeh/9+nwfTKYEu/sHWddX3wEOgBagaaN3alZh+d/3vKK+OhN0vdV3gAOgBWiic8Bd9xDU+s7Q1ND1NI1nQIADoAVokv8F9L26v86E14+8c8Vinne2V98U1XeAA6AFaKIZMPR9X/e0zoTcPa+k/G/7l6CuvgMcAC1AE82A0nuSJnXuqL4DHAAtQBPNgLftd1Av5mV/sPR/ixS6Hj8PEBwALUCT/TygNEN/f4rqO8AB0AI0v8NqTSVA9X9QAAAAAElFTkSuQmCC\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABUElEQVR4nO2asWoCQRRF3TCVYJEyWogSSMAuYCH41f5GakFBDCnUIoWQiK22zhWyyIQc2XdP91ZWz17ejAvzqrfp5NS64KX71Cqh3Xksur+O48++6P7ldpfVD0Xf1gAcAC1Ak3TN6xoe9J7/0+fPmS/ef/08fAc4AFqAJjVtzSuj13FWL7ezrA7fAQ6AFqBxALQAjQOgBWhS0/73byV8BzgAWoDGAdACNA6AFqBJH5tVdiHae0H4DnAAtABN0rO2pu0J+jxK+A5wALQATdILuifUna0p9HxA3e8fdlVWh+8AB0AL0FztAcrXd77m+r1hVtPvCbpHfW7WWa2+SvgOcAC0AE2lc4KKzhDpefu9oXuCzgUq4TvAAdACNFfvAaWzwveGPo9nhQUHQAvQOABagMYB0AI0DoAWoHEAtACNA6AFaBwALUDjAGgBGgdAC9CcAYDuOTwDN8emAAAAAElFTkSuQmCC\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABNElEQVR4nO2aQQsBURRG0VspysKCWYgUZacslP+/sVaUyGLGwkJRtqzfnZppFubQ/c7uvaQzX9/c3hjN5Wb9bjimRQvQKABagCbMhoNoYzFfQSr1cE6P0dp9AxQALUAT2p0e7VAr42Qard03QAHQAjTB3hPecN8ABUAL0CgAWoBGAdACNAqAFqBRALQATbC/kXl7NnDfAAVAC9CE1/MebXibCe4boABoAZpgN+xM2O23lb7w2+8Wq/qU4b4BCoAWoMnNAMvtEc+EUTKJ1nWfE+yMseeWS3qK1v1u8btP9w1QALQATThk18IP2P8Q/dqzgfWx55iy63PfAAVAC9DkzgH2nv937PXYmeC+AQqAFqBRALQAjQKgBWgUAC1AowBoARoFQAvQKABagEYB0AI0CoAWoPkA1dkt6kUtkpYAAAAASUVORK5CYII=\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABUUlEQVR4nO2bMWvCQBiGVW4owQgtZFCnQqF7N6H/f3EWFAQndRAstKV0a1fvyyAf0Twk3/tsJwc+vrx3JvEcvr0v/gYXvM6mAw9F+eiaf2t+vj5c8zeHYzYe3VKmiygAWoAm2TVv1/Tz/KVNn7tTlNtsHL4BCoAWoEl9X/MW+/nCN0AB0AI0qe9r/hrhG6AAaAEaBUAL0CgAWoBGAdACNAqAFqBJu33+jCzavUH4BigAWoAm2d/Wou0J4RugAGgBmmRfsHvCar1sTYYgfAMUAC1AowBoARoFQAvQJHtuzntOsOuEb4ACoAVoavcCdk/w8jR+cM2vJr6zxl6/az7hG6AAaAGa2llhL6fP/PmBd017sb5Nr2PCN0AB0AI0teuAruG97rCEb4ACoAVo3HuA/d4/f/9m42rSTMhL0/cP3wAFQAvQDO1/h6MRvgEKgBag+Qfu+jOSBU5GcAAAAABJRU5ErkJggg==\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABOklEQVR4nO2aMQvCMBQGrTgVKjg4qJMgKLgJDoL/f3EWFERxqI4OiquuvtchQ6BXm++2iJXrkRRJk622m0/nh/l41IkhLwZR14d4Px9R1x9vdzPuRv1aC1AAWoCmF/rCcrGuw6M28uJkxsnPAAWgBWgqz4C2rXnPdDIz4+RngALQAjQKQAvQKAAtQKMAtACNAtACNApAC9AoAC1AowC0AI0C0AI0lT3BS2n3zf0e2r/j7y/5GaAAtABN73XP7CdzO/RrJvR+vunnAzzJzwAFoAVogucDruXZjId9u8bpd4n+GRXy9SQ/AxSAFqDJ/DlBjz83SK/5EPvDzoz9uUBP8jNAAWgBmsr/gNizwk3D34/OCjsUgBagUQBagEYBaAEaBaAFaBSAFqBRAFqARgFoARoFoAVoFIAWoPkCQOwuh7kvX8IAAAAASUVORK5CYII=\"","export default \"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAABLElEQVR4nO2asQrCMAAFq/gT6iQIFZ0UHAT/f3FwUiwITuroILjqal6HUFJ61b7bAlauRxJom95qu3lnX+SjYZbCYrZOuj7G4bRLur643YNxP+nf/gAHoAVoBrEf6JpZ5vNgPBlP6zWKoHvM5XoOxvviGIxje1rnZ4AD0AI0pT1A17yuoabXfAz1eT0fwVjvR+n8DHAAWoCmtAekPgu0Db0fPwsIDkAL0DgALUDjALQAjQPQAjQOQAvQOAAtQOMAtABN9LuAou/h6XeE6lOVzs8AB6AFaCrvAfreXb/Xt/18gNL5GeAAtACNzwfUo/W7OAAtQOPzAbUb/RgOQAvQ+HxAkzJtxAFoARoHoAVoHIAWoHEAWoDGAWgBGgegBWgcgBagcQBagMYBaAGaD9C3Qmb46VC5AAAAAElFTkSuQmCC\"","/**\n * Map-editor grid model: parsing, generation and autotiling.\n *\n * Extracted from MapEditor.tsx (2026-08-17). Every function here is pure and\n * touches no React — it was ~25% of that component's file with none of its\n * state, and living inside a route component meant none of it could be tested.\n *\n * Two of these are hand-ports of Rust in `game/src/board.rs` and MUST stay in\n * step with it, or the editor previews a layout the game will not produce:\n * - `pickWallTile` <- board.rs::pick_hard_handle (16-mask autotile)\n * - `rollSoftBlocks` <- board.rs::roll_map_cells + the pick_map spawn shuffle\n * Both are deterministic, which is what makes that drift testable at all.\n */\n\nimport { CHARACTER_ORDER, type CharacterId } from '@watchparty/shared';\n\nexport type CellChar = string; // 'H' | '.' | '1'..'9' | 'A'\nexport type Grid = CellChar[][];\n\nexport interface MapState {\n width: number;\n height: number;\n grid: Grid;\n}\n\n// Default dimensions per player count, copied from the in-tree\n// game/assets/maps/p{NN}/m01.map headers. The editor lets the operator\n// resize manually after the initial seed.\nexport const DEFAULT_DIMS: Record<number, [number, number]> = {\n 2: [11, 9],\n 3: [11, 9],\n 4: [13, 11],\n 5: [13, 11],\n 6: [17, 15],\n 7: [17, 15],\n 8: [19, 17],\n 9: [19, 17],\n 10: [21, 19],\n};\n\nexport const SPAWN_DIGITS = '123456789A';\n\n// The character previewed on each spawn digit: the character order, one per spawn.\nexport const SPAWN_CHARS: readonly CharacterId[] = CHARACTER_ORDER.slice(0, SPAWN_DIGITS.length);\n\n// Cardinal steps (N/S/E/W) used for both the autotile mask and the\n// spawn-safety-corridor clearing in rollSoftBlocks.\nexport const CARDINALS: ReadonlyArray<[number, number]> = [[1, 0], [-1, 0], [0, 1], [0, -1]];\n// Diagonal steps used by the spawn safety pattern (1-deep) so the second\n// bomb after the bot moves into the corridor has a perpendicular flee\n// path. Matches the diagonal block in game/src/board.rs::roll_map_cells.\nexport const DIAGONALS: ReadonlyArray<[number, number]> = [[1, 1], [1, -1], [-1, 1], [-1, -1]];\n\n// Per-spawn safety: 2-deep cardinal + 1-deep diagonal \"hashtag\" pattern\n// (13 cells per spawn) — kept in sync with board.rs::roll_map_cells so\n// the editor preview matches in-game reality.\nexport const SPAWN_CLEAR_DEPTH = 2;\n\nexport const TILE_NAMES = [\n 'floor',\n 'soft_block',\n 'wall_iso',\n 'wall_h',\n 'wall_v',\n 'wall_cross',\n 'wall_cap_n',\n 'wall_cap_s',\n 'wall_cap_e',\n 'wall_cap_w',\n 'wall_corner_ne',\n 'wall_corner_nw',\n 'wall_corner_se',\n 'wall_corner_sw',\n 'wall_t_n',\n 'wall_t_s',\n 'wall_t_e',\n 'wall_t_w',\n];\n\nexport function spawnIndex(digit: string): number {\n return digit === 'A' ? 9 : parseInt(digit, 10) - 1;\n}\n\nexport function spawnSpriteFor(digit: string): CharacterId {\n return SPAWN_CHARS[spawnIndex(digit) % SPAWN_CHARS.length];\n}\n\n/** Same 16-mask autotile picker as board.rs::pick_hard_handle. The\n * off-board-is-floor convention makes border cells render as clean\n * straights and the four corners pick their corner art. */\nexport function pickWallTile(grid: Grid, c: number, r: number): string {\n const w = grid[0]?.length ?? 0;\n const h = grid.length;\n const wall = (cc: number, rr: number) =>\n cc >= 0 && rr >= 0 && cc < w && rr < h && grid[rr][cc] === 'H';\n const n = wall(c, r - 1) ? 1 : 0;\n const e = wall(c + 1, r) ? 1 : 0;\n const s = wall(c, r + 1) ? 1 : 0;\n const we = wall(c - 1, r) ? 1 : 0;\n const mask = (n << 3) | (e << 2) | (s << 1) | we;\n switch (mask) {\n case 0: return 'wall_iso';\n case 1: return 'wall_cap_e';\n case 2: return 'wall_cap_n';\n case 3: return 'wall_corner_sw';\n case 4: return 'wall_cap_w';\n case 5: return 'wall_h';\n case 6: return 'wall_corner_se';\n case 7: return 'wall_t_n';\n case 8: return 'wall_cap_s';\n case 9: return 'wall_corner_nw';\n case 10: return 'wall_v';\n case 11: return 'wall_t_e';\n case 12: return 'wall_corner_ne';\n case 13: return 'wall_t_s';\n case 14: return 'wall_t_w';\n case 15:\n // All 4 cardinal neighbors are walls — this is genuinely a +\n // intersection and renders as wall_cross. (The previous\n // diagonal-based disambiguation here picked T / corner pieces\n // that HID one or two arms — e.g. floorSE → wall_t_w deleted the\n // visible west arm even though W is wall — which the operator\n // reported as \"W weirdly disconnected\" when N got added to a\n // pre-existing W/S/E cross.)\n return 'wall_cross';\n default: return 'wall_iso';\n }\n}\n\n/** JS port of board.rs::roll_map_cells + the spawn-shuffle in pick_map.\n *\n * Mirrors the runtime exactly: gather spawns in digit order, Fisher-\n * Yates shuffle, take `playerCount`, clear a \"hashtag\" safety pattern\n * (2-deep cardinal + 1-deep diagonal, 13 cells per spawn) at each USED\n * spawn, then roll 80% softs on every other floor cell.\n *\n * When the map has more spawns than `playerCount`, this means each roll\n * shows a different subset of spawns as \"the lucky ones whose corridors\n * are clear\" — same as what the game actually does at match start. */\nexport function rollSoftBlocks(grid: Grid, seed: number, playerCount: number): Set<string> {\n const w = grid[0]?.length ?? 0;\n const h = grid.length;\n\n let state = (seed | 1) >>> 0;\n const next = () => {\n state ^= state << 13;\n state >>>= 0;\n state ^= state >>> 17;\n state ^= state << 5;\n state >>>= 0;\n return state;\n };\n\n const spawns: { c: number; r: number; digit: string }[] = [];\n for (let r = 0; r < h; r++) {\n for (let c = 0; c < w; c++) {\n if (/^[1-9A]$/.test(grid[r][c])) {\n spawns.push({ c, r, digit: grid[r][c] });\n }\n }\n }\n // Sort by digit so the shuffle starts from the same canonical order\n // the game's parse_map produces (sorted by spawn id 1..A).\n spawns.sort((a, b) => SPAWN_DIGITS.indexOf(a.digit) - SPAWN_DIGITS.indexOf(b.digit));\n // Fisher-Yates over the full spawn list — matches board.rs::pick_map.\n for (let i = spawns.length - 1; i >= 1; i--) {\n const j = next() % (i + 1);\n [spawns[i], spawns[j]] = [spawns[j], spawns[i]];\n }\n\n const usedSpawns = spawns.slice(0, Math.max(0, Math.min(playerCount, spawns.length)));\n\n const clear = new Set<string>();\n for (const { c, r } of usedSpawns) {\n clear.add(`${c},${r}`);\n for (const [dc, dr] of CARDINALS) {\n for (let step = 1; step <= SPAWN_CLEAR_DEPTH; step++) {\n clear.add(`${c + dc * step},${r + dr * step}`);\n }\n }\n for (const [dc, dr] of DIAGONALS) {\n clear.add(`${c + dc},${r + dr}`);\n }\n }\n\n const softs = new Set<string>();\n for (let r = 0; r < h; r++) {\n for (let c = 0; c < w; c++) {\n if (grid[r][c] !== '.') continue;\n const key = `${c},${r}`;\n if (clear.has(key)) continue;\n if (next() % 100 < 80) softs.add(key);\n }\n }\n return softs;\n}\n\n/** Parse raw .map text into a MapState. Comment lines (leading `#`) and\n * blank lines are stripped before validation. Used by the Library Load\n * button (response body straight from the server) — kept pure so it has\n * no React/DOM dependencies. */\nexport function parseMapText(text: string): { ok: true; map: MapState } | { ok: false; error: string } {\n const lines = text\n .split(/\\r?\\n/)\n .filter((l) => !l.startsWith('#') && l.trim().length > 0);\n if (lines.length < 5) {\n return { ok: false, error: 'Need at least 5 non-comment lines.' };\n }\n const w = lines[0].length;\n if (!lines.every((l) => l.length === w)) {\n return { ok: false, error: 'All rows must have the same width.' };\n }\n const grid: Grid = lines.map((l) =>\n l.split('').map((ch) => {\n if (ch === 'H' || ch === '.') return ch;\n if (/^[1-9Aa]$/.test(ch)) return ch.toUpperCase();\n return '.';\n }),\n );\n return { ok: true, map: { width: w, height: lines.length, grid } };\n}\n\nexport function makeBlankMap(w: number, h: number): MapState {\n const grid: Grid = [];\n for (let r = 0; r < h; r++) {\n const row: CellChar[] = [];\n for (let c = 0; c < w; c++) {\n const onBorder = r === 0 || r === h - 1 || c === 0 || c === w - 1;\n row.push(onBorder ? 'H' : '.');\n }\n grid.push(row);\n }\n return { width: w, height: h, grid };\n}\n\nexport function nextSpawnDigit(grid: Grid): string | null {\n const used = new Set<string>();\n for (const row of grid) {\n for (const ch of row) {\n if (/^[1-9A]$/.test(ch)) used.add(ch);\n }\n }\n for (const d of SPAWN_DIGITS) {\n if (!used.has(d)) return d;\n }\n return null;\n}\n","/**\n * Standalone Bomberman map editor.\n *\n * Outputs the same `.map` text format the game's `parse_map` consumes\n * (`H` = hard wall, `.` = floor, digit/`A` = numbered spawn). Soft blocks\n * are never authored — the game's `roll_map_cells` rolls 80% softs onto\n * floor cells at world-build time (with the per-spawn safety corridor),\n * so the editor only models walls + spawns + floor and provides a\n * \"Roll softs\" preview button so you can sanity-check density.\n *\n * Rendering uses a single `<canvas>` (not 399 React divs): autotile\n * sprite picking + drawImage is O(w*h) per state change with no React\n * reconciliation in the hot path — drag-paint stays smooth at p10.\n *\n * Wall autotiling mirrors `board.rs::pick_hard_handle` so what the\n * editor renders is what the in-game world renders. Tile + character\n * art is fetched from `/game-assets/` (mounted by the server next to\n * `/game/`) so we don't duplicate PNGs into the React bundle.\n *\n * Friend-tier auth required (gated at the route level in router.tsx and\n * at every /api/maps endpoint server-side). The editor writes directly\n * to `game/assets/maps/p{NN}/{name}.map` via the server — the operator\n * git-adds + commits + pushes the diff themselves. Only a dev server\n * accepts writes (`MAP_EDITOR_WRITES`); elsewhere a save fails with the\n * server's reason, since the game there has its maps built in.\n */\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { getRuntimeServerOrigin } from '../lib/config';\nimport { spriteUrlFor } from '../lib/characters';\n\n/** The server's own message for a refused edit (a read-only host says why),\n * else the status. */\nasync function failureReason(res: Response): Promise<string> {\n try {\n const body = await res.json() as { message?: unknown };\n if (typeof body.message === 'string' && body.message) return body.message;\n } catch {\n // Not JSON (an edge error page): the status is all there is.\n }\n return `HTTP ${res.status}`;\n}\n\n// Bundle tile PNGs INTO the UI bundle via Vite's glob import.\n// Why same-origin: the editor used to fetch from `${serverOrigin}/game-assets/`\n// but the parent site's CSP can block cross-origin img loads (img-src not\n// covering watchparty.hadoku.me). Inlining as Vite assets ships them at\n// `${UI_BUNDLE_ORIGIN}/.../mf/watchparty/assets/...` so they always\n// resolve same-origin.\nconst TILE_URLS = import.meta.glob('../assets/games/bomberman/tiles/*.png', {\n eager: true,\n import: 'default',\n}) as Record<string, string>;\nfunction bundledTileUrl(name: string): string | null {\n return TILE_URLS[`../assets/games/bomberman/tiles/${name}.png`] ?? null;\n}\n\nimport {\n type CellChar, type Grid, type MapState,\n DEFAULT_DIMS, SPAWN_CHARS, SPAWN_DIGITS, TILE_NAMES,\n spawnSpriteFor, pickWallTile, rollSoftBlocks,\n parseMapText, makeBlankMap, nextSpawnDigit,\n} from '../lib/mapEditorGrid';\n\ntype Tool = 'wall' | 'spawn' | 'floor';\n\nexport default function MapEditor() {\n const navigate = useNavigate();\n const [players, setPlayers] = useState(4);\n const [map, setMap] = useState<MapState>(() => {\n const [w, h] = DEFAULT_DIMS[4];\n return makeBlankMap(w, h);\n });\n\n // Soft-block preview: rolled on demand via the toolbar button. Cleared\n // whenever the underlying grid changes structurally (player count,\n // resize, library load) so a stale layout from a prior grid never\n // shows up on the new one — the user has to roll fresh.\n const [softs, setSofts] = useState<Set<string>>(new Set());\n\n // Library state — list of saved map files grouped by player count, the\n // currently-selected file (so Save knows where to PUT), and the new-name\n // input value for \"Save as new\". Status is a one-line banner under the\n // buttons reporting the last action's result.\n type LibraryEntry = { players: number; files: string[] };\n const [library, setLibrary] = useState<LibraryEntry[]>([]);\n const [libraryLoading, setLibraryLoading] = useState(false);\n const [selected, setSelected] = useState<string>(''); // \"p04/m01.map\" or ''\n const [newName, setNewName] = useState('');\n const [status, setStatus] = useState<{ kind: 'ok' | 'err'; text: string } | null>(null);\n\n const origin = useMemo(() => getRuntimeServerOrigin(), []);\n\n // Sprite cache. Bundled PNGs (TILE_URLS / CHARACTER_URLS) resolve to\n // same-origin URLs hashed into the UI bundle, so cross-origin CSP /\n // CORS issues with watchparty.hadoku.me's /game-assets/ never bite.\n // The canvas redraw effect waits on `spritesReady` so we don't flash\n // empty cells.\n const spriteCacheRef = useRef<Record<string, HTMLImageElement>>({});\n const [spritesReady, setSpritesReady] = useState(false);\n\n useEffect(() => {\n let mounted = true;\n function loadOne(key: string, url: string | null | undefined): Promise<void> {\n if (!url) return Promise.resolve();\n return new Promise((resolve) => {\n const img = new Image();\n img.onload = () => {\n if (mounted) spriteCacheRef.current[key] = img;\n resolve();\n };\n img.onerror = () => {\n \n console.warn('[MapEditor] sprite failed to load', { key, url });\n resolve();\n };\n img.src = url;\n });\n }\n Promise.all([\n ...TILE_NAMES.map((t) => loadOne(t, bundledTileUrl(t))),\n // Characters come from the server like every other avatar in the app.\n ...SPAWN_CHARS.map((c) => loadOne(`char:${c}`, spriteUrlFor(c))),\n ]).then(() => {\n if (mounted) setSpritesReady(true);\n });\n return () => {\n mounted = false;\n };\n }, []);\n\n // Paint one or more cells in a SINGLE state update. Drag-paint funnels\n // all cells from one mouse-move's interpolation through here in one\n // setMap; React 18's batching was dropping cells when this was N calls.\n // The tool is passed in (not a useState) so the active mouse button +\n // shift modifier picked at mousedown determines the action — no\n // toolbar tool selector needed.\n const applyCells = useCallback((cells: { c: number; r: number }[], t: Tool) => {\n if (cells.length === 0) return;\n setMap((prev) => {\n const grid = prev.grid.map((row) => [...row]);\n for (const { c, r } of cells) {\n if (r < 0 || r >= prev.height || c < 0 || c >= prev.width) continue;\n if (t === 'wall') {\n grid[r][c] = 'H';\n } else if (t === 'floor') {\n grid[r][c] = '.';\n } else if (t === 'spawn') {\n // Shift+left on an existing spawn cell removes it. Shift+left\n // on a wall is a no-op — the operator has to clear walls first\n // if they want a spawn there (prevents accidentally punching\n // a hole in the border on a stray click).\n if (/^[1-9A]$/.test(grid[r][c])) {\n grid[r][c] = '.';\n } else if (grid[r][c] === '.') {\n const used = new Set<string>();\n for (const row of grid) {\n for (const ch of row) {\n if (/^[1-9A]$/.test(ch)) used.add(ch);\n }\n }\n let nextDigit: string | null = null;\n for (const d of SPAWN_DIGITS) {\n if (!used.has(d)) { nextDigit = d; break; }\n }\n if (nextDigit) grid[r][c] = nextDigit;\n }\n }\n }\n return { ...prev, grid };\n });\n // Any grid mutation invalidates the soft-block preview — the layout\n // would no longer correspond to the displayed grid.\n setSofts((s) => (s.size === 0 ? s : new Set()));\n }, []);\n\n // Drag-paint via canvas pointer events. Tool is implied by mouse button\n // + Shift modifier at the moment the drag starts:\n // Left-click → wall\n // Shift + Left-click → spawn (single placement, no drag-paint)\n // Right-click → floor (erase)\n // Captured into `dragToolRef` on mousedown so a mid-drag shift-release\n // doesn't change the tool mid-stroke. Cleared on mouseup or when\n // `buttons` drops to 0 inside the move handler (covers the \"still\n // thinks I'm dragging\" bug when mouseup happened off-canvas).\n const canvasRef = useRef<HTMLCanvasElement | null>(null);\n const lastCellRef = useRef<{ c: number; r: number } | null>(null);\n const dragToolRef = useRef<Tool | null>(null);\n\n const cellFromEvent = useCallback((e: React.MouseEvent): { c: number; r: number } | null => {\n const canvas = canvasRef.current;\n if (!canvas) return null;\n const box = canvas.getBoundingClientRect();\n const x = e.clientX - box.left;\n const y = e.clientY - box.top;\n if (x < 0 || y < 0 || x >= box.width || y >= box.height) return null;\n const cellW = box.width / map.width;\n const cellH = box.height / map.height;\n return { c: Math.floor(x / cellW), r: Math.floor(y / cellH) };\n }, [map.width, map.height]);\n\n const paintCellsBetween = useCallback((from: { c: number; r: number }, to: { c: number; r: number }, t: Tool) => {\n const dx = Math.abs(to.c - from.c);\n const dy = Math.abs(to.r - from.r);\n const steps = Math.max(dx, dy);\n const cells: { c: number; r: number }[] = [];\n if (steps === 0) {\n cells.push({ c: to.c, r: to.r });\n } else {\n for (let i = 0; i <= steps; i++) {\n const tt = i / steps;\n cells.push({\n c: Math.round(from.c + (to.c - from.c) * tt),\n r: Math.round(from.r + (to.r - from.r) * tt),\n });\n }\n }\n applyCells(cells, t);\n }, [applyCells]);\n\n const handleCanvasDown = (e: React.MouseEvent) => {\n let t: Tool | null = null;\n if (e.button === 0) t = e.shiftKey ? 'spawn' : 'wall';\n else if (e.button === 2) t = 'floor';\n if (!t) return;\n e.preventDefault();\n dragToolRef.current = t;\n const hit = cellFromEvent(e);\n if (!hit) return;\n // Capture pointer so move/up keep firing even if the cursor leaves\n // the canvas mid-drag (fixes \"drag stops working when I cross into\n // the sidebar\").\n (e.currentTarget as HTMLElement).setPointerCapture?.((e as unknown as PointerEvent).pointerId);\n applyCells([hit], t);\n lastCellRef.current = hit;\n };\n const handleCanvasMove = (e: React.MouseEvent) => {\n const t = dragToolRef.current;\n if (!t) return;\n // Either button still held? bit 0 = left, bit 1 = right. If neither\n // is held the user released — end the drag.\n if ((e.buttons & 0b11) === 0) {\n dragToolRef.current = null;\n lastCellRef.current = null;\n return;\n }\n if (t === 'spawn') return; // single placement, no drag-paint\n const hit = cellFromEvent(e);\n if (!hit) return;\n const prev = lastCellRef.current;\n if (prev) {\n if (prev.c === hit.c && prev.r === hit.r) return;\n paintCellsBetween(prev, hit, t);\n } else {\n applyCells([hit], t);\n }\n lastCellRef.current = hit;\n };\n const handleCanvasUp = (e: React.MouseEvent) => {\n (e.currentTarget as HTMLElement).releasePointerCapture?.((e as unknown as PointerEvent).pointerId);\n dragToolRef.current = null;\n lastCellRef.current = null;\n };\n // Window-level mouseup / blur safety net: any release outside the\n // canvas, an alt-tab, or a modal opening all clear the drag so the\n // next move doesn't keep painting.\n useEffect(() => {\n const onUp = () => {\n dragToolRef.current = null;\n lastCellRef.current = null;\n };\n window.addEventListener('mouseup', onUp);\n window.addEventListener('blur', onUp);\n return () => {\n window.removeEventListener('mouseup', onUp);\n window.removeEventListener('blur', onUp);\n };\n }, []);\n\n const changePlayers = (n: number) => {\n setPlayers(n);\n const [w, h] = DEFAULT_DIMS[n];\n setMap(makeBlankMap(w, h));\n setSofts(new Set());\n };\n\n const resize = (newW: number, newH: number) => {\n if (newW < 5 || newW > 31 || newH < 5 || newH > 31) return;\n setMap((prev) => {\n const grid: Grid = [];\n for (let r = 0; r < newH; r++) {\n const row: CellChar[] = [];\n for (let c = 0; c < newW; c++) {\n const onBorder = r === 0 || r === newH - 1 || c === 0 || c === newW - 1;\n if (onBorder) {\n row.push('H');\n } else if (r < prev.height && c < prev.width) {\n row.push(prev.grid[r][c]);\n } else {\n row.push('.');\n }\n }\n grid.push(row);\n }\n return { width: newW, height: newH, grid };\n });\n setSofts(new Set());\n };\n\n const clearInterior = () => {\n setMap((prev) => {\n const grid = prev.grid.map((row, r) =>\n row.map((_cell, c) => {\n const onBorder = r === 0 || r === prev.height - 1 || c === 0 || c === prev.width - 1;\n return onBorder ? 'H' : '.';\n }),\n );\n return { ...prev, grid };\n });\n setSofts(new Set());\n };\n\n // Spawn count drives the save-target group ceiling — you can save a\n // map under p{NN}/ only when it has at least NN spawns. The game\n // shuffles + takes N at match start (see board.rs::pick_map), so a\n // 4-spawn map saved under p02 works fine: 2 of 4 spawns chosen\n // randomly each round.\n // Declared BEFORE rollSofts/hideSofts so the useCallback dep array\n // doesn't TDZ against saveTarget — same class of bug the 'h' before\n // init crash was. Reorder, don't hoist with vars.\n const spawnCount = useMemo(() => {\n let n = 0;\n for (const row of map.grid) {\n for (const ch of row) {\n if (/^[1-9A]$/.test(ch)) n += 1;\n }\n }\n return n;\n }, [map]);\n\n // Save-target = which p{NN} folder the next \"Save as new\" lands in.\n // Defaults to the current spawn count (saves a 4-spawn map as a\n // 4-player map). Operator can override down to 2 so the same map\n // serves smaller player counts with extra-spawn variety.\n const [saveTarget, setSaveTarget] = useState(4);\n useEffect(() => {\n setSaveTarget((cur) => {\n // Clamp down if the spawn count dropped below the chosen target.\n // Leave manual choices intact when they're still valid.\n if (spawnCount < 2) return 2;\n if (cur > spawnCount) return spawnCount;\n return cur;\n });\n }, [spawnCount]);\n\n // Roll a fresh soft-block layout against the current grid. Each click\n // uses a different seed (perf.now-based) so the operator can re-roll\n // to spot-check density variance.\n const rollSofts = useCallback(() => {\n const seed = (Math.floor(performance.now() * 1000) ^ 0x9E3779B9) >>> 0;\n setSofts(rollSoftBlocks(map.grid, seed || 1, saveTarget));\n }, [map.grid, saveTarget]);\n const hideSofts = () => setSofts(new Set());\n\n const exportText = useMemo(() => {\n const designedFor = Math.min(saveTarget, spawnCount);\n const extra = spawnCount > designedFor ? ` (${spawnCount} spawn variants)` : '';\n const header =\n `# ${designedFor}-player map${extra} · custom · edited via map editor\\n` +\n `# dims: ${map.width}x${map.height} · spawns: ${spawnCount}`;\n const body = map.grid.map((row) => row.join('')).join('\\n');\n return `${header}\\n${body}\\n`;\n }, [map, spawnCount, saveTarget]);\n\n // ---- Library actions ----\n const refreshLibrary = useCallback(async () => {\n setLibraryLoading(true);\n try {\n const res = await fetch(`${origin}/api/maps`);\n if (!res.ok) {\n setStatus({ kind: 'err', text: `List failed: HTTP ${res.status}` });\n return;\n }\n const data = await res.json() as { groups: LibraryEntry[] };\n setLibrary(data.groups ?? []);\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'network error';\n setStatus({ kind: 'err', text: `List failed: ${message}` });\n } finally {\n setLibraryLoading(false);\n }\n }, [origin]);\n\n useEffect(() => { void refreshLibrary(); }, [refreshLibrary]);\n\n const splitKey = (key: string): { nn: string; name: string } | null => {\n const m = key.match(/^p(0[2-9]|10)\\/([A-Za-z0-9_-]+)\\.map$/);\n if (!m) return null;\n return { nn: m[1], name: m[2] };\n };\n\n const loadSelected = useCallback(async () => {\n setStatus(null);\n const parts = splitKey(selected);\n if (!parts) {\n setStatus({ kind: 'err', text: 'Pick a map first.' });\n return;\n }\n try {\n const res = await fetch(`${origin}/api/maps/p${parts.nn}/${parts.name}`);\n if (!res.ok) {\n setStatus({ kind: 'err', text: `Load failed: HTTP ${res.status}` });\n return;\n }\n const body = await res.text();\n const parsed = parseMapText(body);\n if (!parsed.ok) {\n setStatus({ kind: 'err', text: `Parse failed: ${parsed.error}` });\n return;\n }\n setMap(parsed.map);\n setSofts(new Set()); // stale soft layout from prior grid is no longer valid\n setStatus({ kind: 'ok', text: `Loaded ${selected}` });\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'network error';\n setStatus({ kind: 'err', text: `Load failed: ${message}` });\n }\n }, [origin, selected]);\n\n const writeMap = useCallback(async (nn: string, name: string, opts: { confirmText?: string } = {}) => {\n if (opts.confirmText && !window.confirm(opts.confirmText)) return false;\n setStatus(null);\n try {\n const res = await fetch(`${origin}/api/maps/p${nn}/${name}`, {\n method: 'PUT',\n headers: { 'Content-Type': 'text/plain' },\n body: exportText,\n });\n if (!res.ok) {\n setStatus({ kind: 'err', text: `Save failed: ${await failureReason(res)}` });\n return false;\n }\n setStatus({ kind: 'ok', text: `Saved p${nn}/${name}.map` });\n setSelected(`p${nn}/${name}.map`);\n await refreshLibrary();\n return true;\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'network error';\n setStatus({ kind: 'err', text: `Save failed: ${message}` });\n return false;\n }\n }, [origin, exportText, refreshLibrary]);\n\n const saveOverwrite = useCallback(async () => {\n const parts = splitKey(selected);\n if (!parts) {\n setStatus({ kind: 'err', text: 'Pick a map first or use \"Save as new\".' });\n return;\n }\n const folderN = parseInt(parts.nn, 10);\n if (spawnCount < folderN) {\n setStatus({\n kind: 'err',\n text: `p${parts.nn} maps need ≥${folderN} spawns — current map has ${spawnCount}.`,\n });\n return;\n }\n await writeMap(parts.nn, parts.name, { confirmText: `Overwrite ${selected}?` });\n }, [selected, spawnCount, writeMap]);\n\n const saveAsNew = useCallback(async () => {\n if (spawnCount < 2) {\n setStatus({ kind: 'err', text: `Place at least 2 spawns before saving (have ${spawnCount}).` });\n return;\n }\n if (saveTarget < 2 || saveTarget > spawnCount || saveTarget > 10) {\n setStatus({\n kind: 'err',\n text: `Target ${saveTarget} invalid — must be 2..${Math.min(10, spawnCount)} for this map.`,\n });\n return;\n }\n const nn = String(saveTarget).padStart(2, '0');\n const name = newName.trim();\n if (!/^[A-Za-z0-9_-]+$/.test(name)) {\n setStatus({ kind: 'err', text: 'New name: alphanumeric / underscore / hyphen, no .map suffix.' });\n return;\n }\n const key = `p${nn}/${name}.map`;\n const existing = library.find((g) => g.players === saveTarget)?.files ?? [];\n const confirmText = existing.includes(`${name}.map`) ? `Overwrite ${key}?` : undefined;\n const ok = await writeMap(nn, name, { confirmText });\n if (ok) setNewName('');\n }, [spawnCount, saveTarget, newName, library, writeMap]);\n\n const deleteSelected = useCallback(async () => {\n const parts = splitKey(selected);\n if (!parts) {\n setStatus({ kind: 'err', text: 'Pick a map first.' });\n return;\n }\n if (!window.confirm(`Delete ${selected}?`)) return;\n setStatus(null);\n try {\n const res = await fetch(`${origin}/api/maps/p${parts.nn}/${parts.name}`, { method: 'DELETE' });\n if (!res.ok) {\n setStatus({ kind: 'err', text: `Delete failed: ${await failureReason(res)}` });\n return;\n }\n setStatus({ kind: 'ok', text: `Deleted ${selected}` });\n setSelected('');\n await refreshLibrary();\n } catch (e: unknown) {\n const message = e instanceof Error ? e.message : 'network error';\n setStatus({ kind: 'err', text: `Delete failed: ${message}` });\n }\n }, [origin, selected, refreshLibrary]);\n\n const suggestedName = useMemo(() => {\n const files = library.find((g) => g.players === saveTarget)?.files ?? [];\n const used = new Set<string>(files);\n for (let n = 1; n <= 99; n++) {\n const candidate = `m${String(n).padStart(2, '0')}.map`;\n if (!used.has(candidate)) return candidate.replace(/\\.map$/, '');\n }\n return 'custom';\n }, [library, saveTarget]);\n\n // Cell size — fit roughly 60% width, 75% viewport-min height.\n const cellSize = useMemo(() => {\n if (typeof window === 'undefined') return 32;\n const maxByW = (window.innerWidth - 360) / map.width;\n const maxByH = (window.innerHeight - 160) / map.height;\n return Math.max(16, Math.floor(Math.min(maxByW, maxByH)));\n }, [map.width, map.height]);\n\n // Imperative canvas redraw — runs once per state change. O(w*h) sprite\n // draws + no React reconciliation. The 21x19 p10 layout drops from\n // 399 div re-renders per paint to one canvas pass.\n useEffect(() => {\n if (!spritesReady) return;\n const canvas = canvasRef.current;\n if (!canvas) return;\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n const dpr = typeof window !== 'undefined' ? (window.devicePixelRatio || 1) : 1;\n const cssW = map.width * cellSize;\n const cssH = map.height * cellSize;\n if (canvas.width !== cssW * dpr || canvas.height !== cssH * dpr) {\n canvas.width = cssW * dpr;\n canvas.height = cssH * dpr;\n }\n canvas.style.width = `${cssW}px`;\n canvas.style.height = `${cssH}px`;\n ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n ctx.imageSmoothingEnabled = false;\n ctx.clearRect(0, 0, cssW, cssH);\n\n const cache = spriteCacheRef.current;\n const floorImg = cache.floor;\n const softImg = cache.soft_block;\n\n for (let r = 0; r < map.height; r++) {\n for (let c = 0; c < map.width; c++) {\n const x = c * cellSize;\n const y = r * cellSize;\n const cell = map.grid[r][c];\n const isWall = cell === 'H';\n const isSpawn = /^[1-9A]$/.test(cell);\n const isSoft = !isWall && !isSpawn && softs.has(`${c},${r}`);\n\n if (floorImg) ctx.drawImage(floorImg, x, y, cellSize, cellSize);\n\n if (isWall) {\n const img = cache[pickWallTile(map.grid, c, r)];\n if (img) ctx.drawImage(img, x, y, cellSize, cellSize);\n } else if (isSoft) {\n if (softImg) ctx.drawImage(softImg, x, y, cellSize, cellSize);\n } else if (isSpawn) {\n const img = cache[`char:${spawnSpriteFor(cell)}`];\n if (img) ctx.drawImage(img, x, y, cellSize, cellSize);\n // Spawn-number badge, top-right corner of the cell.\n const fontSize = Math.max(10, Math.floor(cellSize / 4));\n ctx.font = `800 ${fontSize}px ui-sans-serif, system-ui, sans-serif`;\n ctx.textAlign = 'right';\n ctx.textBaseline = 'top';\n ctx.lineWidth = 3;\n ctx.strokeStyle = '#000';\n ctx.fillStyle = '#fff';\n const tx = x + cellSize - 2;\n const ty = y + 2;\n ctx.strokeText(cell, tx, ty);\n ctx.fillText(cell, tx, ty);\n }\n }\n }\n }, [map, softs, cellSize, spritesReady]);\n\n const utilBtnClass =\n 'px-3 py-1.5 text-sm bg-bg-card border border-border rounded hover:bg-bg-hover whitespace-nowrap';\n // Keycap styling for the input legend below the toolbar.\n const keycap = 'inline-block min-w-[20px] px-1.5 py-0.5 mx-0.5 bg-bg border border-border rounded text-[11px] font-mono font-semibold text-text';\n\n return (\n <div className=\"flex w-full h-full overflow-hidden bg-bg text-text\">\n {/* Toolbar + grid */}\n <div className=\"flex-1 flex flex-col items-center min-w-0 overflow-auto p-4\">\n {/* Toolbar — three shrink-0 groups so the inner buttons never reflow.\n The Tools group has a hard min-width on each tool button so\n \"Spawn (10)\" doesn't push neighbors to a new row. Whole bar\n scrolls horizontally on a small viewport rather than wrapping. */}\n <div className=\"w-full max-w-6xl flex items-center gap-3 mb-3 flex-nowrap overflow-x-auto\">\n <div className=\"flex items-center gap-2 shrink-0\">\n <button onClick={() => navigate('/')} className={utilBtnClass}>\n ← Home\n </button>\n <h1 className=\"text-xl font-bold ml-1\">Map Editor</h1>\n </div>\n\n <div className=\"flex items-center gap-2 shrink-0\">\n <label className=\"text-sm text-text-secondary\">Default for:</label>\n <select\n value={players}\n onChange={(e) => changePlayers(parseInt(e.target.value, 10))}\n className=\"px-2 py-1 bg-bg-card border border-border rounded text-sm\"\n >\n {[2, 3, 4, 5, 6, 7, 8, 9, 10].map((n) => (\n <option key={n} value={n}>\n {n} players\n </option>\n ))}\n </select>\n <label className=\"text-sm text-text-secondary ml-2\">W</label>\n <input\n type=\"number\"\n min={5}\n max={31}\n value={map.width}\n onChange={(e) => resize(parseInt(e.target.value, 10) || map.width, map.height)}\n className=\"w-16 px-2 py-1 bg-bg-card border border-border rounded text-sm\"\n />\n <label className=\"text-sm text-text-secondary\">H</label>\n <input\n type=\"number\"\n min={5}\n max={31}\n value={map.height}\n onChange={(e) => resize(map.width, parseInt(e.target.value, 10) || map.height)}\n className=\"w-16 px-2 py-1 bg-bg-card border border-border rounded text-sm\"\n />\n </div>\n\n <div className=\"flex items-center gap-2 shrink-0 ml-auto\">\n <span className=\"text-sm text-text-secondary whitespace-nowrap\">\n Spawns: <span className=\"font-bold text-text\">{spawnCount}</span>/10\n </span>\n <button onClick={clearInterior} className={utilBtnClass}>\n Clear interior\n </button>\n <button\n onClick={softs.size > 0 ? hideSofts : rollSofts}\n className={utilBtnClass}\n title=\"Preview a random soft-block layout. Re-click to re-roll.\"\n >\n {softs.size > 0 ? `Hide softs (${softs.size})` : 'Roll softs'}\n </button>\n </div>\n </div>\n\n {/* Input legend — tool is implied by mouse button + shift, not a\n modal selector. Keeps the toolbar from needing 3 wide tool\n buttons that resize as the spawn count grows. */}\n <div className=\"text-xs text-text-secondary mb-2 max-w-6xl w-full flex flex-wrap items-center gap-x-4 gap-y-1\">\n <span><span className={keycap}>LMB</span> wall &nbsp;·&nbsp; drag to paint</span>\n <span><span className={keycap}>RMB</span> floor (erase) &nbsp;·&nbsp; drag to wipe</span>\n <span>\n <span className={keycap}>Shift</span>+<span className={keycap}>LMB</span> place spawn {nextSpawnDigit(map.grid) ?? '—'} &nbsp;·&nbsp; click an existing spawn to remove\n </span>\n </div>\n\n <canvas\n ref={canvasRef}\n className=\"border border-border rounded select-none\"\n style={{\n background: '#1a1a1a',\n cursor: 'crosshair',\n imageRendering: 'pixelated',\n }}\n onContextMenu={(e) => e.preventDefault()}\n onMouseDown={handleCanvasDown}\n onMouseMove={handleCanvasMove}\n onMouseUp={handleCanvasUp}\n />\n {!spritesReady && (\n <div className=\"text-xs text-text-secondary mt-2\">Loading tiles…</div>\n )}\n </div>\n\n {/* Right-side library panel — server-backed load/save */}\n <div className=\"w-80 flex flex-col gap-4 p-4 border-l border-border bg-bg-card overflow-auto shrink-0\">\n <div>\n <div className=\"flex items-center justify-between mb-2\">\n <h2 className=\"text-base font-bold\">Saved maps</h2>\n <button\n onClick={() => void refreshLibrary()}\n disabled={libraryLoading}\n className=\"text-xs px-2 py-0.5 bg-bg border border-border rounded hover:bg-bg-hover disabled:opacity-50\"\n title=\"Refresh list\"\n >\n {libraryLoading ? '…' : 'Refresh'}\n </button>\n </div>\n <select\n value={selected}\n onChange={(e) => setSelected(e.target.value)}\n size={10}\n className=\"w-full p-2 bg-bg border border-border rounded font-mono text-xs\"\n >\n <option value=\"\">— pick a map —</option>\n {library.map((group) => (\n <optgroup key={group.players} label={`p${String(group.players).padStart(2, '0')} (${group.players} players)`}>\n {group.files.map((f) => {\n const key = `p${String(group.players).padStart(2, '0')}/${f}`;\n return <option key={key} value={key}>{f}</option>;\n })}\n </optgroup>\n ))}\n </select>\n <div className=\"mt-2 grid grid-cols-3 gap-2\">\n <button\n onClick={() => void loadSelected()}\n disabled={!selected}\n className=\"px-2 py-1.5 bg-accent text-bg rounded text-sm font-semibold hover:opacity-90 disabled:opacity-40\"\n >\n Load\n </button>\n <button\n onClick={() => void saveOverwrite()}\n disabled={!selected}\n className=\"px-2 py-1.5 bg-bg border border-border rounded text-sm font-semibold hover:bg-bg-hover disabled:opacity-40\"\n >\n Save\n </button>\n <button\n onClick={() => void deleteSelected()}\n disabled={!selected}\n className=\"px-2 py-1.5 bg-bg border border-error text-error rounded text-sm font-semibold hover:bg-bg-hover disabled:opacity-40\"\n >\n Delete\n </button>\n </div>\n </div>\n\n <div>\n <h3 className=\"text-sm font-bold mb-2\">Save as new</h3>\n <label className=\"flex items-center gap-2 mb-2 text-xs text-text-secondary\">\n <span className=\"shrink-0\">Save under group:</span>\n <select\n value={saveTarget}\n onChange={(e) => setSaveTarget(parseInt(e.target.value, 10))}\n className=\"flex-1 min-w-0 px-2 py-1 bg-bg border border-border rounded font-mono text-xs\"\n disabled={spawnCount < 2}\n >\n {[2, 3, 4, 5, 6, 7, 8, 9, 10].map((n) => {\n const disabled = n > spawnCount;\n return (\n <option key={n} value={n} disabled={disabled}>\n p{String(n).padStart(2, '0')} ({n}-player{disabled ? ` — need ${n} spawns` : n < spawnCount ? `, ${spawnCount - n} extra` : ''})\n </option>\n );\n })}\n </select>\n </label>\n <div className=\"flex items-center gap-2\">\n <code className=\"text-xs bg-bg border border-border rounded px-2 py-1 font-mono\">\n p{String(saveTarget).padStart(2, '0')}/\n </code>\n <input\n type=\"text\"\n value={newName}\n onChange={(e) => setNewName(e.target.value)}\n placeholder={suggestedName}\n className=\"flex-1 min-w-0 px-2 py-1 bg-bg border border-border rounded font-mono text-xs\"\n />\n <code className=\"text-xs text-text-secondary\">.map</code>\n </div>\n <button\n onClick={() => void saveAsNew()}\n disabled={newName.trim().length === 0 || spawnCount < saveTarget || saveTarget < 2}\n className=\"mt-2 w-full px-3 py-1.5 bg-bg border border-border rounded text-sm font-semibold hover:bg-bg-hover disabled:opacity-50\"\n >\n Save as new\n </button>\n <p className=\"mt-1 text-xs text-text-secondary leading-tight\">\n Writes to <code className=\"bg-bg px-1 rounded\">game/assets/maps/p{String(saveTarget).padStart(2, '0')}/&lt;name&gt;.map</code>.\n {spawnCount > saveTarget && (\n <> {spawnCount - saveTarget} extra spawn{spawnCount - saveTarget === 1 ? '' : 's'} — the game shuffles + picks {saveTarget} of {spawnCount} each round.</>\n )}\n </p>\n </div>\n\n {status && (\n <div className={`text-xs px-2 py-1 rounded border ${\n status.kind === 'ok'\n ? 'text-success border-success/30 bg-success/10'\n : 'text-error border-error/30 bg-error/10'\n }`}>\n {status.text}\n </div>\n )}\n\n <div className=\"text-xs text-text-secondary leading-snug\">\n <h3 className=\"font-bold text-text mb-1\">Notes</h3>\n <ul className=\"list-disc pl-4 space-y-1\">\n <li>Load / Save / Delete hit the server directly. The operator git-commits the resulting diff.</li>\n <li>Roll softs previews the world-build layout (80% softs on floor, with a 13-cell hashtag-shaped safety pattern at every spawn).</li>\n <li>Any grid edit invalidates the soft preview — re-roll to see the new layout.</li>\n </ul>\n </div>\n </div>\n </div>\n );\n}\n"],"names":["__vite_glob_0_0","__vite_glob_0_1","__vite_glob_0_2","__vite_glob_0_3","__vite_glob_0_4","__vite_glob_0_5","__vite_glob_0_6","__vite_glob_0_7","__vite_glob_0_8","__vite_glob_0_9","__vite_glob_0_10","__vite_glob_0_11","__vite_glob_0_12","__vite_glob_0_13","__vite_glob_0_14","__vite_glob_0_15","__vite_glob_0_16","__vite_glob_0_17","DEFAULT_DIMS","SPAWN_DIGITS","SPAWN_CHARS","CHARACTER_ORDER","CARDINALS","DIAGONALS","SPAWN_CLEAR_DEPTH","TILE_NAMES","spawnIndex","digit","spawnSpriteFor","pickWallTile","grid","c","r","w","h","wall","cc","rr","n","e","s","we","rollSoftBlocks","seed","playerCount","state","next","spawns","a","b","i","j","usedSpawns","clear","dc","dr","step","softs","key","parseMapText","text","lines","l","ch","makeBlankMap","row","onBorder","nextSpawnDigit","used","d","failureReason","res","body","TILE_URLS","bundledTileUrl","name","MapEditor","navigate","useNavigate","players","setPlayers","useState","map","setMap","setSofts","library","setLibrary","libraryLoading","setLibraryLoading","selected","setSelected","newName","setNewName","status","setStatus","origin","useMemo","getRuntimeServerOrigin","spriteCacheRef","useRef","spritesReady","setSpritesReady","useEffect","mounted","loadOne","url","resolve","img","t","spriteUrlFor","applyCells","useCallback","cells","prev","nextDigit","canvasRef","lastCellRef","dragToolRef","cellFromEvent","canvas","box","x","y","cellW","cellH","paintCellsBetween","from","to","dx","dy","steps","tt","handleCanvasDown","hit","handleCanvasMove","handleCanvasUp","onUp","changePlayers","resize","newW","newH","clearInterior","_cell","spawnCount","saveTarget","setSaveTarget","cur","rollSofts","hideSofts","exportText","designedFor","extra","header","refreshLibrary","data","message","splitKey","m","loadSelected","parts","parsed","writeMap","nn","opts","saveOverwrite","folderN","saveAsNew","confirmText","g","deleteSelected","suggestedName","files","candidate","cellSize","maxByW","maxByH","ctx","dpr","cssW","cssH","cache","floorImg","softImg","cell","isWall","isSpawn","isSoft","fontSize","tx","ty","utilBtnClass","keycap","jsxs","jsx","group","f","disabled","Fragment"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"MediaDrawer-Da6-fLeV.js","sources":["../../src/lib/catalog.ts","../../src/lib/catalogProbe.ts","../../src/lib/ecchiShows.ts","../../src/lib/mediaTreeExpansion.ts","../../src/lib/catalogTree.ts","../../src/lib/mediaSearchApi.ts","../../src/components/MediaSearchResults.tsx","../../src/components/AddMediaDrawer.tsx","../../src/components/GamePicker.tsx","../../src/components/MediaDrawer.tsx"],"sourcesContent":["import { getRuntimeServerOrigin } from './config';\nimport { isDebugEnabled, debug } from './debug';\n\nexport interface CatalogDir { name: string; path: string; }\nexport interface CatalogFile { id: string; title: string; masterUrl: string; poster: string | null; durationMs: number | null; }\nexport interface CatalogResponse { dirs: CatalogDir[]; files: CatalogFile[]; }\n\n// One entry PER PREFIX. This used to be a single `{prefix, data}` slot, and the\n// media drawer asks for ~18 prefixes per build, so every call evicted the one\n// before it: after a build the slot held only the last prefix, and the next\n// open missed all the others even inside CACHE_MS. A cache that never hits.\nconst cache = new Map<string, { ts: number; data: CatalogResponse }>();\n// Concurrent callers of the same prefix share one request — the drawer's\n// mount prefetch and an early open can overlap.\nconst inflight = new Map<string, Promise<CatalogResponse>>();\nconst CACHE_MS = 60000;\n\nexport async function fetchCatalog(prefix: string = ''): Promise<CatalogResponse> {\n const now = Date.now();\n const hit = cache.get(prefix);\n if (hit && (now - hit.ts) < CACHE_MS) return hit.data;\n const pending = inflight.get(prefix);\n if (pending) return pending;\n\n const request = (async () => {\n const origin = getRuntimeServerOrigin();\n const url = `${origin}/api/catalog${prefix ? `?prefix=${encodeURIComponent(prefix)}` : ''}`;\n const res = await fetch(url, { cache: 'no-store' });\n if (!res.ok) throw new Error(`catalog_http_${res.status}`);\n // The response is not schema-validated (there is no Zod schema for the\n // catalog on either end); this asserts the shape the server documents so the\n // `any` from res.json() does not leak into the cache and the return value.\n const data = (await res.json()) as CatalogResponse;\n if (isDebugEnabled()) debug('catalog', 'Catalog fetched', { prefix, dirs: data.dirs?.length, files: data.files?.length });\n cache.set(prefix, { ts: now, data });\n return data;\n })();\n inflight.set(prefix, request);\n try {\n return await request;\n } finally {\n inflight.delete(prefix);\n }\n}\n\n/** Drop one prefix, or everything when called with no prefix / ''. */\nexport function invalidateCatalog(prefix?: string) {\n if (!prefix) cache.clear();\n else cache.delete(prefix);\n}\n","import { FRAGMENT_MP4_FILENAME } from '@watchparty/shared';\nimport { fetchCatalog } from './catalog';\nimport { isDebugEnabled, debug, warn } from './debug';\n\n// Developer utility (verbose mode) to sanity check media catalog + sample media accessibility.\n// Invoked when MediaDrawer opens in verbose mode.\nexport async function probeMediaEndpoints() {\n if (!isDebugEnabled()) return;\n try {\n const categories = ['output/fmp4/anime','output/fmp4/movie','output/fmp4/tv'];\n for (const cat of categories) {\n const catData = await fetchCatalog(cat);\n debug('catalog', '[probe] catalog ' + cat, { dirs: catData.dirs.length, files: catData.files.length });\n // Try first playable leaf if present\n const candidate = catData.files[0];\n if (candidate) {\n const base = mediaBase();\n const indexUrl = `${base}/${candidate.id}/index.json`.replace(/\\\\/g,'/');\n const mediaUrl = `${base}/${candidate.id}/${FRAGMENT_MP4_FILENAME}`.replace(/\\\\/g,'/');\n await fetchAndLog(indexUrl, 'index');\n // Range fetch first ~1KB\n await fetchAndLog(mediaUrl, 'fragment', { headers: { Range: 'bytes=0-1023' } });\n }\n }\n } catch (err: unknown) {\n warn('catalog', '[probe] error', { error: err instanceof Error ? err.message : String(err) });\n }\n}\n\nfunction mediaBase() {\n return '/media';\n}\n\nasync function fetchAndLog(url: string, label: string, init?: RequestInit) {\n const t0 = performance.now();\n const res = await fetch(url, init);\n const ms = Math.round(performance.now() - t0);\n debug('catalog', '[probe] ' + label, { url, status: res.status, ms, bytes: res.headers.get('content-length') });\n return res;\n}\n","/**\n * Anime series (by output folder name) that are shown in a separate,\n * collapsed-by-default \"Ecchi\" section at the BOTTOM of the media drawer,\n * instead of mixed into the main Anime list.\n *\n * Edit this set to recategorize — matching is case-insensitive and ignores\n * non-alphanumerics, so it tolerates folder-name punctuation/spacing.\n * (WarriorPrincess is intentionally left in the main list — move it here if you\n * consider it ecchi.)\n */\n\nconst ECCHI_SERIES = [\n 'yuuna',\n 'gushingovermagicalgirls',\n 'immoralguild',\n 'ingokudanchi',\n 'isekaionsenparadise',\n];\n\nconst norm = (name: string) => name.toLowerCase().replace(/[^a-z0-9]/g, '');\n\n/**\n * The matcher, over an explicit list. Split out from `isEcchiSeries` so tests\n * can exercise the normalisation rules — which are the part that actually\n * breaks — against a fixed list, independently of what this deployment happens\n * to categorise.\n */\nexport function matchesSeriesList(name: string, series: readonly string[]): boolean {\n return new Set(series.map(norm)).has(norm(name));\n}\n\nexport function isEcchiSeries(name: string): boolean {\n return matchesSeriesList(name, ECCHI_SERIES);\n}\n","/**\n * Carry the user's expand/collapse state across a MediaDrawer refetch.\n *\n * `MediaDrawer` is mounted for the lifetime of the room (`Room.tsx` renders it\n * unconditionally and gates on `open` INSIDE the component, after the hooks),\n * so its `tree` state survives closing the drawer. Every open then refires\n * `buildTree()`, which walks the catalog in ~18 SEQUENTIAL requests — 1.45s\n * measured from the server's own host, several seconds for a viewer over the\n * tunnel.\n *\n * For that whole window the drawer shows the PREVIOUS tree, fully interactive.\n * A user who opens the drawer and expands a series is working on a tree that is\n * about to be thrown away: when the refetch lands, `setTree(result)` replaces it\n * with freshly-built nodes whose `expanded` flags are back at their defaults,\n * and the selection silently collapses a few seconds after opening. It only\n * happens from the second open onward, because the first has no stale tree to\n * show — which is exactly why it reads as intermittent.\n *\n * `mergeExpansion` re-applies the old flags onto the new tree by NAME, so a\n * background refresh can never undo a deliberate interaction. Names are the\n * right key here: they are what the user actually clicked on, they are stable\n * across refetches, and they are unique within their level (a category label, a\n * series directory name). Nodes that are genuinely new keep the defaults\n * `buildTree` gave them — a series added by an ingest since the last open\n * arrives collapsed, and the Ecchi category still starts collapsed the first\n * time it appears.\n */\n\nexport interface ExpandableNode {\n name: string;\n expanded?: boolean;\n}\n\nexport interface ExpandableParent<C extends ExpandableNode> extends ExpandableNode {\n children: C[];\n}\n\n/** Snapshot of which nodes are open, keyed by name at each level. */\nexport type ExpansionState = Map<string, { expanded: boolean; children: Map<string, boolean> }>;\n\n/**\n * Read the current expand/collapse state out of a tree.\n *\n * `childrenOf` returns the nested expandable nodes for a top-level node\n * (series inside a category); levels with no expandable children yield an\n * empty map, which merges as a no-op.\n */\nexport function captureExpansion<T extends ExpandableNode>(\n tree: readonly T[],\n childrenOf: (node: T) => readonly ExpandableNode[]\n): ExpansionState {\n const state: ExpansionState = new Map();\n for (const node of tree) {\n const children = new Map<string, boolean>();\n for (const child of childrenOf(node)) {\n children.set(child.name, child.expanded === true);\n }\n state.set(node.name, { expanded: node.expanded === true, children });\n }\n return state;\n}\n\n/**\n * Re-apply a captured expansion state onto a freshly-built tree.\n *\n * Returns NEW node objects rather than mutating: `tree` is React state, and the\n * caller renders straight off it. A node absent from `state` is untouched, so\n * newly-ingested media keeps whatever default the builder chose.\n */\nexport function mergeExpansion<T extends ExpandableNode>(\n tree: readonly T[],\n state: ExpansionState,\n childrenOf: (node: T) => readonly ExpandableNode[],\n withChildren: (node: T, children: ExpandableNode[]) => T\n): T[] {\n return tree.map((node) => {\n const prev = state.get(node.name);\n if (!prev) return node;\n\n const children = childrenOf(node);\n const mergedChildren = children.map((child) => {\n const wasExpanded = prev.children.get(child.name);\n if (wasExpanded === undefined) return child;\n return { ...child, expanded: wasExpanded };\n });\n\n const merged = withChildren({ ...node, expanded: prev.expanded }, mergedChildren);\n return merged;\n });\n}\n\n/**\n * The one category that starts OPEN when the drawer opens on Watch. Every other\n * top-level node — Playlists, Ecchi, and Movie/TV should they ever hold\n * anything — starts closed, and every series inside starts closed.\n *\n * A name, not a flag on the node: the builder and the reset below must agree\n * on it, and a rule written in two places is how they stop agreeing.\n */\nexport const DEFAULT_OPEN_CATEGORY = 'Anime';\n\nexport function isOpenByDefault(categoryName: string): boolean {\n return categoryName === DEFAULT_OPEN_CATEGORY;\n}\n\n/**\n * Put a tree back to its defaults: {@link DEFAULT_OPEN_CATEGORY} open,\n * everything else closed, every series collapsed.\n *\n * Called when the drawer OPENS, and only then. This is not a change to\n * {@link mergeExpansion}, and the two are not in tension: they answer different\n * moments. Opening the drawer is a fresh start, so it gets the defaults every\n * time rather than whatever state the last visit left behind. A refetch that\n * lands WHILE the drawer is open is not a fresh start, and must still carry the\n * user's clicks over — that is the bug `mergeExpansion` exists for, and it only\n * stays fixed because the reset runs BEFORE the refetch rather than after it:\n * the refetch then merges onto the defaulted tree plus anything clicked since.\n *\n * Returns new objects; `tree` is React state.\n */\nexport function resetToDefaultExpansion<T extends ExpandableNode>(\n tree: readonly T[],\n childrenOf: (node: T) => readonly ExpandableNode[],\n withChildren: (node: T, children: ExpandableNode[]) => T\n): T[] {\n return tree.map((node) =>\n withChildren(\n { ...node, expanded: isOpenByDefault(node.name) },\n childrenOf(node).map((child) => ({ ...child, expanded: false }))\n )\n );\n}\n","/**\n * Builds the media drawer's Watch tree from the catalog API.\n *\n * Every fetch that does not depend on another runs CONCURRENTLY: the category\n * listings at once, then each category's series listings at once — two round\n * trips deep instead of ~18. It used to be a serial `for … await` waterfall,\n * and the server answers each listing in 1-5ms, so the waterfall WAS the load\n * time: ~1.3s measured from the origin box through the edge (~70ms a hop), and\n * a remote viewer pays their own RTT on top of every one of those hops.\n *\n * Order is preserved (Promise.all keeps input order), so the tree reads the\n * same as the serial version. A failed listing drops only that branch.\n */\nimport type { CatalogResponse } from './catalog';\nimport { warn, errorFields } from './debug';\nimport { isEcchiSeries } from './ecchiShows';\nimport { isOpenByDefault } from './mediaTreeExpansion';\n\nexport interface EpisodeNode { type: 'episode'; name: string; mediaId: string; }\nexport interface SeriesNode { type: 'series'; name: string; episodes: EpisodeNode[]; expanded?: boolean; }\nexport interface CategoryNode { type: 'category'; name: string; series: SeriesNode[]; expanded?: boolean; }\nexport interface PlaylistNode { type: 'playlist'; name: string; playlistId: string; }\nexport interface PlaylistCategoryNode { type: 'playlist_category'; name: string; playlists: PlaylistNode[]; expanded?: boolean; }\nexport type MediaTree = (CategoryNode | PlaylistCategoryNode)[];\n\nexport type CatalogFetcher = (prefix: string) => Promise<CatalogResponse>;\n\nconst ROOT = 'output/fmp4';\nconst CATEGORIES: { key: string; label: string }[] = [\n { key: 'anime', label: 'Anime' },\n { key: 'movie', label: 'Movie' },\n { key: 'tv', label: 'TV' },\n];\n\nasync function tryFetch(fetcher: CatalogFetcher, prefix: string): Promise<CatalogResponse | null> {\n try {\n return await fetcher(prefix);\n } catch (err) {\n warn('catalog', 'Fetch failed', { prefix, ...errorFields(err) });\n return null;\n }\n}\n\nexport async function loadCatalogTree(fetcher: CatalogFetcher): Promise<MediaTree> {\n const [categoryListings, playlistListing] = await Promise.all([\n Promise.all(CATEGORIES.map(cat => tryFetch(fetcher, `${ROOT}/${cat.key}`))),\n tryFetch(fetcher, `${ROOT}/playlists`),\n ]);\n\n const seriesPerCategory = await Promise.all(categoryListings.map(listing =>\n Promise.all((listing?.dirs ?? []).map(async (d): Promise<SeriesNode | null> => {\n const series = await tryFetch(fetcher, d.path);\n const episodes: EpisodeNode[] = (series?.files ?? []).map(f => ({ type: 'episode', name: f.title, mediaId: f.id }));\n return episodes.length ? { type: 'series', name: d.name, episodes, expanded: false } : null;\n }))));\n\n const result: MediaTree = [];\n // Ecchi anime is pulled out of the main Anime list and shown in its own\n // collapsed category at the very bottom of the drawer.\n const ecchiSeries: SeriesNode[] = [];\n CATEGORIES.forEach((cat, i) => {\n const seriesNodes: SeriesNode[] = [];\n for (const node of seriesPerCategory[i]) {\n if (!node) continue;\n if (cat.key === 'anime' && isEcchiSeries(node.name)) ecchiSeries.push(node);\n else seriesNodes.push(node);\n }\n if (seriesNodes.length) result.push({ type: 'category', name: cat.label, series: seriesNodes, expanded: isOpenByDefault(cat.label) });\n });\n const playlists: PlaylistNode[] = (playlistListing?.dirs ?? []).map(d => ({ type: 'playlist', name: d.name, playlistId: d.name }));\n if (playlists.length) result.push({ type: 'playlist_category', name: 'Playlists', playlists, expanded: isOpenByDefault('Playlists') });\n // Ecchi category goes LAST. Every default above reads the same rule\n // (`isOpenByDefault`) so the builder and the on-open reset cannot drift.\n if (ecchiSeries.length) result.push({ type: 'category', name: 'Ecchi', series: ecchiSeries, expanded: isOpenByDefault('Ecchi') });\n return result;\n}\n","/**\n * Search in the Add media drawer: shows (to add to the scraper's watch list)\n * and YouTube videos (to submit as a link).\n *\n * The drawer's one field takes a link, a magnet, or words. Words search;\n * anything with a scheme keeps the existing submit path — `isSearchText` is\n * that split, and it is pure so it is tested rather than living in the\n * component.\n *\n * The add sends `{source, id}` and nothing else: the server re-reads the show\n * and derives every name it forwards (see `apps/server/src/http/routes/search.ts`).\n */\nimport {\n SEARCH_MAX_QUERY,\n SEARCH_MIN_QUERY,\n type TShowResult,\n type TShowSearchOut,\n type TShowSource,\n type TVideoResult,\n type TVideoSearchOut,\n type TWatchlistAddOut,\n} from '@watchparty/shared';\nimport { getRuntimeServerOrigin } from './config';\nimport { debug, warn, errorFields } from './debug';\n\n/** The two schemes the submit path accepts mean a link, never a search. Not\n * any scheme: `Re:Zero` is a title. */\nconst HAS_SCHEME = /^(?:https?|magnet):/i;\n\nexport function isSearchText(raw: string): boolean {\n const text = raw.trim();\n return text.length >= SEARCH_MIN_QUERY && text.length <= SEARCH_MAX_QUERY && !HAS_SCHEME.test(text);\n}\n\n/** Server-relative proxy path → absolute URL on this app's server. */\nexport function resolveSearchImage(pathOrNull: string | null): string | null {\n return pathOrNull ? `${getRuntimeServerOrigin()}${pathOrNull}` : null;\n}\n\n/** `204` → `3:24`, `3725` → `1:02:05`. */\nexport function formatDuration(totalS: number | null): string {\n if (totalS === null || !Number.isFinite(totalS) || totalS < 0) return '';\n const s = Math.floor(totalS % 60);\n const m = Math.floor(totalS / 60) % 60;\n const h = Math.floor(totalS / 3600);\n const ss = String(s).padStart(2, '0');\n return h > 0 ? `${h}:${String(m).padStart(2, '0')}:${ss}` : `${m}:${ss}`;\n}\n\nexport type SearchOutcome<T> = { ok: true; value: T } | { ok: false; status: number | 'network' | 'aborted' };\n\nasync function getJson<T>(path: string, signal: AbortSignal): Promise<SearchOutcome<T>> {\n try {\n const res = await fetch(`${getRuntimeServerOrigin()}${path}`, { signal });\n if (!res.ok) return { ok: false, status: res.status };\n return { ok: true, value: await res.json() as T };\n } catch (err: unknown) {\n if (signal.aborted) return { ok: false, status: 'aborted' };\n warn('search', 'Search request threw', errorFields(err));\n return { ok: false, status: 'network' };\n }\n}\n\nexport function searchShows(q: string, signal: AbortSignal): Promise<SearchOutcome<TShowSearchOut>> {\n return getJson(`/api/search/shows?q=${encodeURIComponent(q.trim())}`, signal);\n}\n\nexport function searchVideos(q: string, signal: AbortSignal): Promise<SearchOutcome<TVideoSearchOut>> {\n return getJson(`/api/search/youtube?q=${encodeURIComponent(q.trim())}`, signal);\n}\n\nexport type WatchlistAddOutcome =\n | { ok: true; value: TWatchlistAddOut }\n /** Already on the list — as good as added, from the user's side. */\n | { ok: false; code: 'exists' | 'failed'; status: number | 'network' };\n\nexport async function addShowToWatchlist(source: TShowSource, id: string): Promise<WatchlistAddOutcome> {\n try {\n const res = await fetch(`${getRuntimeServerOrigin()}/api/watchlist`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ source, id }),\n });\n if (res.status === 201) {\n const value = await res.json() as TWatchlistAddOut;\n debug('search', 'Added to watch list', { source, id, series: value.series });\n return { ok: true, value };\n }\n return { ok: false, code: res.status === 409 ? 'exists' : 'failed', status: res.status };\n } catch (err: unknown) {\n warn('search', 'Watch-list add threw', errorFields(err));\n return { ok: false, code: 'failed', status: 'network' };\n }\n}\n\nexport type { TShowResult, TVideoResult };\n","/**\n * Search results under the Add media field: shows and YouTube videos.\n *\n * A show's `+` adds it to the scraper's watch list, so its episodes arrive on\n * their own; a video's `+` submits its link exactly as pasting it would (a\n * `pending` request). Both searches run in parallel, each landing when it\n * lands — the show lookup answers in a few hundred ms, yt-dlp in a second or\n * so, and neither waits for the other.\n *\n * Results are stored WITH the query they answer and shown only while that is\n * still the query, so typing on never flashes the previous word's results\n * and no effect has to clear state synchronously.\n */\nimport { useEffect, useState } from 'react';\nimport { Icon } from '@wolffm/themes';\nimport type { TSubmission } from '@watchparty/shared';\nimport {\n addShowToWatchlist,\n formatDuration,\n isSearchText,\n resolveSearchImage,\n searchShows,\n searchVideos,\n type SearchOutcome,\n type TShowResult,\n type TVideoResult,\n} from '../lib/mediaSearchApi';\nimport { submissionFailureLabel, submitSubmission } from '../lib/submissionsApi';\n\n/** Pause after the last keystroke before searching. */\nconst DEBOUNCE_MS = 350;\n\ntype Answer<T> = { q: string; outcome: SearchOutcome<T[]> };\ntype RowState = 'adding' | 'added' | string;\n\ninterface Props {\n query: string;\n onSubmitted: (row: TSubmission) => void;\n}\n\nexport default function MediaSearchResults({ query, onSubmitted }: Props) {\n const q = query.trim();\n const active = isSearchText(q);\n const [shows, setShows] = useState<Answer<TShowResult> | null>(null);\n const [videos, setVideos] = useState<Answer<TVideoResult> | null>(null);\n const [rowState, setRowState] = useState<Record<string, RowState>>({});\n\n useEffect(() => {\n if (!active) return;\n const ctrl = new AbortController();\n const timer = setTimeout(() => {\n void searchShows(q, ctrl.signal).then(r => {\n if (ctrl.signal.aborted) return;\n setShows({ q, outcome: r.ok ? { ok: true, value: r.value.results } : r });\n });\n void searchVideos(q, ctrl.signal).then(r => {\n if (ctrl.signal.aborted) return;\n setVideos({ q, outcome: r.ok ? { ok: true, value: r.value.results } : r });\n });\n }, DEBOUNCE_MS);\n return () => { clearTimeout(timer); ctrl.abort(); };\n }, [q, active]);\n\n if (!active) return null;\n\n const showAnswer = shows?.q === q ? shows.outcome : null;\n const videoAnswer = videos?.q === q ? videos.outcome : null;\n\n async function addShow(show: TShowResult) {\n const key = `${show.source}:${show.id}`;\n setRowState(s => ({ ...s, [key]: 'adding' }));\n const r = await addShowToWatchlist(show.source, show.id);\n setRowState(s => ({ ...s, [key]: r.ok || r.code === 'exists' ? 'added' : `error ${r.status}` }));\n }\n\n async function addVideo(video: TVideoResult) {\n const key = `yt:${video.id}`;\n setRowState(s => ({ ...s, [key]: 'adding' }));\n const r = await submitSubmission(video.url, video.title);\n if (r.ok) {\n setRowState(s => ({ ...s, [key]: 'added' }));\n onSubmitted(r.value);\n } else {\n setRowState(s => ({ ...s, [key]: submissionFailureLabel(r.failure) }));\n }\n }\n\n const bothEmpty = showAnswer?.ok && videoAnswer?.ok\n && showAnswer.value.length === 0 && videoAnswer.value.length === 0;\n\n return (\n <div className=\"border-b border-border\" data-testid=\"media-search\">\n {bothEmpty && <div className=\"px-3 py-2 text-[11px] text-text-muted\">no results</div>}\n <Section label=\"Shows\" answer={showAnswer} testId=\"search-shows\">\n {showAnswer?.ok && showAnswer.value.map(show => {\n const key = `${show.source}:${show.id}`;\n const state = show.onWatchlist ? 'added' : rowState[key];\n return (\n <ResultRow\n key={key}\n testId={`show-${key}`}\n image={resolveSearchImage(show.poster)}\n imageClass=\"w-8 h-11\"\n title={show.title}\n meta={[show.year, show.format, show.episodes ? `${show.episodes} ep` : null]}\n state={state}\n canAdd={show.addable}\n addLabel={`Add ${show.title} to watch list`}\n onAdd={() => { void addShow(show); }}\n />\n );\n })}\n </Section>\n <Section label=\"YouTube\" answer={videoAnswer} testId=\"search-youtube\">\n {videoAnswer?.ok && videoAnswer.value.map(video => (\n <ResultRow\n key={video.id}\n testId={`video-${video.id}`}\n image={resolveSearchImage(video.thumb)}\n imageClass=\"w-16 h-9\"\n title={video.title}\n meta={[video.channel, formatDuration(video.durationS)]}\n state={rowState[`yt:${video.id}`]}\n canAdd\n addLabel={`Request ${video.title}`}\n onAdd={() => { void addVideo(video); }}\n />\n ))}\n </Section>\n </div>\n );\n}\n\nfunction Section<T>({ label, answer, testId, children }: {\n label: string;\n answer: SearchOutcome<T[]> | null;\n testId: string;\n children: React.ReactNode;\n}) {\n if (answer?.ok && answer.value.length === 0) return null;\n const failed = answer && !answer.ok && answer.status !== 'aborted';\n return (\n <div data-testid={testId} data-loaded={answer ? 'true' : 'false'}>\n <div className=\"px-3 pt-2 pb-1 flex items-center gap-2 text-[10px] uppercase tracking-wider text-text-muted\">\n <span>{label}</span>\n {!answer && <span className=\"text-text-tertiary\">…</span>}\n {failed && <span className=\"bg-danger-bg text-on-danger-bg px-1 rounded normal-case\">unavailable</span>}\n </div>\n {children}\n </div>\n );\n}\n\nfunction ResultRow({ testId, image, imageClass, title, meta, state, canAdd, addLabel, onAdd }: {\n testId: string;\n image: string | null;\n imageClass: string;\n title: string;\n meta: Array<string | number | null>;\n state: RowState | undefined;\n canAdd: boolean;\n addLabel: string;\n onAdd: () => void;\n}) {\n const metaText = meta.filter(m => m !== null && m !== '').join(' · ');\n return (\n <div className=\"px-3 py-1 flex items-center gap-2\" data-testid={testId} data-state={state ?? ''}>\n {image\n ? <img src={image} alt=\"\" loading=\"lazy\" className={`${imageClass} object-cover rounded shrink-0 bg-bg-alt`} />\n : <div className={`${imageClass} rounded shrink-0 bg-bg-alt`} />}\n <div className=\"flex-1 min-w-0\">\n <div className=\"text-[11px] text-text truncate\" title={title}>{title}</div>\n {metaText && <div className=\"text-[10px] text-text-muted truncate\">{metaText}</div>}\n </div>\n {state === 'added'\n ? <Icon name=\"check\" variant=\"tint\" family=\"success\" round className=\"shrink-0\" />\n : state && state !== 'adding'\n ? <span className=\"text-[10px] bg-danger-bg text-on-danger-bg px-1 rounded shrink-0\">{state}</span>\n : canAdd && (\n <button\n onClick={onAdd}\n disabled={state === 'adding'}\n aria-label={addLabel}\n className=\"drawer-btn shrink-0\"\n >\n <Icon name=\"plus\" />\n </button>\n )}\n </div>\n );\n}\n","/**\n * Ask for media — a link, a magnet, or a file — and the state of everything\n * you have asked for.\n *\n * The first user-facing surface of the submissions programme. Moderation is\n * approve-first, so nothing typed here reaches the catalog on its own: a row\n * lands in `pending` and an operator moves it. That is why this drawer shows\n * the caller's OWN queue rather than a confirmation toast — the honest answer\n * to \"did that work?\" is the row's state, and it keeps changing after the\n * click.\n *\n * ── Three ways in, one field each ──────────────────────────────────────────\n * The link field takes words as well: anything without a scheme searches\n * (`MediaSearchResults` — shows to add to the scraper's watch list, YouTube\n * videos to submit as links), and Submit stands down while it does.\n * Otherwise it takes a URL or a magnet (kind is read off the scheme —\n * `guessSubmissionKind` — never chosen from a select). Reading as a magnet\n * reveals two more fields: `series` (the scraper's destination folder — an\n * anime series already in the catalog is offered as a suggestion, a new name\n * is accepted) and a title, PRE-FILLED from the magnet's `dn=` so a caller\n * who already typed a well-formed link rarely has to type anything twice.\n * Both are sent to the server and are a COURTESY only — `checkSource('magnet',\n * …)` is the one place either requirement is enforced; see the wave brief's\n * mutation check (a).\n *\n * A file goes a different way entirely: it chunk-uploads straight to\n * filetransfer (own protocol, verified against that service's own browser\n * client — see `lib/submissionsApi.ts`), addressed to watchparty's own\n * identity, and only once that lands does a submission get recorded — with\n * the filetransfer file id as `source`. The 4 GB cap is shown and enforced\n * client-side BEFORE the upload starts, as a courtesy; the authoritative\n * check is server-side, on bytes actually received (wave 4, lane A).\n *\n * ── Through DrawerShell, not a third backdrop ──────────────────────────────\n * MediaDrawer and the old GameDrawer each hand-copied the backdrop/panel/\n * Escape chrome and had already drifted (only one closed on a backdrop\n * click). The shell exists so a third drawer is not a third copy.\n *\n * ── Buttons carry a real class ─────────────────────────────────────────────\n * `styles.css` has an unlayered `button { background: none; color: inherit;\n * border: none }` reset, and unlayered CSS beats anything in a `@layer`\n * regardless of specificity — so every `bg-*`, `text-*` and `border-*` utility\n * on a `<button>` is silently discarded. `.drawer-btn` and its modifiers are\n * real class selectors and outrank the reset.\n *\n * ── The list polls while open ──────────────────────────────────────────────\n * A submission's state is moved by an operator and then by a server-side\n * runner; nothing pushes it to this client. The poll is scoped to the drawer\n * being open and torn down with it.\n */\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { Icon } from '@wolffm/themes';\nimport type { TSubmission, TSubmissionState } from '@watchparty/shared';\nimport DrawerShell, { DRAWER_BTN } from './DrawerShell';\nimport { fetchCatalog } from '../lib/catalog';\nimport {\n extractMagnetDisplayName,\n fetchMySubmissions,\n fetchUploadTarget,\n guessSubmissionKind,\n hasEpisodeCode,\n submissionFailureLabel,\n submitMagnetSubmission,\n submitSubmission,\n submitUploadSubmission,\n uploadFailureLabel,\n uploadFileForSubmission,\n formatSubmissionBytes,\n type SubmissionFailure,\n type UploadFailure,\n type UploadPhase,\n type UploadTarget,\n} from '../lib/submissionsApi';\nimport { isSearchText } from '../lib/mediaSearchApi';\nimport { debug } from '../lib/debug';\nimport MediaSearchResults from './MediaSearchResults';\n\ninterface AddMediaDrawerProps { open: boolean; onClose: () => void; }\n\n/** How often the caller's own queue is re-read while this drawer is open. */\nconst POLL_MS = 4000;\n\n/**\n * State → colour. Terminal-good is success, terminal-bad is danger, and\n * everything in flight is the primary accent — the three groups a user\n * actually distinguishes.\n */\nconst STATE_CLASS: Record<TSubmissionState, string> = {\n pending: 'bg-neutral-bg text-on-neutral-bg',\n approved: 'bg-primary-bg text-on-primary-bg',\n fetching: 'bg-primary-bg text-on-primary-bg',\n ingesting: 'bg-primary-bg text-on-primary-bg',\n ready: 'bg-success-bg text-on-success-bg',\n rejected: 'bg-danger-bg text-on-danger-bg',\n failed: 'bg-danger-bg text-on-danger-bg',\n};\n\n/** `TSubmission` as read today, plus the field `series` adds once the shared\n * schema carries it (packages/shared/src/submissions.ts). A cast rather than\n * a shared-type edit — this file does not own that schema — and harmless\n * once the real field lands: the shapes agree. */\ntype SubmissionWithSeries = TSubmission & { series?: string };\n\nfunction SubmissionRow({ item }: { item: TSubmission }) {\n const withSeries = item as SubmissionWithSeries;\n const label = item.title || item.source;\n return (\n <div\n className=\"px-3 py-1.5 border-b border-border/50 flex items-center gap-2\"\n data-submission-id={item.id}\n data-submission-state={item.state}\n data-submission-kind={item.kind}\n data-submission-series={withSeries.series ?? ''}\n >\n <span className=\"text-[10px] uppercase tracking-wider text-text-muted shrink-0\">{item.kind}</span>\n <span className=\"text-[11px] text-text-tertiary truncate flex-1\" title={item.source}>\n {withSeries.series ? `${withSeries.series} — ${label}` : label}\n </span>\n <span className={`text-[10px] uppercase tracking-wider px-1 rounded ${STATE_CLASS[item.state] ?? 'text-text-muted'}`}>\n {item.state}\n </span>\n {item.error && <span className=\"text-[10px] bg-danger-bg text-on-danger-bg px-1 rounded truncate max-w-[10rem]\">{item.error}</span>}\n </div>\n );\n}\n\nexport default function AddMediaDrawer({ open, onClose }: AddMediaDrawerProps) {\n const [source, setSource] = useState('');\n const [series, setSeries] = useState('');\n const [title, setTitle] = useState('');\n const [titleTouched, setTitleTouched] = useState(false);\n const [seriesOptions, setSeriesOptions] = useState<string[]>([]);\n const [busy, setBusy] = useState(false);\n const [failure, setFailure] = useState<SubmissionFailure | null>(null);\n const [rows, setRows] = useState<TSubmission[] | null>(null);\n const [listFailure, setListFailure] = useState<SubmissionFailure | null>(null);\n\n const [uploadTarget, setUploadTarget] = useState<UploadTarget | null>(null);\n const [selectedFile, setSelectedFile] = useState<File | null>(null);\n const [uploadBusy, setUploadBusy] = useState(false);\n const [uploadProgress, setUploadProgress] = useState<{ sent: number; total: number; phase: UploadPhase } | null>(null);\n const [uploadFailure, setUploadFailure] = useState<UploadFailure | SubmissionFailure | null>(null);\n const fileInputRef = useRef<HTMLInputElement | null>(null);\n\n // A poll that resolves after the drawer closed must not resurrect its state.\n const liveRef = useRef(false);\n\n const kind = useMemo(() => guessSubmissionKind(source), [source]);\n const isMagnet = kind === 'magnet' && source.trim().length > 0;\n const searching = isSearchText(source);\n\n const refresh = useCallback(async () => {\n const res = await fetchMySubmissions();\n if (!liveRef.current) return;\n if (res.ok) { setRows(res.value); setListFailure(null); }\n else { setListFailure(res.failure); }\n }, []);\n\n useEffect(() => {\n if (!open) return;\n liveRef.current = true;\n void refresh();\n const timer = setInterval(() => { void refresh(); }, POLL_MS);\n return () => { liveRef.current = false; clearInterval(timer); };\n }, [open, refresh]);\n\n // The upload target (recipient + cap) is fetched once per open, ahead of any\n // file being picked, so the cap is on screen before the user chooses a file\n // rather than only after they commit to uploading one.\n useEffect(() => {\n if (!open) return;\n let live = true;\n void fetchUploadTarget().then(res => { if (live && res.ok) setUploadTarget(res.value); });\n return () => { live = false; };\n }, [open]);\n\n // Existing anime series, as suggestions only — a new name is accepted.\n useEffect(() => {\n if (!open) return;\n let live = true;\n // The anime SERIES live at output/fmp4/anime. This read 'anime', a prefix\n // that does not exist, so the suggestion list was always empty.\n void fetchCatalog('output/fmp4/anime').then(cat => {\n if (live) setSeriesOptions(cat.dirs.map(d => d.name));\n }).catch(() => { /* suggestions are a nicety; their absence changes nothing */ });\n return () => { live = false; };\n }, [open]);\n\n /**\n * The link field's `onChange`. Prefilling the title from a magnet's `dn=`\n * lives HERE, in the event handler that changes `source`, rather than in a\n * `useEffect` watching it — a `useEffect` that calls `setState` in its body\n * (as opposed to synchronizing with something external) is exactly the\n * pattern `react-hooks/set-state-in-effect` flags, and the derivation is\n * already keyed on the very event that should trigger it, so there is\n * nothing left for an effect to \"synchronize\". Never clobbers a manual\n * title edit — a caller who already typed their own title keeps it even if\n * the source field changes afterward.\n */\n function handleSourceChange(value: string) {\n setSource(value);\n const nowMagnet = guessSubmissionKind(value) === 'magnet' && value.trim().length > 0;\n if (nowMagnet && !titleTouched) {\n const dn = extractMagnetDisplayName(value);\n if (dn) setTitle(dn);\n }\n }\n\n function resetLinkFields() {\n setSource('');\n setSeries('');\n setTitle('');\n setTitleTouched(false);\n }\n\n const magnetReady = !isMagnet || (series.trim().length > 0 && hasEpisodeCode(title));\n\n async function handleSubmit() {\n const trimmed = source.trim();\n if (!trimmed || busy || searching) return;\n if (isMagnet && !magnetReady) return;\n setBusy(true);\n setFailure(null);\n const res = isMagnet\n ? await submitMagnetSubmission(trimmed, { title: title.trim() || undefined, series: series.trim() })\n : await submitSubmission(trimmed);\n setBusy(false);\n if (!res.ok) { setFailure(res.failure); return; }\n debug('submissions', 'Submit accepted', { id: res.value.id, state: res.value.state });\n resetLinkFields();\n // Show the new row at once rather than waiting out a poll tick: the\n // response already carries it, and a field that empties with nothing new\n // beneath it reads as a click that did nothing.\n setRows(prev => [res.value, ...(prev ?? []).filter(r => r.id !== res.value.id)]);\n void refresh();\n }\n\n function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {\n const file = e.target.files?.[0] ?? null;\n setUploadFailure(null);\n if (file && uploadTarget && file.size > uploadTarget.maxBytes) {\n setSelectedFile(null);\n setUploadFailure({ code: 'too_large', maxBytes: uploadTarget.maxBytes });\n if (fileInputRef.current) fileInputRef.current.value = '';\n return;\n }\n setSelectedFile(file);\n }\n\n async function handleUpload() {\n if (!selectedFile || uploadBusy) return;\n setUploadBusy(true);\n setUploadFailure(null);\n\n let target = uploadTarget;\n if (!target) {\n const res = await fetchUploadTarget();\n if (!res.ok) { setUploadBusy(false); setUploadFailure(res.failure); return; }\n target = res.value;\n setUploadTarget(target);\n }\n if (selectedFile.size > target.maxBytes) {\n setUploadBusy(false);\n setUploadFailure({ code: 'too_large', maxBytes: target.maxBytes });\n return;\n }\n\n const uploaded = await uploadFileForSubmission(selectedFile, target, (sent, total, phase) => {\n setUploadProgress({ sent, total, phase });\n });\n if (!uploaded.ok) {\n setUploadBusy(false);\n setUploadProgress(null);\n setUploadFailure(uploaded.failure);\n return;\n }\n\n const recorded = await submitUploadSubmission(uploaded.value.fileId, selectedFile.name);\n setUploadBusy(false);\n setUploadProgress(null);\n if (!recorded.ok) { setUploadFailure(recorded.failure); return; }\n\n debug('submissions', 'Upload accepted', { id: recorded.value.id });\n setSelectedFile(null);\n if (fileInputRef.current) fileInputRef.current.value = '';\n setRows(prev => [recorded.value, ...(prev ?? []).filter(r => r.id !== recorded.value.id)]);\n void refresh();\n }\n\n const uploadStatus = uploadFailure\n ? ('maxBytes' in uploadFailure || uploadFailure.code === 'target_unavailable' || uploadFailure.code === 'upload_failed'\n ? uploadFailureLabel(uploadFailure as UploadFailure)\n : submissionFailureLabel(uploadFailure as SubmissionFailure))\n : null;\n\n return (\n <DrawerShell\n open={open}\n onClose={onClose}\n header={\n <>\n <span className=\"text-xs uppercase tracking-wider text-text-muted\">Add media</span>\n <button\n onClick={() => { void refresh(); }}\n aria-label=\"Refresh submissions\"\n className={`${DRAWER_BTN} ml-auto`}\n >\n <Icon name=\"refresh\" />\n </button>\n </>\n }\n >\n <div className=\"p-3 border-b border-border flex flex-col gap-2\">\n <input\n value={source}\n onChange={e => handleSourceChange(e.target.value)}\n onKeyDown={e => { if (e.key === 'Enter' && !isMagnet) void handleSubmit(); }}\n aria-label=\"Media link\"\n placeholder=\"search, https:// or magnet:\"\n className=\"w-full bg-bg-alt border border-border focus:border-border-light outline-none px-2 py-1 rounded text-xs\"\n />\n {isMagnet && (\n <div className=\"flex flex-col gap-1.5\" data-testid=\"magnet-fields\">\n <input\n value={series}\n onChange={e => setSeries(e.target.value)}\n list=\"add-media-series-options\"\n aria-label=\"Series\"\n placeholder=\"series\"\n className=\"w-full bg-bg-alt border border-border focus:border-border-light outline-none px-2 py-1 rounded text-xs\"\n />\n <datalist id=\"add-media-series-options\">\n {seriesOptions.map(name => <option key={name} value={name} />)}\n </datalist>\n <input\n value={title}\n onChange={e => { setTitleTouched(true); setTitle(e.target.value); }}\n aria-label=\"Title\"\n placeholder=\"title (SxxExx)\"\n className=\"w-full bg-bg-alt border border-border focus:border-border-light outline-none px-2 py-1 rounded text-xs\"\n />\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <button\n onClick={() => { void handleSubmit(); }}\n disabled={busy || !source.trim() || searching || (isMagnet && !magnetReady)}\n aria-label=\"Submit media\"\n className=\"drawer-btn drawer-btn-primary\"\n >\n {busy ? 'Submitting…' : 'Submit'}\n </button>\n {failure && (\n <span data-testid=\"submit-failure\" className=\"text-[11px] bg-danger-bg text-on-danger-bg px-1.5 py-0.5 rounded\">\n {submissionFailureLabel(failure)}\n </span>\n )}\n </div>\n </div>\n\n <MediaSearchResults\n query={source}\n onSubmitted={row => setRows(prev => [row, ...(prev ?? []).filter(r => r.id !== row.id)])}\n />\n\n <div className=\"p-3 border-b border-border flex flex-col gap-2\">\n <div className=\"flex items-center gap-2\">\n <input\n ref={fileInputRef}\n type=\"file\"\n onChange={handleFileChange}\n aria-label=\"Upload file\"\n className=\"text-[11px] flex-1 min-w-0\"\n />\n <button\n onClick={() => { void handleUpload(); }}\n disabled={!selectedFile || uploadBusy}\n aria-label=\"Upload\"\n className=\"drawer-btn drawer-btn-primary\"\n >\n {uploadBusy\n ? (uploadProgress?.phase === 'assembling' ? 'Assembling…' : 'Uploading…')\n : 'Upload'}\n </button>\n </div>\n <span className=\"text-[10px] text-text-muted\" data-testid=\"upload-cap\">\n {uploadTarget ? `cap ${formatSubmissionBytes(uploadTarget.maxBytes)}` : ''}\n </span>\n {uploadStatus && (\n <span data-testid=\"upload-failure\" className=\"text-[11px] bg-danger-bg text-on-danger-bg px-1.5 py-0.5 rounded\">{uploadStatus}</span>\n )}\n </div>\n\n <div className=\"flex-1 overflow-auto\" data-testid=\"my-submissions\">\n {listFailure && (\n <div className=\"mx-3 my-2 px-3 py-2 text-[11px] bg-danger-bg text-on-danger-bg rounded\">{submissionFailureLabel(listFailure)}</div>\n )}\n {rows?.map(item => <SubmissionRow key={item.id} item={item} />)}\n </div>\n </DrawerShell>\n );\n}\n","import { useAppStore } from '../lib/store';\nimport { emitGameSelect } from '../lib/socket';\nimport { debug } from '../lib/debug';\nimport { useGameCatalog } from '../lib/gameCatalog';\nimport bombermanCover from '../assets/games/bomberman/cover.webp';\n\n// The games themselves are DATA — the server's GAMES config, via\n// lib/gameCatalog.ts. A new game brings its own `cover` URL; this is only the\n// built-in game's art, which ships inside this bundle.\nconst BUILT_IN_COVERS: Record<string, string> = { bomberman: bombermanCover };\n\nfunction stopGame() {\n emitGameSelect(null);\n debug('game', 'game_select', { gameId: null });\n}\n\n/**\n * The media drawer's \"Play\" tab: pick a game to turn on for the whole room\n * (broadcast via game_select → every client's idle screen mounts it), or stop\n * the active one.\n *\n * This used to be its own drawer behind its own toolbar button. It is content\n * now, rendered inside `MediaDrawer` beside \"Watch\", so the leader has ONE\n * place to decide what the room does next rather than two buttons that each\n * open a drawer. Like the drawer it lives in, it is only reachable by a leader\n * — the Select Media entry point is leader-gated — so it carries no gate of\n * its own.\n */\nexport function GameGrid({ onDone }: { onDone: () => void }) {\n const activeGameId = useAppStore(s => s.activeGameId);\n const games = useGameCatalog();\n\n function start(gameId: string) {\n emitGameSelect(gameId);\n debug('game', 'game_select', { gameId });\n onDone();\n }\n function stop() {\n stopGame();\n onDone();\n }\n\n return (\n <div className=\"flex-1 overflow-auto p-3\">\n <div className=\"grid grid-cols-2 gap-3\">\n {games.map(g => {\n const isActive = activeGameId === g.id;\n const cover = g.cover ?? BUILT_IN_COVERS[g.id];\n return (\n <button\n key={g.id}\n onClick={() => (isActive ? stop() : start(g.id))}\n className={`group relative aspect-square overflow-hidden rounded-lg border-2 transition-all cursor-pointer ${\n isActive\n ? 'border-primary shadow-lg shadow-primary/30'\n : 'border-border hover:border-primary'\n }`}\n title={isActive ? `Stop ${g.name}` : `Start ${g.name}`}\n >\n {cover ? (\n <img\n src={cover}\n alt={g.name}\n className=\"absolute inset-0 w-full h-full object-cover transition-transform group-hover:scale-105\"\n />\n ) : (\n <div className=\"absolute inset-0 bg-bg-alt\" />\n )}\n {/* Bottom-up gradient for the name label so it stays readable on any cover. */}\n <div className=\"absolute inset-x-0 bottom-0 bg-gradient-to-t from-black/85 via-black/40 to-transparent px-2 pt-6 pb-2\">\n <div className=\"text-sm font-medium text-white leading-tight\">{g.name}</div>\n </div>\n {isActive && (\n <span className=\"absolute top-2 left-2 px-1.5 py-0.5 bg-primary text-[10px] uppercase tracking-wide rounded text-white\">\n Active\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n );\n}\n\n/** Header control for the Play tab; renders nothing when no game is running. */\nexport function StopGameButton({ onDone }: { onDone: () => void }) {\n const activeGameId = useAppStore(s => s.activeGameId);\n if (!activeGameId) return null;\n return (\n <button onClick={() => { stopGame(); onDone(); }} className=\"drawer-btn drawer-btn-danger ml-auto\">\n Stop game\n </button>\n );\n}\n","import { useCallback, useEffect, useMemo, useState } from 'react';\nimport { fetchCatalog, invalidateCatalog } from '../lib/catalog';\nimport { sendLoad, sendHome } from '../lib/controls';\nimport { isDebugEnabled, debug, warn, errorFields } from '../lib/debug';\nimport { probeMediaEndpoints } from '../lib/catalogProbe';\nimport { loadCatalogTree, type CategoryNode, type MediaTree, type PlaylistCategoryNode, type SeriesNode } from '../lib/catalogTree';\nimport { emitPlaylistLoad } from '../lib/socket';\nimport { captureExpansion, mergeExpansion, resetToDefaultExpansion } from '../lib/mediaTreeExpansion';\nimport { useLeaderAffordance } from '../hooks/useStoreSelectors';\nimport { useAppStore } from '../lib/store';\nimport { canSubmitMedia } from '../lib/submissionsApi';\nimport AddMediaDrawer from './AddMediaDrawer';\nimport DrawerShell, { DRAWER_BTN } from './DrawerShell';\nimport { GameGrid, StopGameButton } from './GamePicker';\n\n/** What the room does next: put something on (Watch) or start a waiting-room game (Play). */\ntype DrawerMode = 'watch' | 'play';\n\ninterface MediaDrawerProps { open: boolean; onClose: () => void; }\n\n// A category's expandable children are its series; a playlist category's\n// entries are leaves, so it contributes nothing to merge and is left alone.\nconst childrenOf = (node: CategoryNode | PlaylistCategoryNode) =>\n node.type === 'category' ? node.series : [];\nconst withChildren = (\n node: CategoryNode | PlaylistCategoryNode,\n children: { name: string; expanded?: boolean }[]\n) => (node.type === 'category' ? { ...node, series: children as SeriesNode[] } : node);\n\nexport default function MediaDrawer({ open, onClose }: MediaDrawerProps) {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [tree, setTree] = useState<MediaTree>([]);\n const [filter, setFilter] = useState('');\n // The submit surface is a SECOND drawer, not a section of this one: it owns\n // a field, a queue and a poll, and folding it in here would put all three\n // behind the catalog fetches this drawer performs on open.\n // Only one is rendered at a time (see `open={open && !addOpen}` below), so\n // there is never a second backdrop or a second Escape handler in play.\n const [addOpen, setAddOpen] = useState(false);\n const [mode, setMode] = useState<DrawerMode>('watch');\n const mediaId = useAppStore(s => s.snapshot.mediaId);\n // Friend tier and above — the same boundary `POST /api/submissions` enforces.\n // Read once per render rather than stored: auth is module state set from\n // mount props before this component ever mounts.\n const canSubmit = canSubmitMedia();\n\n const buildTree = useCallback(async () => {\n setLoading(true); setError(null);\n try {\n const result = await loadCatalogTree(fetchCatalog);\n // The drawer stays mounted, so it renders the PREVIOUS tree while this\n // rebuild is in flight. Anything the user expanded in that window is on\n // a tree about to be replaced — carry those flags over, or a background\n // refresh silently collapses a deliberate click right after the drawer\n // opens. Uses the functional form so it merges against the tree as it is\n // AT THAT MOMENT, including expansions made while the fetch was in flight.\n setTree(prev => mergeExpansion(result, captureExpansion(prev, childrenOf), childrenOf, withChildren));\n if (isDebugEnabled()) probeMediaEndpoints();\n } catch (e:any) {\n setError(e.message || 'catalog_error');\n } finally { setLoading(false); }\n }, []);\n\n // Build the tree when the room loads, not when the drawer first opens. This\n // component is mounted (closed) for the life of the room, so without this the\n // leader's first click paid for the whole catalog. Only a leader can open the\n // drawer, so nobody else fetches it. The JSON is a few KB — the tierlist's\n // \"fetch the board on MOUNT\" trade, not its \"warm images on intent\" one.\n // State is set only when the data lands (never synchronously in the effect\n // body), and with no loading flag: nobody is looking at the drawer yet.\n const isLeader = useLeaderAffordance();\n useEffect(() => {\n if (!isLeader) return;\n let live = true;\n loadCatalogTree(fetchCatalog).then(\n result => {\n if (live) setTree(prev => mergeExpansion(result, captureExpansion(prev, childrenOf), childrenOf, withChildren));\n },\n (err: unknown) => warn('catalog', 'Catalog prefetch failed', errorFields(err)),\n );\n return () => { live = false; };\n }, [isLeader]);\n\n // Every open is a fresh start on Watch, with only Anime expanded — not a\n // resumption of whatever the last visit left open. The reset MUST run before\n // the rebuild: `buildTree` merges its result onto the tree as it stands when\n // the fetch lands, so resetting first means a series the user expands during\n // that fetch window still survives, while everything untouched shows\n // the defaults. Resetting after would discard that click. Pinned in\n // mediaTreeExpansion.test.ts. The mount prefetch means the tree is already\n // on screen; this rebuild refreshes it behind the user (a cache hit within\n // lib/catalog.ts's CACHE_MS, a background refetch after).\n useEffect(() => {\n if (!open) return;\n setMode('watch');\n setTree(t => resetToDefaultExpansion(t, childrenOf, withChildren));\n buildTree();\n }, [open, buildTree]);\n\n function handleEpisodeSelect(mediaId: string) {\n sendLoad(mediaId);\n debug('catalog', 'control_load', { mediaId });\n onClose();\n }\n function handlePlaylistSelect(playlistId: string) {\n emitPlaylistLoad(playlistId);\n debug('catalog', 'playlist_load', { playlistId });\n onClose();\n }\n // Room-wide unload: control/home resets the room's media to null for\n // EVERY viewer and ends any active playlist — it is not a second way to\n // dismiss this drawer. It used to be labelled \"Home\", which read as exactly\n // that next to Close, and with no media loaded it genuinely is a no-op that\n // only closes the drawer. Hence the honest label, and hiding it unless\n // there is something to unload.\n function handleUnload() {\n sendHome();\n debug('controls', 'Unload media clicked (media drawer)');\n onClose();\n }\n function toggleSeries(ci: number, si: number) {\n setTree(t => t.map((c,i)=> (i!==ci || !('series' in c))? c : ({ ...c, series: c.series.map((s,j)=> j!==si? s : ({ ...s, expanded: !s.expanded })) })));\n }\n function toggleCategory(ci: number) {\n setTree(t => t.map((c,i)=> i!==ci? c : ({ ...c, expanded: !c.expanded })));\n }\n function refreshAll() {\n invalidateCatalog('');\n buildTree();\n }\n\n const filteredTree = useMemo(() => {\n if (!filter.trim()) return tree;\n const q = filter.toLowerCase();\n return tree.map(node => {\n if (node.type === 'playlist_category') {\n const playlists = node.playlists.filter(p => p.name.toLowerCase().includes(q));\n if (playlists.length) return { ...node, playlists, expanded: true } as PlaylistCategoryNode;\n if (node.name.toLowerCase().includes(q)) return { ...node, expanded: true } as PlaylistCategoryNode;\n return null;\n }\n const seriesFiltered = node.series.map(s => {\n const eps = s.episodes.filter(e => e.name.toLowerCase().includes(q));\n if (eps.length) return { ...s, episodes: eps, expanded: true } as SeriesNode;\n if (s.name.toLowerCase().includes(q)) return { ...s, expanded: true } as SeriesNode;\n return null;\n }).filter(Boolean) as SeriesNode[];\n if (seriesFiltered.length) return { ...node, series: seriesFiltered, expanded: true } as CategoryNode;\n if (node.name.toLowerCase().includes(q)) return { ...node, expanded: true } as CategoryNode;\n return null;\n }).filter(Boolean) as MediaTree;\n }, [tree, filter]);\n\n return (\n <>\n <AddMediaDrawer open={addOpen} onClose={() => setAddOpen(false)} />\n <DrawerShell\n open={open && !addOpen}\n onClose={onClose}\n header={\n <>\n {/* Reuses the tierlist's segmented-tab styling rather than a second\n copy of it. Real classes, not Tailwind utilities: the unlayered\n `button` reset makes every colour/background utility inert. */}\n <div className=\"tl-tabs\" role=\"tablist\" aria-label=\"Media drawer\">\n {(['watch', 'play'] as const).map(m => (\n <button\n key={m}\n role=\"tab\"\n aria-selected={mode === m}\n onClick={() => setMode(m)}\n className=\"tl-tab\"\n >\n {m === 'watch' ? 'Watch' : 'Play'}\n </button>\n ))}\n </div>\n {mode === 'play' && <StopGameButton onDone={onClose} />}\n {mode === 'watch' && <>\n {canSubmit && (\n <button\n onClick={() => setAddOpen(true)}\n aria-label=\"Add Media\"\n className={DRAWER_BTN}\n >\n Add\n </button>\n )}\n {mediaId && (\n <button\n onClick={handleUnload}\n title=\"Unload the current media for everyone in the room\"\n className=\"drawer-btn drawer-btn-primary\"\n >\n Unload\n </button>\n )}\n {/* The spinner is the whole point of this control: a reload that\n hits a warm catalog cache returns too fast to see, so without a\n visible in-flight state you cannot tell a refresh happened.\n Disabled while in flight so a second click can't stack rebuilds. */}\n <button onClick={refreshAll} disabled={loading} className={`${DRAWER_BTN} inline-flex items-center gap-1.5`}>\n {loading && <span className=\"inline-block w-3 h-3 border-2 border-text-muted border-t-transparent rounded-full animate-spin\" />}\n {loading ? 'Reloading…' : 'Reload'}\n </button>\n <input value={filter} onChange={e=>setFilter(e.target.value)} placeholder=\"search\" className=\"ml-auto bg-bg-alt border border-border focus:border-border-light outline-none px-2 py-1 rounded text-xs\" />\n </>}\n </>\n }\n footer={isDebugEnabled() ? (\n <div className=\"p-2 text-[10px] text-text-muted border-t border-border flex justify-end\">\n <span className=\"opacity-70\">verbose</span>\n </div>\n ) : undefined}\n >\n {mode === 'play' ? <GameGrid onDone={onClose} /> : <>\n {loading && !tree.length && <div className=\"px-4 py-2 text-xs text-text-muted\">Loading catalog…</div>}\n {error && <div className=\"mx-3 px-3 py-2 text-xs bg-danger-bg text-on-danger-bg rounded\">{error}</div>}\n <div className=\"flex-1 overflow-auto text-sm py-2\">\n {filteredTree.map((node, ci) => (\n <div key={node.name} className=\"px-3 pb-3\">\n {/* data-* expose the STATE, so E2E asserts which categories are\n open rather than reading the −/+ glyph. */}\n <div\n className=\"flex items-center gap-2 cursor-pointer select-none group\"\n data-category={node.name}\n data-expanded={node.expanded ? 'true' : 'false'}\n onClick={()=>toggleCategory(ci)}\n >\n <span className=\"text-xs uppercase tracking-wider text-text-muted group-hover:text-text-secondary\">{node.name}</span>\n <span className=\"text-[10px] opacity-50\">{node.expanded? '−':'+'}</span>\n </div>\n {node.expanded && node.type === 'playlist_category' && (\n <div className=\"mt-2 space-y-1\">\n {node.playlists.map(p => (\n <button key={p.playlistId} onClick={()=>handlePlaylistSelect(p.playlistId)} className=\"w-full text-left px-2 py-1 rounded hover:bg-bg-alt flex items-center gap-2 group\">\n <span className=\"text-[11px] text-text-tertiary group-hover:text-text-secondary truncate\">{p.name}</span>\n <span className=\"ml-auto text-[10px] text-text underline decoration-primary underline-offset-2 opacity-0 group-hover:opacity-100 transition-opacity\">Load</span>\n </button>\n ))}\n {node.playlists.length === 0 && <div className=\"text-[11px] text-text-muted italic\">(no playlists)</div>}\n </div>\n )}\n {node.expanded && node.type === 'category' && (\n <div className=\"mt-2 space-y-2\">\n {node.series.map((s, si) => (\n <div key={s.name}>\n <div className=\"flex items-center gap-2 cursor-pointer text-text-secondary hover:text-text\" onClick={()=>toggleSeries(ci, si)}>\n <span className=\"text-xs font-medium\">{s.name}</span>\n <span className=\"text-[10px] opacity-40\">{s.expanded? '−':'+'}</span>\n </div>\n {s.expanded && (\n <div className=\"mt-1 ml-3 border-l border-border pl-3 space-y-1\">\n {s.episodes.map(ep => (\n <button key={ep.mediaId} onClick={()=>handleEpisodeSelect(ep.mediaId)} className=\"w-full text-left px-2 py-1 rounded hover:bg-bg-alt flex items-center gap-2 group\">\n <span className=\"text-[11px] text-text-tertiary group-hover:text-text-secondary truncate\">{ep.name}</span>\n <span className=\"ml-auto text-[10px] text-text underline decoration-primary underline-offset-2 opacity-0 group-hover:opacity-100 transition-opacity\">Load</span>\n </button>\n ))}\n {s.episodes.length === 0 && <div className=\"text-[11px] text-text-muted italic\">(no episodes)</div>}\n </div>\n )}\n </div>\n ))}\n {node.series.length === 0 && <div className=\"text-[11px] text-text-muted italic\">(empty)</div>}\n </div>\n )}\n </div>\n ))}\n {!loading && !filteredTree.length && <div className=\"px-4 py-6 text-center text-xs text-text-muted\">No matches</div>}\n </div>\n </>}\n </DrawerShell>\n </>\n );\n}\n"],"names":["cache","inflight","CACHE_MS","fetchCatalog","prefix","now","hit","pending","request","url","getRuntimeServerOrigin","res","data","isDebugEnabled","debug","invalidateCatalog","probeMediaEndpoints","categories","cat","catData","candidate","base","mediaBase","indexUrl","mediaUrl","FRAGMENT_MP4_FILENAME","fetchAndLog","err","warn","label","init","t0","ms","ECCHI_SERIES","norm","name","matchesSeriesList","series","isEcchiSeries","captureExpansion","tree","childrenOf","state","node","children","child","mergeExpansion","withChildren","prev","mergedChildren","wasExpanded","DEFAULT_OPEN_CATEGORY","isOpenByDefault","categoryName","resetToDefaultExpansion","ROOT","CATEGORIES","tryFetch","fetcher","errorFields","loadCatalogTree","categoryListings","playlistListing","seriesPerCategory","listing","d","episodes","f","result","ecchiSeries","i","seriesNodes","playlists","HAS_SCHEME","isSearchText","raw","text","SEARCH_MIN_QUERY","SEARCH_MAX_QUERY","resolveSearchImage","pathOrNull","formatDuration","totalS","s","m","h","ss","getJson","path","signal","searchShows","q","searchVideos","addShowToWatchlist","source","id","value","DEBOUNCE_MS","MediaSearchResults","query","onSubmitted","active","shows","setShows","useState","videos","setVideos","rowState","setRowState","useEffect","ctrl","timer","r","showAnswer","videoAnswer","addShow","show","key","addVideo","video","submitSubmission","submissionFailureLabel","bothEmpty","jsxs","jsx","Section","ResultRow","answer","testId","failed","image","imageClass","title","meta","canAdd","addLabel","onAdd","metaText","Icon","POLL_MS","STATE_CLASS","SubmissionRow","item","withSeries","AddMediaDrawer","open","onClose","setSource","setSeries","setTitle","titleTouched","setTitleTouched","seriesOptions","setSeriesOptions","busy","setBusy","failure","setFailure","rows","setRows","listFailure","setListFailure","uploadTarget","setUploadTarget","selectedFile","setSelectedFile","uploadBusy","setUploadBusy","uploadProgress","setUploadProgress","uploadFailure","setUploadFailure","fileInputRef","useRef","liveRef","isMagnet","useMemo","guessSubmissionKind","searching","refresh","useCallback","fetchMySubmissions","live","fetchUploadTarget","handleSourceChange","dn","extractMagnetDisplayName","resetLinkFields","magnetReady","hasEpisodeCode","handleSubmit","trimmed","submitMagnetSubmission","handleFileChange","e","file","handleUpload","target","uploaded","uploadFileForSubmission","sent","total","phase","recorded","submitUploadSubmission","uploadStatus","uploadFailureLabel","DrawerShell","Fragment","DRAWER_BTN","row","formatSubmissionBytes","BUILT_IN_COVERS","bombermanCover","stopGame","emitGameSelect","GameGrid","onDone","activeGameId","useAppStore","games","useGameCatalog","start","gameId","stop","g","isActive","cover","StopGameButton","MediaDrawer","loading","setLoading","error","setError","setTree","filter","setFilter","addOpen","setAddOpen","mode","setMode","mediaId","canSubmit","canSubmitMedia","buildTree","isLeader","useLeaderAffordance","t","handleEpisodeSelect","sendLoad","handlePlaylistSelect","playlistId","emitPlaylistLoad","handleUnload","sendHome","toggleSeries","ci","si","c","j","toggleCategory","refreshAll","filteredTree","p","seriesFiltered","eps","ep"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"PlaylistPanel-fy0YV4nH.js","sources":["../../src/components/PlaylistRevealPopup.tsx","../../src/components/PlaylistPanel.tsx"],"sourcesContent":["import { useEffect, useState, type ReactNode } from 'react';\nimport type { TPlaylistResultsOut, TVoteValue } from '@watchparty/shared';\nimport { useAppStore } from '../lib/store';\nimport { spriteUrlFor, colorForSprite, isCharacterId } from '../lib/characters';\nimport { StarIcon } from './controls/Icons';\nimport { useIsLeader } from '../hooks/useStoreSelectors';\nimport { emitVoteRemove } from '../lib/socket';\n\n/** How long the ✕ stays armed after the first click before it disarms. */\nconst REMOVE_ARM_MS = 3000;\n\ninterface Props {\n results: TPlaylistResultsOut;\n onClose: () => void;\n}\n\nconst VOTE_WEIGHTS: Record<TVoteValue, number> = {\n super_upvote: 4,\n upvote: 3,\n mid: 2,\n downvote: 1,\n};\n\nconst VOTE_ICONS: Record<TVoteValue, { icon: ReactNode; color: string }> = {\n // Bigger than the other marks on purpose: a super-upvote is the strongest\n // vote and was the hardest to see (a 10px star under the avatar).\n super_upvote: { icon: <StarIcon className=\"w-4 h-4\" />, color: 'bg-warning-bg text-on-warning-bg rounded' },\n upvote: { icon: '▲', color: 'bg-success-bg text-on-success-bg rounded px-0.5' },\n mid: { icon: '─', color: 'text-text-secondary' },\n downvote: { icon: '▼', color: 'bg-danger-bg text-on-danger-bg rounded px-0.5' },\n};\n\nfunction computeScores(results: TPlaylistResultsOut) {\n const { playlist, votes } = results;\n return playlist.videos.map(video => {\n let totalScore = 0;\n let voterCount = 0;\n for (const clientVotes of Object.values(votes)) {\n const v = clientVotes[video.mediaId] as TVoteValue | undefined;\n if (v) { totalScore += VOTE_WEIGHTS[v]; voterCount++; }\n }\n const avgScore = voterCount > 0 ? totalScore / voterCount : 0;\n return { video, avgScore, voterCount };\n }).sort((a, b) => b.avgScore - a.avgScore);\n}\n\nexport default function PlaylistRevealPopup({ results, onClose }: Props) {\n const roster = useAppStore(s => s.roster);\n const isLeader = useIsLeader();\n const scored = computeScores(results);\n // Two clicks to remove: the first arms the ✕, the second sends. A stray\n // click mid-party must not silently drop someone's ballot.\n const [armed, setArmed] = useState<string | null>(null);\n useEffect(() => {\n if (!armed) return;\n const t = setTimeout(() => setArmed(null), REMOVE_ARM_MS);\n return () => clearTimeout(t);\n }, [armed]);\n\n // Ordered list of clients who voted\n const voters = Object.keys(results.votes).filter(\n id => Object.keys(results.votes[id]).length > 0\n );\n // Frozen at reveal by the server, so someone who has since LEFT (and is\n // gone from the live roster) still has an avatar.\n const spriteOf = (id: string) => {\n const sprite = roster[id]?.sprite ?? results.sprites?.[id];\n return isCharacterId(sprite) ? sprite : undefined;\n };\n\n function onRemove(voterKey: string) {\n if (armed !== voterKey) { setArmed(voterKey); return; }\n setArmed(null);\n emitVoteRemove(voterKey);\n }\n\n return (\n <div\n className=\"fixed inset-0 bg-black/70 flex items-center justify-center z-50\"\n onClick={e => { if (e.target === e.currentTarget) onClose(); }}\n >\n <div data-testid=\"reveal-popup\" className=\"bg-bg-card border border-border rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] flex flex-col mx-4\">\n {/* Header */}\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border shrink-0\">\n <h2 className=\"text-sm font-semibold text-text\">\n {results.playlist.name} — Results\n </h2>\n <button\n onClick={onClose}\n aria-label=\"Close results\"\n className=\"p-1 text-text-muted hover:text-text transition-colors cursor-pointer\"\n >\n <svg className=\"w-4 h-4\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M6 18L18 6M6 6l12 12\" />\n </svg>\n </button>\n </div>\n\n {/* Voters — the leader can drop one; the table regenerates for everyone */}\n {voters.length > 0 && (\n <div className=\"flex flex-wrap items-center gap-1.5 px-4 py-2 border-b border-border shrink-0\">\n {voters.map(id => {\n const spriteId = spriteOf(id);\n return (\n <span key={id} className=\"reveal-voter\">\n {spriteId\n ? <img src={spriteUrlFor(spriteId)} alt=\"\" crossOrigin=\"anonymous\" className=\"w-4 h-4 rounded-full object-cover\" />\n : <span className=\"w-4 h-4 rounded-full bg-bg-alt\" />}\n <span className=\"text-xs text-text truncate max-w-[8rem]\">{results.names[id] || id}</span>\n {isLeader && (\n <button\n type=\"button\"\n className=\"reveal-voter-remove\"\n data-armed={armed === id ? '' : undefined}\n onClick={() => onRemove(id)}\n aria-label={`Remove ${results.names[id] || id}'s votes`}\n title={armed === id ? 'Click again to remove' : `Remove ${results.names[id] || id}'s votes`}\n >\n {armed === id ? 'remove?' : '✕'}\n </button>\n )}\n </span>\n );\n })}\n </div>\n )}\n\n {/* Results list */}\n <div className=\"overflow-y-auto flex-1 py-1\">\n {scored.map(({ video, avgScore, voterCount }, rank) => (\n <div\n key={video.mediaId}\n className={`flex items-center gap-3 px-4 py-2 ${rank % 2 === 0 ? '' : 'bg-bg-alt/20'}`}\n >\n {/* Rank */}\n <span className=\"text-xs text-text-muted w-5 shrink-0 text-right\">{rank + 1}</span>\n\n {/* Video name */}\n <span className=\"text-xs text-text truncate flex-1 min-w-0\">\n {video.displayName}\n </span>\n\n {/* Per-voter avatars + vote icon */}\n <div className=\"flex items-center gap-1.5 shrink-0\">\n {voters.map(clientId => {\n const vote = results.votes[clientId]?.[video.mediaId] as TVoteValue | undefined;\n if (!vote) return null;\n const spriteId = spriteOf(clientId);\n const voteStyle = VOTE_ICONS[vote];\n const borderColor = colorForSprite(spriteId ?? null);\n\n return (\n <div key={clientId} className=\"flex flex-col items-center gap-0.5\">\n {/* Avatar */}\n <div className=\"relative w-6 h-6 shrink-0\">\n {spriteId ? (\n <img\n src={spriteUrlFor(spriteId)}\n alt={results.names[clientId] || clientId}\n crossOrigin=\"anonymous\"\n className=\"w-6 h-6 rounded-full object-cover\"\n style={{ border: `1.5px solid ${borderColor}` }}\n />\n ) : (\n <div\n className=\"w-6 h-6 rounded-full bg-bg-alt\"\n style={{ border: `1.5px solid ${borderColor}` }}\n />\n )}\n </div>\n {/* Vote icon */}\n <span className={`text-[9px] leading-none font-bold ${voteStyle.color}`}>\n {voteStyle.icon}\n </span>\n </div>\n );\n })}\n {/* Show dashes for non-voters */}\n {voters.map(clientId => {\n const voted = !!results.votes[clientId]?.[video.mediaId];\n if (voted) return null;\n return (\n <div key={`no-${clientId}`} className=\"flex flex-col items-center gap-0.5 opacity-20\">\n <div className=\"w-6 h-6 rounded-full bg-bg-alt border border-border\" />\n <span className=\"text-[9px] leading-none text-text-muted\">·</span>\n </div>\n );\n })}\n </div>\n\n {/* Avg score */}\n <span className=\"text-xs text-text-secondary w-8 text-right shrink-0 font-medium\">\n {voterCount > 0 ? avgScore.toFixed(1) : '—'}\n </span>\n </div>\n ))}\n </div>\n </div>\n </div>\n );\n}\n","import React, { useState } from 'react';\nimport { useAppStore } from '../lib/store';\nimport { useIsLeader } from '../hooks/useStoreSelectors';\nimport { emitPlaylistLoad, emitPlaylistSelect, emitVoteUpdate, emitRevealRequest } from '../lib/socket';\nimport { resolveMediaAsset, resolveCardImage } from '../lib/mediaUrls';\nimport { debug } from '../lib/debug';\nimport { STORAGE_KEYS } from '../lib/storageKeys';\nimport { voteOwnerScope } from '../lib/auth';\nimport type { TVoteValue } from '@watchparty/shared';\nimport PlaylistRevealPopup from './PlaylistRevealPopup';\nimport { StarIcon } from './controls/Icons';\nimport { readStored, writeStored } from '../lib/browserStorage';\n\n// ── Vote icons ────────────────────────────────────────────────────────────────\n\n\nfunction UpvoteIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <polyline points=\"18 15 12 9 6 15\" />\n </svg>\n );\n}\n\nfunction MidIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <line x1=\"5\" y1=\"12\" x2=\"19\" y2=\"12\" />\n </svg>\n );\n}\n\nfunction DownvoteIcon() {\n return (\n <svg className=\"w-3.5 h-3.5\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2.5}>\n <polyline points=\"6 9 12 15 18 9\" />\n </svg>\n );\n}\n\n// Colours live in `.vote-btn` (styles.css), keyed by `data-vote`: a <button>'s\n// colour/background utilities are discarded by the unlayered `button` reset, so\n// the per-vote `text-*` classes these carried never rendered — every vote icon\n// was the same white, selected or not.\nconst VOTE_OPTIONS: { value: TVoteValue; icon: React.ReactNode; label: string }[] = [\n { value: 'super_upvote', icon: <StarIcon className=\"w-4 h-4\" />, label: 'Super' },\n { value: 'upvote', icon: <UpvoteIcon />, label: 'Up' },\n { value: 'mid', icon: <MidIcon />, label: 'Mid' },\n { value: 'downvote', icon: <DownvoteIcon />, label: 'Down' },\n];\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction formatDuration(ms: number): string {\n const total = Math.round(ms / 1000);\n const h = Math.floor(total / 3600);\n const m = Math.floor((total % 3600) / 60);\n const s = total % 60;\n return h > 0\n ? `${h}:${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`\n : `${m}:${String(s).padStart(2, '0')}`;\n}\n\n// ── Load form (leader, no playlist loaded) ────────────────────────────────────\n\nfunction LoadForm() {\n const [input, setInput] = useState('');\n const [loading, setLoading] = useState(false);\n const [err, setErr] = useState<string | null>(null);\n\n const handleLoad = () => {\n const id = input.trim();\n if (!id) return;\n setLoading(true);\n setErr(null);\n emitPlaylistLoad(id);\n // Error will arrive via the 'error' socket event and show as a toast.\n // Reset loading state after a short delay.\n setTimeout(() => setLoading(false), 1500);\n };\n\n return (\n <div className=\"p-4 flex flex-col gap-3\">\n <p className=\"text-xs text-text-muted\">Enter a playlist ID to load it for the room.</p>\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={input}\n onChange={e => setInput(e.target.value)}\n onKeyDown={e => e.key === 'Enter' && handleLoad()}\n placeholder=\"Playlist ID (e.g. april-4)\"\n className=\"modal-input flex-1 text-xs px-2 py-1.5\"\n disabled={loading}\n />\n <button\n onClick={handleLoad}\n disabled={loading || !input.trim()}\n className=\"modal-btn-update text-xs px-3 py-1.5 rounded disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n Load\n </button>\n </div>\n {err && <p className=\"text-xs bg-danger-bg text-on-danger-bg px-2 py-1 rounded\">{err}</p>}\n </div>\n );\n}\n\n// ── Empty state (viewer, no playlist) ─────────────────────────────────────────\n\nfunction EmptyState() {\n return (\n <div className=\"flex-1 flex items-center justify-center p-4\">\n <p className=\"text-xs text-text-muted text-center\">No playlist loaded yet.</p>\n </div>\n );\n}\n\n// ── Main panel ────────────────────────────────────────────────────────────────\n\nexport default function PlaylistPanel() {\n const isLeader = useIsLeader();\n const playlist = useAppStore(s => s.playlist);\n const activeIndex = useAppStore(s => s.activePlaylistIndex);\n const myVotes = useAppStore(s => s.myVotes);\n const voteProgress = useAppStore(s => s.voteProgress);\n const results = useAppStore(s => s.playlistResults);\n const currentMediaId = useAppStore(s => s.snapshot.mediaId);\n const [showResults, setShowResults] = useState(false);\n\n // Show results popup when a reveal arrives. NOT on an `update` (a voter was\n // removed): that re-sends the table to everyone, and reopening it would put\n // the popup back in front of people who had already closed it.\n React.useEffect(() => {\n if (results && !results.update) setShowResults(true);\n }, [results]);\n\n // Restore votes from localStorage when a playlist is first loaded\n React.useEffect(() => {\n if (!playlist || playlist.type !== 'vote') return;\n const savedVotes: Record<string, TVoteValue> = {};\n for (const video of playlist.videos) {\n const key = STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, video.mediaId);\n const saved = readStored(key) as TVoteValue | null;\n if (saved && VOTE_OPTIONS.some(o => o.value === saved)) {\n savedVotes[video.mediaId] = saved;\n }\n }\n debug('playlist', 'Checked localStorage votes', { playlistId: playlist.id, found: Object.keys(savedVotes).length });\n if (Object.keys(savedVotes).length === 0) return;\n useAppStore.getState().applyPlaylistState({ playlist, activeIndex, myVotes: { ...myVotes, ...savedVotes } });\n for (const [mediaId, vote] of Object.entries(savedVotes)) {\n emitVoteUpdate(playlist.id, mediaId, vote);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [playlist?.id]);\n\n if (!playlist) {\n return isLeader ? <LoadForm /> : <EmptyState />;\n }\n\n // `basic` playlists are a plain watch-through — no vote buttons, progress,\n // or reveal. Only `vote` playlists show the rating mechanic.\n const isVoting = playlist.type === 'vote';\n const totalUsers = voteProgress.length;\n\n const handleVote = (mediaId: string, vote: TVoteValue) => {\n emitVoteUpdate(playlist.id, mediaId, vote);\n debug('playlist', 'Vote emitted', { playlistId: playlist.id, mediaId, vote });\n writeStored(STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, mediaId), vote);\n\n // Mirror the server's super_upvote exclusivity rule: if this vote is a star,\n // downgrade any other video's star to upvote so the visual ring updates immediately.\n const updatedVotes = { ...myVotes, [mediaId]: vote };\n if (vote === 'super_upvote') {\n for (const [otherId, otherVote] of Object.entries(updatedVotes)) {\n if (otherId !== mediaId && otherVote === 'super_upvote') {\n updatedVotes[otherId] = 'upvote';\n writeStored(STORAGE_KEYS.voteKey(voteOwnerScope(), playlist.id, otherId), 'upvote');\n }\n }\n }\n\n useAppStore.getState().applyPlaylistState({\n playlist,\n activeIndex,\n myVotes: updatedVotes,\n });\n };\n\n return (\n <div className=\"flex flex-col flex-1 min-h-0\">\n {/* Header */}\n <div className=\"flex items-center gap-2 px-3 py-2 border-b border-border shrink-0\">\n {playlist.cover && (\n <img\n src={resolveMediaAsset(playlist.cover)}\n alt=\"\"\n className=\"w-8 h-8 object-cover rounded shrink-0 bg-bg-alt\"\n />\n )}\n <span className=\"text-xs font-medium text-text truncate flex-1\">\n {playlist.name}\n </span>\n {isLeader && isVoting && (\n <button\n onClick={() => emitRevealRequest()}\n className=\"text-xs text-text-muted hover:text-text transition-colors cursor-pointer shrink-0\"\n title=\"Reveal votes to all\"\n >\n Reveal\n </button>\n )}\n </div>\n\n {/* Vote progress bar (leader only, vote playlists only) */}\n {isLeader && isVoting && totalUsers > 0 && (\n <div className=\"px-3 py-1.5 border-b border-border shrink-0\">\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-[10px] text-text-muted shrink-0\">Votes:</span>\n <div className=\"flex gap-1 flex-wrap flex-1\">\n {voteProgress.map(p => (\n <span key={p.clientId} className=\"text-[10px] text-text-secondary\">\n {p.name} {p.votedCount}/{p.totalVideos}\n </span>\n ))}\n </div>\n </div>\n </div>\n )}\n\n {/* Video list */}\n <div className=\"flex-1 overflow-y-auto\">\n {playlist.videos.map((video, i) => {\n const isActive = activeIndex === i;\n const myVote = myVotes[video.mediaId] as TVoteValue | undefined;\n const isCurrentMedia = currentMediaId === video.mediaId;\n\n return (\n <div\n key={video.mediaId}\n className={`flex items-center gap-2 px-3 py-2 border-b border-border/50 group ${\n isActive ? 'bg-primary/10' : 'hover:bg-bg-alt/50'\n }`}\n >\n {/* Play indicator */}\n <div className=\"w-3.5 shrink-0 text-text-muted\">\n {isCurrentMedia ? (\n <svg className=\"w-3.5 h-3.5 text-text\" viewBox=\"0 0 24 24\" fill=\"currentColor\">\n <polygon points=\"5 3 19 12 5 21 5 3\" />\n </svg>\n ) : (\n <span className=\"text-[10px] text-text-muted\">{i + 1}</span>\n )}\n </div>\n\n {/* Thumbnail. No longer flat-playlist-only: `poster.jpg` is\n derived by convention when a clip carries no explicit\n thumbnail (see resolveCardImage), and a 404 hides the image. */}\n <div className=\"relative shrink-0\">\n <img\n src={resolveCardImage(video.mediaId, video.thumbnail)}\n alt=\"\"\n loading=\"lazy\"\n onError={e => {\n const el = e.currentTarget as HTMLImageElement;\n // Hide the whole slot, not just the img — the duration\n // badge is positioned against it.\n (el.parentElement ?? el).style.display = 'none';\n }}\n className=\"w-14 h-10 object-cover rounded bg-bg-alt\"\n />\n {video.durationMs ? (\n <span className=\"absolute bottom-0 right-0 px-1 text-[9px] leading-tight rounded-tl rounded-br bg-bg-card/90 text-text-secondary\">\n {formatDuration(video.durationMs)}\n </span>\n ) : null}\n </div>\n\n {/* Video name — leader can click to select */}\n <button\n className={`flex-1 text-left text-xs truncate cursor-pointer transition-colors min-w-0 ${\n isLeader\n ? 'hover:text-text text-text-secondary'\n : 'text-text-secondary cursor-default'\n } ${isActive ? 'text-text font-medium' : ''}`}\n onClick={isLeader ? () => emitPlaylistSelect(i) : undefined}\n title={isLeader ? `Play: ${video.displayName}` : video.displayName}\n disabled={!isLeader}\n >\n {video.displayName}\n </button>\n\n {/* Vote buttons (vote playlists only) */}\n {isVoting && (\n <div className=\"flex gap-0.5 shrink-0\">\n {VOTE_OPTIONS.map(opt => {\n const isSelected = myVote === opt.value;\n return (\n <button\n key={opt.value}\n onClick={() => handleVote(video.mediaId, opt.value)}\n title={opt.label}\n className=\"vote-btn\"\n data-vote={opt.value}\n data-selected={isSelected ? '' : undefined}\n aria-pressed={isSelected}\n >\n {opt.icon}\n </button>\n );\n })}\n </div>\n )}\n </div>\n );\n })}\n </div>\n\n {/* Results popup (vote playlists only) */}\n {isVoting && showResults && results && (\n <PlaylistRevealPopup\n results={results}\n onClose={() => setShowResults(false)}\n />\n )}\n </div>\n );\n}\n"],"names":["REMOVE_ARM_MS","VOTE_WEIGHTS","VOTE_ICONS","jsx","StarIcon","computeScores","results","playlist","votes","video","totalScore","voterCount","clientVotes","v","avgScore","a","b","PlaylistRevealPopup","onClose","roster","useAppStore","isLeader","useIsLeader","scored","armed","setArmed","useState","useEffect","t","voters","id","spriteOf","sprite","isCharacterId","onRemove","voterKey","emitVoteRemove","e","jsxs","spriteId","spriteUrlFor","rank","clientId","vote","voteStyle","borderColor","colorForSprite","UpvoteIcon","MidIcon","DownvoteIcon","VOTE_OPTIONS","formatDuration","ms","total","h","m","s","LoadForm","input","setInput","loading","setLoading","err","setErr","handleLoad","emitPlaylistLoad","EmptyState","PlaylistPanel","activeIndex","myVotes","voteProgress","currentMediaId","showResults","setShowResults","React","savedVotes","key","STORAGE_KEYS","voteOwnerScope","saved","readStored","o","debug","mediaId","emitVoteUpdate","isVoting","totalUsers","handleVote","writeStored","updatedVotes","otherId","otherVote","resolveMediaAsset","emitRevealRequest","p","i","isActive","myVote","isCurrentMedia","resolveCardImage","el","emitPlaylistSelect","opt","isSelected"],"mappings":"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"}