{"_id":"@appshoteditor/shot-dsl","_rev":"10-ce9f233d9701a070c322ed9e72822cc6","name":"@appshoteditor/shot-dsl","dist-tags":{"latest":"0.5.3"},"versions":{"0.1.0":{"name":"@appshoteditor/shot-dsl","version":"0.1.0","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.1.0","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"84a1c3d6be829c694f04f1de3149eb16afe1254f","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.1.0.tgz","fileCount":9,"integrity":"sha512-gM6m1r5qHkCqnwd/EA2CmVNBe6uFd/jFwUmLacIhi8kgbRo8OKPhKF4XspjibE5giimU2TjDbjCA1Y4den4Qqw==","signatures":[{"sig":"MEQCIE4xZF+jMT52lGCWUkOyf8iYbGNUtpJYnujMcw2p3jg9AiBrXlEoNtpmnxHc9v6pFcBf1Kzz6X51G/UOkdjHAr8sHw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":31335},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"99fbfb1f1405760d51a1e3bd9057eb21450c7714","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.1.0_1779970030134_0.34374304332509675","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@appshoteditor/shot-dsl","version":"0.1.1","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.1.1","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"c78c18082e37bbed38d7a2f19b9e52155fe2a3f4","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.1.1.tgz","fileCount":9,"integrity":"sha512-4efaYO+vv6pup5huKJhkptRPVrWqI1gHlyeAi9JeupLSuKuc5ABZYyjZrxuDpjd70hrZN4/O7+FSCDoPShkC0g==","signatures":[{"sig":"MEYCIQCBl1sM6u6UaKrOucrftFZ+Y9HxhM/hlpzo4l+NTAWD/QIhALz+p9f8nDHPXenJhO94A8dq0gAopQVe35T8q2ZdR/Sd","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":32486},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"34eca4729b4d60c614db64e4f6a01830698a0e5c","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.1.1_1780966912105_0.8299097073330413","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@appshoteditor/shot-dsl","version":"0.1.2","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.1.2","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"69a03aeb0afa8f1cf54dfa07c525893f228216fc","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.1.2.tgz","fileCount":9,"integrity":"sha512-fx6qnbZsbqgf5JWEx26s+RBswA0H9VRKw+mnT1Cb5dTbwFO41i3H8EGpR7oE+So0nLbT/fSqLeGSX+grgHPYmw==","signatures":[{"sig":"MEYCIQDEyHmI4LTei/LWpGw+GMCSWvgX0VP6fdLfCHrnbU+l1QIhAMorBS3Boa2wWS6lpFcEemcEN08I17wVeeOiViNcLL7b","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":33883},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"ea32edb1d5102576d51571b2f08d8679108c05ab","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.1.2_1781048937250_0.9950791613427046","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@appshoteditor/shot-dsl","version":"0.2.0","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.2.0","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"239b0025c79b40afeb4fcf718eb4f4e92184f7e5","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.2.0.tgz","fileCount":9,"integrity":"sha512-Sph7phygfijl4U4xyRHvsma5QqzC8qW8KynDLZnYt6cQgpoo5k4ECWARbNW8R9Hr8jn+74eBCLO2+M3fAWxJEQ==","signatures":[{"sig":"MEQCIEmDMqWZEY8VudAblZ8etntKV7sY8HvvcE7s5y44f1VKAiAdwwCKY4AmgCMG+ZLxVRJq+rCb5xyD/l1cffHoctfC6g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCUFgxkmykNbvvm5EjETffob+npOYzvlWGSRCIqzH3KawIgMC6o0acB1zjPAI0XLGLz81tTGeTCVCL+bZGpghMwXE8=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":41187},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"00a985edbab8072fd861f0f1adbd50e073be0716","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.2.0_1790078499696_0.7997774811924587","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@appshoteditor/shot-dsl","version":"0.3.0","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.3.0","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"439b245d18fa604d21620893392b9a26f44a60a3","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.3.0.tgz","fileCount":9,"integrity":"sha512-OIBqx6vAa82xYh3etOCl/Kw6dZQI14EMh6Dcem4wd0g2GWYnaSLcaCceAmizOb4yegYsr3rmWPYKsR1HXNOVkQ==","signatures":[{"sig":"MEYCIQDlHi2uTVqimuQB1OYAz+sF0W1ImRtuCHYrfzCUmoCP9gIhAPaJN7EshPODhMOzCKoXAj5grgI44ZZgJctaY4/ZAjgh","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIGUEC9Kzip/7ncEBHtblzi/gLX3PQKIFfqDnB8qhJHvgAiEA8PAeAG5u014H2LqRVy1qONe8swpFh+H52EKfT+vnMsQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57835},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"036428bcbf803930c06ae87dc584c3f1d4bb7471","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.3.0_1790087506774_0.3835709699876888","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"name":"@appshoteditor/shot-dsl","version":"0.4.0","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.4.0","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"ff2763f4ed0439f208475f05101a4705b6222bd9","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.4.0.tgz","fileCount":12,"integrity":"sha512-8Zt5AlZc8TjhqU5X2hTN+vuPUF3DSGj2jZZpOnxgly3RlLAwVbuPYIfDAWKzAKaZRRwMPLr3Lb9OZn/YB2S6kw==","signatures":[{"sig":"MEQCICUtL/ui0wnjSdX8wDEW/qOI3xlMEeLpokKRoK+H3qR2AiBpcnv3pvg8lXu86aMhPREv8HAqE9mnj2kcGYevFVaoIQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIHIAnhP50WMh+NZAAEO+ACjk83jrm03pYZNdbeA8KqHpAiBG6NPS9YofadcepNPb6Hjokd3KWpyt0RB80tKgk/YbGQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":136404},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"2ed326cbd3dda60d136a7b31d32a1f5b42ce48ed","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.4.0_1790093596291_0.4387373419223668","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@appshoteditor/shot-dsl","version":"0.5.0","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.5.0","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"fc410c328ad8622509257055ccb579617fe1b6b4","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.5.0.tgz","fileCount":15,"integrity":"sha512-BI8cs6lZdlARpsv70zDdATci8VjcAbn4xdOn+pheIE5nzx48YvF/FkyjRGzkr4e4BJvd3zG+LfRiTqECG0RIQg==","signatures":[{"sig":"MEUCIQC0bA4UT8s/2mfUx3+GVhZtpM4pBUwDiWd4o89+PZMaUAIgIMmA0+Fn9QlMEXga5aDzIfxgmwSBBO/f+lr09p/ZemY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEQCIDAxWLZGmqQv4coJ0Gg9V5CNFfVprTIatWw0xgsReGUBAiB7oTMQi2TMfvHdonNjxewpf93bjMmmiM9NmjTChygRjQ==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":212482},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"187aafe5fe54b691bc5466955ebe82d5cb19b010","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.5.0_1790129326973_0.8559181695166131","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"@appshoteditor/shot-dsl","version":"0.5.1","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.5.1","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"0a9ac6cf46896acaac3e56dd03ebfaafb0298afa","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.5.1.tgz","fileCount":15,"integrity":"sha512-uz4L0N8ohLeit55uuUaxnckg6grPGxv7GokJMuUTYfn4g13vJFqLCEE2+It+kk3bbgTUFU9hGEa4qMMPESxXjQ==","signatures":[{"sig":"MEUCIQCWkUC+4kHBgjECDxgmn7tHLKA8ziGqD20+8ZcRxKFk3AIgPx2Z2elsiHcTu/JoflDDnIp5e9Ks0M/vKjAu4biHh5k=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEUCIQCZBLZ5D5oiik8/x5bm3e0pCRz4Jr9MY8wB12dGlFHEpwIgVZkKmBfVT9M/sUncoF9IHZAdWCOx+lb3jyaQUjAvr20=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":217990},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"dd79f256d10533540c7bb50a4b1ba6d3ea4ff48b","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.5.1_1790336926733_0.2698293338569826","host":"s3://npm-registry-packages-npm-production"}},"0.5.2":{"name":"@appshoteditor/shot-dsl","version":"0.5.2","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"license":"MIT","_id":"@appshoteditor/shot-dsl@0.5.2","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"743174b5d446b1cdc6a5fd42c6f2b953612855ab","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.5.2.tgz","fileCount":15,"integrity":"sha512-C9OQP4tEpUxZ+SqhrMema3d2F/l+68lOaksgSuHZlpoB0RJHFkmUk3b2rjC4fimVnuOCPaGeefxXC2kUBsMMmA==","signatures":[{"sig":"MEUCIQD6tePSHIZXERvnfZlAKfiSEDchbCC8WZyabX0QFB4K3QIgFCyEPWZVBgxSZCr/hINhw40wHvvN9deTzMOAEg2+e7I=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"sig":"MEYCIQDu2A6WOuX5Dz7LOGYqYn46jsmnOkkhgPcNFo2xXmYT6wIhANslM6pdBojLOKnO23yoZdAb++wme5nSYfHZlGtq2b3f","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":220085},"main":"./src/index.ts","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"26531de97295b2d6fb717ce5984a51d417892929","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/shot-dsl_0.5.2_1790343516602_0.49143246505565963","host":"s3://npm-registry-packages-npm-production"}},"0.5.3":{"_id":"@appshoteditor/shot-dsl@0.5.3","bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"dist":{"shasum":"aa4705b12331fbffbedb746f20b2dd4b0261cf10","tarball":"https://registry.npmjs.org/@appshoteditor/shot-dsl/-/shot-dsl-0.5.3.tgz","fileCount":15,"integrity":"sha512-2d73OrF1DTj4liqKdEow2iHxH9ed5JGs2ZdJXigyG1HewKYA/N9O64xtC+UtJgyhnNOrDrl89AUKgVYYJyEsmQ==","signatures":[{"sig":"MEUCIQCSUXe1+So472D7MyuAAPuyImsV5O6p5yW8yshZtete0QIgPeUowFBp1t9lTmrl73Ezaj0SF+na0iHy0FebDYFKVOs=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIDjoPFIEtUEOBLefb2U9U00+gos8Y+BvQ7XIRJizSB+VAiEAznxcMPsc/Hv0Vk/xlAI+G4oz2KbA2wDH+bTtu/sYBJg="}],"unpackedSize":224973},"main":"./src/index.ts","name":"@appshoteditor/shot-dsl","type":"module","types":"./src/index.ts","module":"./src/index.ts","exports":{".":{"types":"./src/index.ts","default":"./src/index.ts"}},"gitHead":"a9b851cbddb8a858956e530366be2c75a2de0b36","license":"MIT","version":"0.5.3","_npmUser":{"name":"geetfun","email":"simon@furvur.com"},"homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"_npmVersion":"10.9.3","description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","directories":{},"maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"sideEffects":false,"_nodeVersion":"22.20.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/shot-dsl_0.5.3_1790355703960_0.414258294971211"}}},"time":{"created":"2026-05-28T12:07:09.984Z","modified":"2026-09-25T17:01:44.230Z","0.1.0":"2026-05-28T12:07:10.303Z","0.1.1":"2026-06-09T01:01:52.295Z","0.1.2":"2026-06-09T23:48:57.431Z","0.2.0":"2026-09-22T12:01:39.799Z","0.3.0":"2026-09-22T14:31:46.868Z","0.4.0":"2026-09-22T16:13:16.380Z","0.5.0":"2026-09-23T02:08:47.067Z","0.5.1":"2026-09-25T11:48:46.820Z","0.5.2":"2026-09-25T13:38:36.700Z","0.5.3":"2026-09-25T17:01:44.056Z"},"bugs":{"url":"https://github.com/AppShotEditor/app-shot-editor/issues"},"license":"MIT","homepage":"https://github.com/AppShotEditor/app-shot-editor#readme","keywords":["app-store-screenshots","appshoteditor","agent-skill","device-frames","layout-dsl"],"repository":{"url":"git+https://github.com/AppShotEditor/app-shot-editor.git","type":"git","directory":"packages/shot-dsl"},"description":"App Shot Editor layout DSL + device-frame geometry — framework-free building blocks for composing editable App Store screenshot layouts. Intended for use via a bundler (Vite, esbuild, etc.).","maintainers":[{"name":"geetfun","email":"simon@furvur.com"}],"readme":"# @appshoteditor/shot-dsl\n\nThe layout DSL behind [appshoteditor.com](https://appshoteditor.com) — framework-free building blocks\nfor composing **editable App Store / Play Store screenshot layouts**, plus device-frame geometry.\n\nIt is the single source of truth shared by the web editor, the Cloudflare Worker (handoff validation),\nand the [App Store Screenshots agent skill](https://github.com/AppShotEditor/app-store-screenshots-skill)'s\ncomposer — so a layout composed by the skill renders identically in the editor.\n\n## What's inside\n\n- **Types** — `Template`, `ScreenLayersJSON`, `LayerJSON`, `BackgroundJSON`, `CURRENT_SCHEMA_VERSION`.\n- **Validators** — `validateTemplate()` (strict, error-reporting), `isValidTemplate`, `migrateScreenLayersJSON`,\n  `validateDeviceScreenshot`, `isUploadedScreenshotSrc`.\n- **Builders** — `makeTextLayer`, `makeImageLayer`, `makeShapeLayer`, `makeSolid/GradientBackground`,\n  `makeScreen`, `makeTemplate`.\n- **Device geometry** — `deviceFrames`, `getDeviceFrame`, `makeDeviceFrameLayer`, `calculateDeviceScale`,\n  `computeScreenshotPlacement` (the screenshot-in-frame fit/clip math the editor uses).\n- **Composer** — `composeTemplate(plan)`: a benefit/screenshot plan → a validated, device-framed `Template`\n  (`composeSet(plan)` returns the same template plus a `report` with lint warnings and per-screen\n  geometry). Per screen: `headline`, optional `subheadline` / `headlineColor` / `subheadlineColor`, and\n  `layout` (`text-top` default, `text-bottom`, `device-bleed` — see `COMPOSE_LAYOUTS`). All geometry\n  (device scale/position, font sizes, padding) is derived from the canvas size, so a plan composes to\n  the same proportions at 280×608 editor units or 1320×2868 native pixels.\n- **Set layout system (0.4.0)** — `solveVertical` / `NO_TANGENT` (the no-tangent rule), `focusReach`,\n  `rotatedBox` (`layout-system.ts`); `makeVariants(plan)` (three concept plans, `variants.ts`); small\n  colour helpers (`color.ts`).\n- **Art direction (0.5.0)** — `breakLines` (balanced line breaks, `typography.ts`), tonal palettes +\n  `harmonize` (one WCAG-AA text colour per set, `palette.ts`), panorama motifs (`decor.ts`), and in\n  `composeSet`: the hero, accent rhythm, callouts, mascots and shadows (see \"Art direction\" below).\n\n```ts\nimport { composeTemplate, validateTemplate } from '@appshoteditor/shot-dsl';\n\nconst template = composeTemplate({\n  name: 'My App',\n  screens: [\n    {\n      headline: 'Track every workout',\n      subheadline: 'Sets, reps and rest — logged for you',\n      layout: 'text-top',\n      background: { type: 'gradient', gradient: { type: 'linear', colorStops: [/* … */] } },\n      deviceId: 'iphone_16_pro',\n      screenshot: { url: '…', width: 1179, height: 2556 }\n    }\n  ]\n});\nvalidateTemplate(template); // { valid: true, errors: [] }\n```\n\n## Set layout system (0.4.0)\n\n`composeSet` lays a plan out as ONE system, the way a screenshot designer would:\n\n1. **One system per set.** It measures every screen's text block first and reserves a text area\n   for the TALLEST one (per canvas size). The headline size is fixed for the whole set\n   (`HEADLINE_SIZE` × the type unit). Long copy is never shrunk: it gets a lint warning instead.\n   Screens that share a canvas size, presentation, device, layout and tilt (`report.screens[i].group`)\n   get the same subject scale and baseline, so copy length never moves or resizes anything.\n2. **No tangents.** A device (or frameless screenshot) either clears the far edge by ≥ 4% of H or\n   bleeds off it by ≥ 12% of its own height. The band in between is the \"just touching\" look, and\n   `solveVertical` never emits it. `style.bleed` picks how a tangent is resolved:\n   - `auto` (default): a device that already clears by the margin, or already bleeds decisively\n     (≥ 12%, without cropping its focus band), stays as it is. Otherwise it grows toward the\n     90%-of-W cap; if that's still short of a 12% bleed, \"minimum disturbance\" decides between\n     clearing and shifting down: it clears at the near edge (no shift) when that clear scale is\n     ≥ 88% of the grown/capped scale (`NO_TANGENT.clearOverShift`); otherwise it shifts down only\n     as far as exactly a 12% bleed.\n   - `none`: always fully visible.\n   - `deep`: grows the subject toward a 25% bleed (still capped at 90% W). When growth alone can't\n     reach 25% and a down-shift would be the only way to reach even 12%, the same minimum-disturbance\n     rule applies: it clears at the near edge when that costs ≤ 12% of the grown/capped scale;\n     otherwise it shifts down only as far as exactly a 12% bleed needs — never deeper, even though\n     the target is 25%.\n3. **No side tangents.** No subject is wider than 90% of W (`NO_TANGENT.maxWidth`). The part that is\n   actually on the canvas keeps ≥ 5% of W from both side edges. The only side bleed is a deliberate\n   panorama straddle across a seam.\n4. **Focus-aware, on all four edges.** Marked `focus` band corners (`{ top, bottom }`, fractions of\n   the screenshot height) always stay:\n   - ≥ 2% of H inside the top and bottom edges;\n   - ≥ 2% of W inside the side edges and any seam. This includes tilted subjects: their rotated\n     corners are checked.\n\n   A bleed that would crop the band is reduced, or turned into the clear-with-margin option. When a\n   tilt doesn't fit the side rules, the composer first shrinks the device, or the zoom card (keeping\n   its aspect), by at most 20%. After\n   that it reduces the tilt in quarter steps, down to straight if needed, and warns `tilt-reduced`.\n   In a group, the most restrictive focus decides for every screen. A zoom card that can't show a\n   whole band warns `zoom-focus-cropped`.\n5. **Room for the device.** The device must keep at least half the size it would get on this canvas\n   with NO copy. That size is height-limited for tall devices on landscape canvases, such as an iMac\n   on 608×380. If the text leaves less room, `composeSet` throws \"copy too long for this canvas … cut\n   the subheadline or headline\". If even the no-copy fit is under 30% of the target width, it throws\n   \"device … doesn't fit a W×H canvas … use the device's own canvas\", because the canvas, not the\n   copy, is the problem.\n\nText metrics are per font (`FONT_CHAR_WIDTH`, plus character classes: capitals and m/w count\nwider, i/l and punctuation narrower). They are calibrated to be conservative for every\n`COMPOSE_FONTS` entry, so an estimated 2-line headline never renders as 3.\n\nPlan additions (all optional; a 0.3.0 plan composes and validates unchanged):\n\n| Field | Where | Meaning |\n| --- | --- | --- |\n| `style.presentation` | plan | `device` (default) · `frameless` (rounded screenshot + shadow) · `zoom` (magnified crop of `crop`, else the centre of the `focus` band) |\n| `style.tilt`, `style.tiltScreens` | plan | Degrees (clockwise) applied only to the listed screen indices. A tilted subject prefers a deeper bleed under `auto`/`deep`, but clears instead when its scale cap (from keeping the side margins) stops it reaching a decisive bleed without a big down-shift (minimum disturbance, see above); the tilt itself is reduced (`tilt-reduced`) if it can't keep the side margins at all. |\n| `style.bleed` | plan | `auto` · `none` · `deep` (see above) |\n| `style.palette` | plan | `{ mode: \"tonal\" \\| \"family\" \\| \"sequence\", colors: [hex…], tone? }`. Background for screens that omit one; ONE text colour per set (see Art direction). |\n| `style.panorama` | plan | `{ spans?: [[0,1], …], straddle?: true \\| [screen…], decoration?: \"orbs\" \\| \"honeycomb\" \\| \"wave\" \\| \"none\" }`. Adjacent screens share one continuous background; `straddle` picks the span-start screens whose device crosses the seam (only by ≥ 18% of it). |\n| `style.hero`, `style.rhythm`, `style.callouts`, `style.shadows` | plan | 0.5.0 — see Art direction. |\n| `art` | plan | 0.5.0 `[{ id, url, width, height, faces? }]` brand art (uploaded-asset URLs only). |\n| `callout`, `mascot` | screen | 0.5.0 — a magnified crop of the selling element · brand art placement. |\n| `style.font` | plan | One of `COMPOSE_FONTS` (the editor's font list). |\n| `focus`, `crop`, `presentation`, `tilt`, `badge` | screen | Focus band · zoom crop `{x,y,w,h}` · per-screen overrides · a social-proof pill above the headline |\n\nOutput shapes:\n\n- **Text colour (0.5.0: ONE per set).** An explicit `headlineColor` / `subheadlineColor` always\n  wins. Otherwise EVERY screen takes part in choosing one set colour: `harmonize` tries a dark and a\n  light candidate (a deep / pale tint of the brand hue for tonal palettes, else `#111827` / white),\n  sampling every screen on a 5×7 grid. Palette / span backgrounds may be re-toned (moved away from\n  the text in small steps until the worst sample passes WCAG 4.5); explicit backgrounds are sampled\n  as they are and never recoloured. The candidate needing the least re-toning wins (the other only\n  if it also passes on the explicit backgrounds for ≤ 0.15 more). If the set colour still can't reach\n  4.5 on an explicit background, ONLY that screen gets its own best colour (a panorama span whose\n  fill is explicit counts as ONE unit: one colour for the whole span, so the scene stays consistent) —\n  `report.screens[i].textColorSource === \"screen\"` (else `\"set\"`, or `\"plan\"` for an explicit\n  `headlineColor`). An explicit-only set keeps white whenever white passes (0.4.0). The headline and\n  subheadline are also checked densely right behind their boxes; a screen that no colour can bring to\n  4.5 warns `contrast-low` — never silent. The subheadline's brand tint is used only when it clears\n  4.6 (a margin, so it never lands at 4.49).\n- **Frameless / zoom** is a plain `type: 'image'` layer:\n  - Its `src` is the uploaded screenshot, with `crossOrigin: 'anonymous'`.\n  - It has a rounded `clipPath` Rect in the image's local space, `imageCornerRadius` (canvas units,\n    the editor's corner-radius control) and a Fabric `shadow` (blur/offset in the object's own units,\n    like the editor's shadow control).\n  - Zoom adds Fabric `cropX`/`cropY` with `width`/`height` set to the crop size.\n  - Every screen of a zoom group uses the same card box.\n- **Tilt** is the device layer's `angle`. The editor rotates the screenshot with the frame on attach, on\n  move/scale/rotate, and in the offscreen export.\n- **Panorama** is pure DSL:\n  - Each screen of a span gets a locked `Rect` of size N·W × H at `left = N·W/2 − k·W`, painted with\n    the span-start screen's background.\n    - A solid colour stays solid.\n    - A linear gradient keeps its stops and `angle`, applied across N·W; without an `angle` it runs\n      corner to corner.\n    - Radial gradients and explicit `coords` throw a clear error.\n  - Optional seam orbs are moved or shrunk so they never overlap either neighbour's text block, and\n    dropped when there's no room.\n  - With `straddle` (`true` = every span, or a list of span-start screen indices), that span's first\n    device is shifted right so it crosses the seam. More than one crossing per set is lint-warned\n    (`panorama-straddle-count`). A copy of it,\n    named \"… (continued)\", is emitted on the next screen at `left − W`, below that screen's own device.\n  - The straddle is only ever a rightward shift. It must keep the left side margin, and when a\n    `focus` band is marked, the band stays ≥ 2% of W inside the seam. Otherwise the straddle is\n    skipped (`straddle-skipped`) and the device is placed like any other screen.\n  - In the editor, the halves are independent layers: moving one does not move the other.\n- **Badge** is a rounded `Rect` pill plus a `Textbox` (`templateKey: 'badge'`). They sit in a badge row\n  above the headline, reserved set-wide, so they never collide with the text or the device.\n\n### Art direction (0.5.0)\n\n- **Line breaks.** Headlines and subheadlines are set in `breakLines` lines. An explicit `\\n` is ALWAYS\n  a break — lines the author broke are never merged, on any device or role. Only a line (explicit or\n  not) wider than the box is broken further: into the greedy line count, re-balanced (no single word\n  on the last line, breaks after punctuation preferred, never after \"the\"/\"to\"/…; past 40 tokens it\n  breaks greedily, so absurd copy fails fast). CJK / kana / Hangul count ~1 em per glyph and may\n  break between glyphs. The breaks are emitted as `\\n` in the Textbox. A single word wider than the\n  box throws (Fabric can't wrap inside a word — it would widen the box past the margins).\n  Subheadline: 0.5× the headline, weight 600, full opacity, tinted 18% toward the brand hue when\n  that still passes AA. Text box 0.88 W.\n- **Tonal palette.** `{ mode: \"tonal\", colors: [base, accent?], tone: \"light\" | \"vivid\" | \"deep\" }`:\n  screen i gets tonal step i % 4 of the base hue as a vertical gradient (the hero step 0). The accent\n  fills badge pills. Tonal panorama spans run one ramp through their members' tones.\n- **Hero** (`style.hero`, default ON for screen 1; `false` = off): headline 1.25× (stepped down in\n  0.05s — to 1× if needed — so it never has more lines than at the set size; `scale` 1–1.4), its own group with bleed `deep`, optional `layout`, `tilt`,\n  `badge`, `mascot`. `report.screens[i].role` is `hero`.\n- **Rhythm** (`style.rhythm: { every, treatment }`): screens where `i % every === every − 1` are\n  `accent`s — `text-bottom` layout, or `callout` (a forced callout + deep bleed). All `set`-role\n  screens still share one group, type size, text area, scale and baseline.\n- **Callouts** (`screen.callout` crop, or `style.callouts: \"auto\"` from a focus band ≤ 50% tall): an\n  image layer with the SAME uploaded `src` + `cropX`/`cropY`, rounded, shadowed, magnified 1.6–2.2×\n  (down to 1.25× to fit), inside 3.5% side margins, between the text block and a 4% far-edge margin.\n  The requested crop is automatically padded, on whichever sides have room inside the screenshot,\n  before it's magnified, so the rounded corners of the card don't clip content sitting at the\n  requested crop's own corners (e.g. a selection handle); a side with no room (flush against the\n  screenshot edge) can still clip there. It may cover its own source slice (the pop-out), but\n  it hides at most `CALLOUT_MAX_FOCUS_COVER` (35%) of the REST of the focus band: it is centred on its\n  source when that allows, else moved up/down or out to a side edge (breaking out of the device), else\n  made smaller; if nothing works it is skipped (`callout-skipped`). `report.screens[i].callout.focusCover`\n  is the share hidden. Never on zoom cards or a straddling device.\n- **Mascots** (`plan.art` + `screen.mascot` / `style.hero.mascot`): an image layer (uploaded src,\n  `flipX` to face into the canvas when `faces` is given, shadow). Anchors `headline` / `device-top` /\n  `device-side` / `seam` are tried in order with fallbacks at 100/85/70% size; a placement must clear\n  every text line, the badge, the subheadline, the callout, every other mascot on the screen\n  (including a neighbour's seam half) and the focus band's core (its outer 10% W may be overlapped).\n  `seam` puts one mascot across a panorama seam, emitted on both screens (±W). Otherwise\n  `mascot-skipped`.\n- **Badges** keep their label at WCAG ≥ 4.5 on the pill: a solid (tonal accent) pill is toned\n  toward black or white until it does; a translucent one gets fainter.\n- **Shadows** (`style.shadows`, default true), tinted with the brand hue: device layers get\n  `fabricData.deviceShadow = { color, blur, offsetX, offsetY }` in CANVAS units — the editor casts it\n  from the frame's screenshot (a Fabric shadow on the frame PNG would fall into its transparent screen\n  hole); frameless / zoom / callouts / mascots get a Fabric `shadow` in object units.\n  `validateTemplate` checks `deviceShadow` (device layers only; colour ≤ 64 chars; blur 0…canvasWidth;\n  offsets ±canvasWidth).\n- **Panorama motif** (`decoration: \"honeycomb\" | \"wave\"`): the motif is laid out across the whole span,\n  but each screen emits only ITS part (the hexagons / wave segments touching it plus a 2-stroke\n  overdraw) as one locked `Path` in its own coordinates, rounded to 0.1 unit; both neighbours emit\n  the same points around a seam, so it runs on continuously. (0.5.0-pre emitted the whole N·W path on\n  every screen — O(N²) bytes.)\n- **Handoff size.** `MAX_HANDOFF_BYTES` (256 KiB, the server's cap) and `handoffBytes(template)` (UTF-8\n  size of the `{ template }` request body). The skill's `lint` / `compose` / `publish` / `variants`\n  error over the cap and warn over 80%. A 30-screen phone + iPad + Mac honeycomb panorama is ≈ 166 KB.\n\n`report.warnings` (`ComposeWarning`) flags:\n\n- a headline over 5 words, or estimated at more than 2 lines at the set size;\n- a subheadline estimated at more than 1 line;\n- more than 2 tilted screens;\n- a seam crossing a headline or focus band, or a straddle that can't be checked because no focus\n  band is marked;\n- skipped straddles, and more than one straddle per set;\n- tilts reduced to keep the side margins;\n- zoom crops that can't show their whole focus band;\n- long badges;\n- 0.5.0: a stranded last headline word (`headline-orphan`), a screen no colour can bring to AA\n  (`contrast-low`), a callout that can't be magnified or placed without hiding the focus band\n  (`callout-skipped`), no room for a mascot (`mascot-skipped`).\n\nBackward compatibility (0.5.0): a 0.4.0 plan still composes, but screen 1 is now the hero (1.25×\nheadline, `deep` bleed, its own device pose) and devices get shadows — `style.hero: false` /\n`style.shadows: false` restore the 0.4.0 look. Balanced line breaks can change where a headline\nwraps, and the new warnings can make `--strict` fail for a plan that passed before.\n\nWarnings never block composing.\n\n`makeVariants(plan)` returns three distinct ART DIRECTIONS, intended as Product Page Optimization\ntest candidates. Each is named `<name> — A Brand Classic` / `— B Clean Frameless` / `— C Story Panorama`:\n\n- **A Brand Classic:** device frames on the `vivid` tonal palette, hero with the mascot (first `art`)\n  + badge, callouts (the input's explicit `style.callouts`, else `auto`), rhythm every 4 (`text-bottom`).\n- **B Clean Frameless:** `light` tonal palette, full-width frameless screenshots with a `deep` bleed\n  (a zoom card only for an explicit `crop` — B1), callouts (same default as A), hero at 1.3× with the mascot.\n- **C Story Panorama:** `deep` tonal palette; spans = the input's `panorama.spans`, else triples and\n  pairs (`panoramaRuns`); `decoration` = the input's (not `orbs`), else `wave`; a mascot crossing the\n  first seam of every span but the hero's; the hero tilted (the input's `style.tilt`, or 8°); bleed\n  `deep`; a straddle only if the input asks.\n\n**Kept from the input** in every concept:\n- copy (headline / subheadline / badge) and `headlineColor` / `subheadlineColor`;\n- `background`, `deviceId`, `screenshot`, `focus`, `crop`, `callout`, `mascot`, `art`;\n- canvas size;\n- the brand colours (`style.palette.colors`; else the first screen's background colour), `style.font`,\n  and `style.bleed: none`.\n\n**Overridden:**\n- every `layout` becomes `text-top` (the hero and rhythm accents differ on purpose);\n- per-screen `presentation` and `tilt` are dropped;\n- palette mode → `tonal` with the concept's tone; hero, rhythm, panorama per concept;\n- `callouts` per concept too, but only C's is fixed (`none`) — A and B keep the input's explicit\n  `style.callouts`, defaulting to `auto` only when the input leaves it unset.\n\n## Layer order\n\nA screen's `layers` array is **bottom → top**: index 0 is painted first (canvas add order), the last\nentry is frontmost. A composed screen is `[device, headline, subheadline?]`; the editor's built-in\ntemplates list background shapes, then the device, then text. (The editor's layers *panel* shows the\nreverse — top first.)\n\n## Device screenshots (0.3.0)\n\nA device mockup is **one** layer. Its screenshot is a property of the device layer, at\n`fabricData.screenshot`:\n\n```json\n{\n  \"id\": \"device-frame-layer-1790080000000-abc123def\",\n  \"name\": \"iPhone 17 Pro Max\",\n  \"type\": \"device\",\n  \"visible\": true,\n  \"locked\": false,\n  \"fabricData\": {\n    \"type\": \"image\",\n    \"src\": \"/devices/iphone-17-pro-max.webp\",\n    \"left\": 140, \"top\": 438.69, \"width\": 1520, \"height\": 3068,\n    \"scaleX\": 0.175, \"scaleY\": 0.175, \"originX\": \"center\", \"originY\": \"center\",\n    \"layerId\": \"device-frame-layer-1790080000000-abc123def\",\n    \"layerType\": \"deviceFrame\",\n    \"layerRole\": \"frame\",\n    \"deviceFrameId\": \"device-frame-layer-1790080000000-abc123def\",\n    \"deviceId\": \"iphone_17_pro_max\",\n    \"deviceScale\": 0.175,\n    \"screenshot\": { \"src\": \"/api/screenshots/<asset-id>/raw\", \"width\": 1320, \"height\": 2868 }\n  }\n}\n```\n\n`screenshot` is `{ src, width, height, rotation? }` (`DeviceScreenshotJSON`; `rotation` ∈ 0/90/180/270).\nProducers emit **no** screenshot geometry or clip path: the editor places the image under the frame\nand clips it to the screen with the same code path it uses when a user drops a screenshot onto a\nframe (`computeScreenshotPlacement`), so the screenshot belongs to the frame (it is not a separate\nlayer). `validateTemplate` requires `src` to be an uploaded-asset URL (`/api/screenshots/<id>/raw`) —\nexternal, protocol-relative and `data:` URLs are rejected. It also requires every device layer to have a\nknown `deviceId` and a frame-asset `src` (`/devices/<name>.webp|png`), and rejects duplicate layer ids\nor `deviceFrameId`s within a screen. Every other image reference in a handoff (image layers, pattern\n`fill.source`, nested objects) must be an uploaded screenshot or a `/devices/` asset. Frame metadata\n(`layerRole: 'frame'`, `deviceFrameId`, `deviceId`, `deviceScale`) is only allowed on device layers,\n`layerRole: 'screenshot'` only on legacy image layers, and editor-internal props\n(`EDITOR_INTERNAL_PROPS`, e.g. `pendingScreenshot`) are rejected everywhere, as are `__proto__` /\n`constructor` / `prototype` keys at any depth (`FORBIDDEN_KEYS`).\n\n`computeScreenshotPlacement` centres the screenshot on the frame's screen bounds; the screen-centre\noffset scales with the frame's scale *relative to* `deviceScale` (0.2.x editors multiplied by the\nabsolute frame scale, misplacing off-centre screens such as MacBooks by a few canvas units).\n\n**Legacy (≤ 0.2.0):** screenshots were a separate `type: 'image'` layer with\n`fabricData.layerRole: 'screenshot'` and a `deviceFrameId` matching its frame, placed directly below\nthe frame. `validateTemplate` still accepts that shape (its `src` must also be an uploaded-asset URL)\nand the editor converts it on import.\n`makeDeviceFrameLayers()` (which returned `{ screenshot, frame }`) was replaced by\n`makeDeviceFrameLayer()` (returns the single device layer).\n\n> Consumers: a template in the 0.3.0 shape needs an editor that understands `fabricData.screenshot`\n> (appshoteditor.com with the matching importer). 0.4.0 adds no new layer shapes that the 0.3.0 validator\n> or importer would reject: frameless/zoom are ordinary image layers, and panorama and badges are\n> ordinary shape and text layers. 0.5.0 likewise: callouts and mascots are image layers with\n> uploaded-asset srcs, motifs are `Path` shape layers — all accepted by the 0.4.0 validator (and by\n> 0.5.0's, which additionally checks the shape of `deviceShadow`). Only the device shadow needs a\n> 0.5.0-aware editor to render; older editors ignore it.\n\nZero runtime dependencies. The `schemaVersion` is the compatibility contract between producers and the editor.\n\n> Intended for use via a bundler (Vite, esbuild, etc.). Device `frameAsset` values are URL paths the\n> editor serves; this package ships only the geometry, not the frame images.\n","readmeFilename":"README.md"}