From 97c29d79b9213c855bc4edfd28d16d704805f3bc Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Wed, 25 Jun 2025 12:38:10 -0500 Subject: [PATCH 01/23] Move web files into src/ --- blinkywww/README | 3 +++ blinkywww/{ => src}/SOTC-DnDLists.js | 0 blinkywww/{ => src}/delete.png | Bin blinkywww/{ => src}/drag.css | 0 blinkywww/{ => src}/get_chrome.png | Bin blinkywww/{ => src}/get_firefox.png | Bin blinkywww/{ => src}/index.php | 0 blinkywww/{ => src}/led_dark.png | Bin blinkywww/{ => src}/led_lit.png | Bin blinkywww/{ => src}/led_x.png | Bin blinkywww/{ => src}/move.gif | Bin blinkywww/{ => src}/msg_anim.gif | Bin blinkywww/{ => src}/msg_marq.gif | Bin blinkywww/{ => src}/progress_bar_30px_tall.png | Bin blinkywww/{ => src}/white.png | Bin 15 files changed, 3 insertions(+) rename blinkywww/{ => src}/SOTC-DnDLists.js (100%) rename blinkywww/{ => src}/delete.png (100%) rename blinkywww/{ => src}/drag.css (100%) rename blinkywww/{ => src}/get_chrome.png (100%) rename blinkywww/{ => src}/get_firefox.png (100%) rename blinkywww/{ => src}/index.php (100%) rename blinkywww/{ => src}/led_dark.png (100%) rename blinkywww/{ => src}/led_lit.png (100%) rename blinkywww/{ => src}/led_x.png (100%) rename blinkywww/{ => src}/move.gif (100%) rename blinkywww/{ => src}/msg_anim.gif (100%) rename blinkywww/{ => src}/msg_marq.gif (100%) rename blinkywww/{ => src}/progress_bar_30px_tall.png (100%) rename blinkywww/{ => src}/white.png (100%) diff --git a/blinkywww/README b/blinkywww/README index 1dbc8c5..2cc3008 100644 --- a/blinkywww/README +++ b/blinkywww/README @@ -3,3 +3,6 @@ http://www.wayneandlayne.com/blinky_programmer/ Sorry that the code is probably really awkward or doing lots of things wrong. We're not really web developers, and only know enough to get by (also known as "knowing just enough to be dangerous"). +# Development + +## Testing \ No newline at end of file diff --git a/blinkywww/SOTC-DnDLists.js b/blinkywww/src/SOTC-DnDLists.js similarity index 100% rename from blinkywww/SOTC-DnDLists.js rename to blinkywww/src/SOTC-DnDLists.js diff --git a/blinkywww/delete.png b/blinkywww/src/delete.png similarity index 100% rename from blinkywww/delete.png rename to blinkywww/src/delete.png diff --git a/blinkywww/drag.css b/blinkywww/src/drag.css similarity index 100% rename from blinkywww/drag.css rename to blinkywww/src/drag.css diff --git a/blinkywww/get_chrome.png b/blinkywww/src/get_chrome.png similarity index 100% rename from blinkywww/get_chrome.png rename to blinkywww/src/get_chrome.png diff --git a/blinkywww/get_firefox.png b/blinkywww/src/get_firefox.png similarity index 100% rename from blinkywww/get_firefox.png rename to blinkywww/src/get_firefox.png diff --git a/blinkywww/index.php b/blinkywww/src/index.php similarity index 100% rename from blinkywww/index.php rename to blinkywww/src/index.php diff --git a/blinkywww/led_dark.png b/blinkywww/src/led_dark.png similarity index 100% rename from blinkywww/led_dark.png rename to blinkywww/src/led_dark.png diff --git a/blinkywww/led_lit.png b/blinkywww/src/led_lit.png similarity index 100% rename from blinkywww/led_lit.png rename to blinkywww/src/led_lit.png diff --git a/blinkywww/led_x.png b/blinkywww/src/led_x.png similarity index 100% rename from blinkywww/led_x.png rename to blinkywww/src/led_x.png diff --git a/blinkywww/move.gif b/blinkywww/src/move.gif similarity index 100% rename from blinkywww/move.gif rename to blinkywww/src/move.gif diff --git a/blinkywww/msg_anim.gif b/blinkywww/src/msg_anim.gif similarity index 100% rename from blinkywww/msg_anim.gif rename to blinkywww/src/msg_anim.gif diff --git a/blinkywww/msg_marq.gif b/blinkywww/src/msg_marq.gif similarity index 100% rename from blinkywww/msg_marq.gif rename to blinkywww/src/msg_marq.gif diff --git a/blinkywww/progress_bar_30px_tall.png b/blinkywww/src/progress_bar_30px_tall.png similarity index 100% rename from blinkywww/progress_bar_30px_tall.png rename to blinkywww/src/progress_bar_30px_tall.png diff --git a/blinkywww/white.png b/blinkywww/src/white.png similarity index 100% rename from blinkywww/white.png rename to blinkywww/src/white.png From b0931b262960cc04318dff481d40968c0710baf7 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Wed, 25 Jun 2025 12:41:18 -0500 Subject: [PATCH 02/23] Remove PHP --- blinkywww/src/{index.php => index.html} | 68 +++++-------------------- 1 file changed, 12 insertions(+), 56 deletions(-) rename blinkywww/src/{index.php => index.html} (85%) diff --git a/blinkywww/src/index.php b/blinkywww/src/index.html similarity index 85% rename from blinkywww/src/index.php rename to blinkywww/src/index.html index 84774d0..6cc7a78 100644 --- a/blinkywww/src/index.php +++ b/blinkywww/src/index.html @@ -1,5 +1,12 @@ - + + +Blinky POV and Blinky GRID Programming + + + -

2. Programming

Enter programming mode: Hold down the button while turning on the power switch. Release the button. Hold the Blinky up to the screen. Be sure to align the sensors correctly: The sensor labeled C points to clock, and D points to data. Press and release the button when you're ready to begin transmission. Press the Go button to start.

-


Check out this awesome animated guide to programming!

+


Check out this awesome animated guide to programming!

Please note! On 2011/09/10 we switched the locations of the clock and data squares below to make it easier to hold the Blinky device while programming, based on customer feed back we have received over the past few months. This requires no change in the Blinky firmware, only a change in how you align the sensors on the screen. Please let us know if this change is a dealbreaker.

From 6eeab78c76935dd68dd4144a2c0b794be8398643 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Wed, 25 Jun 2025 13:47:54 -0500 Subject: [PATCH 04/23] Add Playwright tests --- blinkywww/.gitignore | 6 + blinkywww/README | 13 +- blinkywww/package-lock.json | 1067 ++++++++++++++++++++++++++++++++ blinkywww/package.json | 21 + blinkywww/playwright.config.js | 80 +++ blinkywww/tests/test.spec.js | 178 ++++++ 6 files changed, 1363 insertions(+), 2 deletions(-) create mode 100644 blinkywww/.gitignore create mode 100644 blinkywww/package-lock.json create mode 100644 blinkywww/package.json create mode 100644 blinkywww/playwright.config.js create mode 100644 blinkywww/tests/test.spec.js diff --git a/blinkywww/.gitignore b/blinkywww/.gitignore new file mode 100644 index 0000000..a41aa66 --- /dev/null +++ b/blinkywww/.gitignore @@ -0,0 +1,6 @@ +# Playwright +node_modules/ +test-results/ +playwright-report/ +blob-report/ +playwright/.cache/ diff --git a/blinkywww/README b/blinkywww/README index 2cc3008..eaba1f8 100644 --- a/blinkywww/README +++ b/blinkywww/README @@ -1,8 +1,17 @@ This is the code that runs the Blinky Programmer website: http://www.wayneandlayne.com/blinky_programmer/ -Sorry that the code is probably really awkward or doing lots of things wrong. We're not really web developers, and only know enough to get by (also known as "knowing just enough to be dangerous"). +We wrote this code circa 2010? It could use some love. # Development -## Testing \ No newline at end of file +Try `npm install` to install the dependencies. If you don't have npm installed, you can try that. + +`npm install` + +You can serve the site locally with `npm run start` which starts a local web server at http://localhost:3000. + +## Testing + +We use Playwright. You can run `npm run test` but you can also use `npx playwright test` to run the tests directly. + diff --git a/blinkywww/package-lock.json b/blinkywww/package-lock.json new file mode 100644 index 0000000..261501e --- /dev/null +++ b/blinkywww/package-lock.json @@ -0,0 +1,1067 @@ +{ + "name": "blinky", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "blinky", + "version": "1.0.0", + "dependencies": { + }, + "devDependencies": { + "playwright": "^1.53.1", + "serve": "^14.2.4", + "@playwright/test": "^1.53.1", + "@types/node": "^24.0.4" + } + }, + "node_modules/@playwright/test": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.53.1.tgz", + "integrity": "sha512-Z4c23LHV0muZ8hfv4jw6HngPJkbbtZxTkxPNIg7cJcTc9C28N/p2q7g3JZS2SiKBBHJ3uM1dgDye66bB7LEk5w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "playwright": "1.53.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@types/node": { + "version": "24.0.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.4.tgz", + "integrity": "sha512-ulyqAkrhnuNq9pB76DRBTkcS6YsmDALy6Ua63V8OhrOBgbcYt6IOdzpw5P1+dyRIyMerzLkeYWBeOXPpA9GMAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.8.0" + } + }, + "node_modules/@zeit/schemas": { + "version": "2.36.0", + "resolved": "https://registry.npmjs.org/@zeit/schemas/-/schemas-2.36.0.tgz", + "integrity": "sha512-7kjMwcChYEzMKjeex9ZFXkt1AyNov9R5HZtjBKVsmVpw7pa7ZtlCGvCBC2vnnXctaYN+aRI61HjIqeetZW5ROg==", + "license": "MIT" + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-align": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-3.0.1.tgz", + "integrity": "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==", + "license": "ISC", + "dependencies": { + "string-width": "^4.1.0" + } + }, + "node_modules/ansi-align/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/ansi-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-align/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", + "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/arch": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", + "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/boxen": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/boxen/-/boxen-7.0.0.tgz", + "integrity": "sha512-j//dBVuyacJbvW+tvZ9HuH03fZ46QcaKvvhZickZqtB271DxJ7SNRSNxrV/dZX0085m7hISRZWbzWlJvx/rHSg==", + "license": "MIT", + "dependencies": { + "ansi-align": "^3.0.1", + "camelcase": "^7.0.0", + "chalk": "^5.0.1", + "cli-boxes": "^3.0.0", + "string-width": "^5.1.2", + "type-fest": "^2.13.0", + "widest-line": "^4.0.1", + "wrap-ansi": "^8.0.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/camelcase": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-7.0.1.tgz", + "integrity": "sha512-xlx1yCK2Oc1APsPXDL2LdlNP6+uu8OCDdhOBSVT279M/S+y75O30C2VuD8T2ogdePBBl7PfPF4504tnLgX3zfw==", + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/chalk": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.0.1.tgz", + "integrity": "sha512-Fo07WOYGqMfCWHOzSXOt2CxDbC6skS/jO9ynEcmpANMoPrD+W1r1K6Vx7iNm+AQmETU1Xr2t+n8nzkV9t6xh3w==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-0.4.0.tgz", + "integrity": "sha512-/ghrgmhfY8RaSdeo43hNXxpoHAtxdbskUHjPpfqUWGttFgycUhYPGx3YZBCnUCvOa7Doivn1IZec3DEGFoMgLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/chalk-template/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/cli-boxes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-3.0.0.tgz", + "integrity": "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clipboardy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/clipboardy/-/clipboardy-3.0.0.tgz", + "integrity": "sha512-Su+uU5sr1jkUy1sGRpLKjKrvEOVXgSgiSInwa/qeID6aJ07yh+5NWc3h2QfjHjBnfX4LhtFcuAWKUsJ3r+fjbg==", + "license": "MIT", + "dependencies": { + "arch": "^2.2.0", + "execa": "^5.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.7.4.tgz", + "integrity": "sha512-jaSIDzP9pZVS4ZfQ+TzvtiWhdpFhE2RDHz8QJkpX9SIpLq88VueF5jJw6t+6CUQcAoA6t+x89MLrWAqpfDE8iQ==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.5", + "bytes": "3.0.0", + "compressible": "~2.0.16", + "debug": "2.6.9", + "on-headers": "~1.0.2", + "safe-buffer": "5.1.2", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha512-kRGRZw3bLlFISDBgwTSA1TMBFN6J6GWDeubmDE3AF+3+yXL8hTWv8r5rkLbqYXY4RjPk/EzHnClI3zQf1cFmHA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "license": "MIT" + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "license": "MIT", + "bin": { + "is-docker": "cli.js" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-port-reachable": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-port-reachable/-/is-port-reachable-4.0.0.tgz", + "integrity": "sha512-9UoipoxYmSk6Xy7QFgRv2HDyaysmgSG75TFQs6S+3pDM7ZhKTF/bskZV+0UlABHzKjNVhPjYCLfeZUEg1wXxig==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", + "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "license": "MIT", + "dependencies": { + "is-docker": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC" + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "license": "MIT" + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/on-headers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==", + "license": "(WTFPL OR MIT)" + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-to-regexp": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", + "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", + "license": "MIT" + }, + "node_modules/playwright": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.53.1.tgz", + "integrity": "sha512-LJ13YLr/ocweuwxyGf1XNFWIU4M2zUSo149Qbp+A4cpwDjsxRPj7k6H25LBrEHiEwxvRbD8HdwvQmRMSvquhYw==", + "license": "Apache-2.0", + "dependencies": { + "playwright-core": "1.53.1" + }, + "bin": { + "playwright": "cli.js" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "fsevents": "2.3.2" + } + }, + "node_modules/playwright-core": { + "version": "1.53.1", + "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.53.1.tgz", + "integrity": "sha512-Z46Oq7tLAyT0lGoFx4DOuB1IA9D1TPj0QkYxpPVUnGDqHHvDpCftu1J2hM2PiWsNMoZh8+LQaarAWcDfPBc6zg==", + "license": "Apache-2.0", + "bin": { + "playwright-core": "cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha512-kA5WQoNVo4t9lNx2kQNFCxKeBl5IbbSNBl1M/tLkw9WCn+hxNBAW5Qh8gdhs63CJnhjJ2zQWFoqPJP2sK1AV5A==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/registry-auth-token": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.3.2.tgz", + "integrity": "sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==", + "license": "MIT", + "dependencies": { + "rc": "^1.1.6", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/registry-url": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", + "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "license": "MIT", + "dependencies": { + "rc": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/serve": { + "version": "14.2.4", + "resolved": "https://registry.npmjs.org/serve/-/serve-14.2.4.tgz", + "integrity": "sha512-qy1S34PJ/fcY8gjVGszDB3EXiPSk5FKhUa7tQe0UPRddxRidc2V6cNHPNewbE1D7MAkgLuWEt3Vw56vYy73tzQ==", + "license": "MIT", + "dependencies": { + "@zeit/schemas": "2.36.0", + "ajv": "8.12.0", + "arg": "5.0.2", + "boxen": "7.0.0", + "chalk": "5.0.1", + "chalk-template": "0.4.0", + "clipboardy": "3.0.0", + "compression": "1.7.4", + "is-port-reachable": "4.0.0", + "serve-handler": "6.1.6", + "update-check": "1.5.4" + }, + "bin": { + "serve": "build/main.js" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/serve-handler": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/serve-handler/-/serve-handler-6.1.6.tgz", + "integrity": "sha512-x5RL9Y2p5+Sh3D38Fh9i/iQ5ZK+e4xuXRd/pGbM4D13tgo/MGwbttUk8emytcr1YYzBYs+apnUngBDFYfpjPuQ==", + "license": "MIT", + "dependencies": { + "bytes": "3.0.0", + "content-disposition": "0.5.2", + "mime-types": "2.1.18", + "minimatch": "3.1.2", + "path-is-inside": "1.0.2", + "path-to-regexp": "3.3.0", + "range-parser": "1.2.0" + } + }, + "node_modules/serve-handler/node_modules/mime-db": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", + "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/serve-handler/node_modules/mime-types": { + "version": "2.1.18", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", + "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", + "license": "MIT", + "dependencies": { + "mime-db": "~1.33.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "license": "MIT", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/undici-types": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", + "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/update-check": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/update-check/-/update-check-1.5.4.tgz", + "integrity": "sha512-5YHsflzHP4t1G+8WGPlvKbJEbAJGCgw+Em+dGR1KmBUbr1J36SJBqlHLjR7oob7sco5hWHGQVcr9B2poIVDDTQ==", + "license": "MIT", + "dependencies": { + "registry-auth-token": "3.3.2", + "registry-url": "3.1.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/widest-line": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/widest-line/-/widest-line-4.0.1.tgz", + "integrity": "sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==", + "license": "MIT", + "dependencies": { + "string-width": "^5.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + } + } +} diff --git a/blinkywww/package.json b/blinkywww/package.json new file mode 100644 index 0000000..b881665 --- /dev/null +++ b/blinkywww/package.json @@ -0,0 +1,21 @@ +{ + "name": "blinky", + "version": "1.0.0", + "description": "Blinky POV/GRID programming interface", + "author": "Adam Wolf, Matthew Beckler", + "main": "index.js", + "directories": { + "test": "playwright test" + }, + "scripts": { + "start": "serve src/" + }, + "dependencies": { + "playwright": "^1.53.1", + "serve": "^14.2.4" + }, + "devDependencies": { + "@playwright/test": "^1.53.1", + "@types/node": "^24.0.4" + } +} diff --git a/blinkywww/playwright.config.js b/blinkywww/playwright.config.js new file mode 100644 index 0000000..9d6992c --- /dev/null +++ b/blinkywww/playwright.config.js @@ -0,0 +1,80 @@ +// @ts-check +import { defineConfig, devices } from '@playwright/test'; + +/** + * Read environment variables from file. + * https://github.com/motdotla/dotenv + */ +// import dotenv from 'dotenv'; +// import path from 'path'; +// dotenv.config({ path: path.resolve(__dirname, '.env') }); + +/** + * @see https://playwright.dev/docs/test-configuration + */ +export default defineConfig({ + testDir: './tests', + /* Run tests in files in parallel */ + fullyParallel: true, + /* Fail the build on CI if you accidentally left test.only in the source code. */ + forbidOnly: !!process.env.CI, + /* Retry on CI only */ + retries: process.env.CI ? 2 : 0, + /* Opt out of parallel tests on CI. */ + workers: process.env.CI ? 1 : undefined, + /* Reporter to use. See https://playwright.dev/docs/test-reporters */ + reporter: 'html', + /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */ + use: { + /* Base URL to use in actions like `await page.goto('/')`. */ + baseURL: 'http://localhost:3000', + + /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ + trace: 'on-first-retry', + }, + + /* Configure projects for major browsers */ + projects: [ + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + }, + + { + name: 'firefox', + use: { ...devices['Desktop Firefox'] }, + }, + + { + name: 'webkit', + use: { ...devices['Desktop Safari'] }, + }, + + /* Test against mobile viewports. */ + { + name: 'Mobile Chrome', + use: { ...devices['Pixel 5'] }, + }, + { + name: 'Mobile Safari', + use: { ...devices['iPhone 12'] }, + }, + + /* Test against branded browsers. */ + { + name: 'Microsoft Edge', + use: { ...devices['Desktop Edge'], channel: 'msedge' }, + }, + { + name: 'Google Chrome', + use: { ...devices['Desktop Chrome'], channel: 'chrome' }, + }, + ], + + /* Run your local dev server before starting the tests */ + webServer: { + command: 'npm run start', + url: 'http://localhost:3000', + reuseExistingServer: !process.env.CI, + } +}); diff --git a/blinkywww/tests/test.spec.js b/blinkywww/tests/test.spec.js new file mode 100644 index 0000000..656cb35 --- /dev/null +++ b/blinkywww/tests/test.spec.js @@ -0,0 +1,178 @@ +const { test, expect } = require('@playwright/test'); + +test.describe('eeprom data', () => { + test('verify bitstream for happy path simple text message', async ({ page }) => { + await page.goto('/'); + + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('TEST'); + + await page.getByRole('button', { name: 'Go' }).click(); + const messageData = await page.locator('#message_data').inputValue(); + + expect(messageData).toEqual('1,30,4,29,14,28,29'); + // 1 message, 30 is the config byte for the message, 4 data bytes in message, T=29, E=14, S=28, T=29 + }); + + test('verify bitstream for simple pixel message', async ({ page }) => { + await page.goto('http://localhost:3000/'); + + await page.getByRole('radio', { name: 'Pixel' }).check(); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); + await page.locator('#msg_0_delay').selectOption('7'); + + await page.locator('#msg_0_pixel_leds > div > img').first().click(); + await page.locator('#msg_0_pixel_leds > div:nth-child(2) > img:nth-child(3)').click(); + await page.locator('#msg_0_pixel_leds > div:nth-child(3) > img:nth-child(5)').click(); + await page.locator('#msg_0_pixel_leds > div:nth-child(4) > img:nth-child(7)').click(); + await page.locator('#msg_0_pixel_leds > div:nth-child(5) > img:nth-child(9)').click(); + await page.locator('#msg_0_pixel_leds > div:nth-child(6) > img:nth-child(11)').click(); + await page.locator('#msg_0_pixel_leds > div:nth-child(7) > img:nth-child(13)').click(); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,158,7,128,64,32,16,8,4,2'); + }); + + test('verify bitstream for multiple text messages', async ({ page }) => { + await page.goto('/'); + + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('Hello'); + + await page.locator('input[value="Add New Message"]').click(); + await page.locator('#msg_1_anim_marq').check(); + await page.locator('#msg_1_end_type').selectOption('Advance to next message'); + await page.locator('#msg_1_delay').selectOption('7'); + await page.locator('#msg_1_text_message').fill('World'); + + await page.getByRole('button', { name: 'Go' }).click(); + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('2,30,5,17,14,21,21,24,30,5,32,24,27,21,13'); + }); + + test('verify bitstream for animation-style text message', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_anim').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('animation'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,94,9,10,23,18,22,10,29,18,24,23'); + }); + + test('verify bitstream for marquee-style text message', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('marquee'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,30,7,22,10,27,26,30,14,14'); + }); + + test('verify bitstream for text message that ends by stopping', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Stop'); + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('stop'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,28,4,28,29,24,25'); + }); + + test('verify bitstream for text message that ends by repeating', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Repeat this message'); + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('repeat'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,29,6,27,14,25,14,10,29'); + }); + + test('verify bitstream for text message that ends by advancing', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('advance'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,30,7,10,13,31,10,23,12,14'); + }); + + test('verify bitstream for easter egg text message', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Easter egg...'); + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('egg'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,31,3,14,16,16'); + }); + + test('verify bitstream for low delay text message', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); // advance + await page.locator('#msg_0_delay').selectOption('0'); + await page.locator('#msg_0_text_message').fill('fast'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,2,4,15,10,28,29'); + }); + + test('verify bitstream for middle delay text message', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); // advance + await page.locator('#msg_0_delay').selectOption('5'); + await page.locator('#msg_0_text_message').fill('medium'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,22,6,22,14,13,18,30,22'); + }); + + test('verify bitstream for slow delay text message', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('Advance to next message'); // advance + await page.locator('#msg_0_delay').selectOption('15'); + await page.locator('#msg_0_text_message').fill('sloooow'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,62,7,28,21,24,24,24,24,32'); + }); + + test('verify bitstream for all the advertised text characters', async ({ page }) => { + await page.goto('/'); + await page.locator('#msg_0_anim_marq').check(); + await page.locator('#msg_0_end_type').selectOption('2'); // advance + await page.locator('#msg_0_delay').selectOption('7'); + await page.locator('#msg_0_text_message').fill('0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!?&,.'); + await page.getByRole('button', { name: 'Go' }).click(); + + const messageData = await page.locator('#message_data').inputValue(); + expect(messageData).toEqual('1,30,67,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,37,39,38,40,36'); + }); +}); From 67fb137ffeba703f582bf3282f0b8b68f55d6434 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Wed, 25 Jun 2025 14:24:42 -0500 Subject: [PATCH 05/23] Add additional Playwright tests --- blinkywww/tests/test.spec.js | 38 ++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/blinkywww/tests/test.spec.js b/blinkywww/tests/test.spec.js index 656cb35..c81bde3 100644 --- a/blinkywww/tests/test.spec.js +++ b/blinkywww/tests/test.spec.js @@ -176,3 +176,41 @@ test.describe('eeprom data', () => { expect(messageData).toEqual('1,30,67,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,37,39,38,40,36'); }); }); + + test('verify initial square state is black for data and clock', async ({ page }) => { + await page.goto('/'); + + const clockBg = await page.evaluate(() => { + return window.getComputedStyle(document.getElementById('divclock')).backgroundColor; + }); + + const dataBg = await page.evaluate(() => { + return window.getComputedStyle(document.getElementById('divdata')).backgroundColor; + }); + + // Both should start as black (rgb(0, 0, 0)) + expect(clockBg).toBe('rgb(0, 0, 0)'); + expect(dataBg).toBe('rgb(0, 0, 0)'); + }); + + + test('verify stop button resets to black', async ({ page }) => { + await page.goto('/'); + + await page.locator('#msg_0_text_message').fill('TEST'); + await page.getByRole('button', { name: 'Go' }).click(); + await page.waitForTimeout(100); + await page.getByRole('button', { name: 'Stop' }).click(); + await page.waitForTimeout(50); + + const clockBg = await page.evaluate(() => { + return window.getComputedStyle(document.getElementById('divclock')).backgroundColor; + }); + + const dataBg = await page.evaluate(() => { + return window.getComputedStyle(document.getElementById('divdata')).backgroundColor; + }); + + expect(clockBg).toBe('rgb(0, 0, 0)'); + expect(dataBg).toBe('rgb(0, 0, 0)'); + }); \ No newline at end of file From 72f5655d567c446b4aedb4db5899e255ab8eceac Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Wed, 25 Jun 2025 14:25:23 -0500 Subject: [PATCH 06/23] Fix bug with ie message --- blinkywww/src/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blinkywww/src/index.html b/blinkywww/src/index.html index 9b575b3..60a3967 100644 --- a/blinkywww/src/index.html +++ b/blinkywww/src/index.html @@ -406,7 +406,7 @@ return rv; } -disp_ie_message = none; +disp_ie_message = "none"; +

Blinky POV and Blinky GRID Programming

This webpage will let you design and upload new messages to your Blinky POV or Blinky GRID kit. First, you should create one or more messages in the first section below. Once your messages are prepared, section 2 below will let you transmit the new message(s) to your Blinky kit.

+
diff --git a/blinkywww/src/style.css b/blinkywww/src/style.css index b7f61e0..b59734b 100644 --- a/blinkywww/src/style.css +++ b/blinkywww/src/style.css @@ -1,6 +1,42 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + * { - box-sizing: border-box; + margin: 0; + font-family: system-ui, sans-serif; +} + +body { + line-height: 1.5; + min-height: 100vh; } + +main { + max-width: 60ch; + margin: auto; /* center */ + padding: 0 2em; +} + +p { + text-wrap: pretty; + margin-bottom: 1em; +} + +h1, h2, h3, h4, h5, h6 { + text-wrap: balance; + margin-bottom: 0.5em; +} + +/* Inherit fonts for inputs and buttons */ +input, button, +textarea, select { + font-family: inherit; + font-size: inherit; +} + [id$="_pixel_leds"] { display: grid; grid-auto-flow: column; From 7729063bf8d84a397065eea18e772f7de511fd5d Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Wed, 25 Jun 2025 15:34:37 -0500 Subject: [PATCH 12/23] Extract style from HTML --- blinkywww/src/index.html | 2 +- blinkywww/src/style.css | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/blinkywww/src/index.html b/blinkywww/src/index.html index 1b1bdc9..498e2f6 100644 --- a/blinkywww/src/index.html +++ b/blinkywww/src/index.html @@ -466,7 +466,7 @@

Blinky POV and Blinky GRID Programming

1. Create Messages

You can store multiple messages on your Blinky kit. Messages can be either text-based or pixel-based. Font-based messages take up much less memory than pixel-based messages. Marquee/Animation only matters on Blinky GRID. For delay, 0 is the fastest value and 15 is the slowest value. Drag the arrow icons to reorder your messages. Use the garbage can to remove a message.

-
+
diff --git a/blinkywww/src/style.css b/blinkywww/src/style.css index b59734b..2fd51a9 100644 --- a/blinkywww/src/style.css +++ b/blinkywww/src/style.css @@ -55,4 +55,10 @@ textarea, select { width: 16px; height: 16px; display: block; +} + +#list { + position: relative; + border: 1px solid white; + width: 575px; } \ No newline at end of file From 6c57f99db54df2c3654e061fbc597d2620e0c3ad Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Wed, 25 Jun 2025 15:47:22 -0500 Subject: [PATCH 13/23] Update progress bar --- blinkywww/src/index.html | 22 +++------ blinkywww/src/progress_bar_30px_tall.png | Bin 182 -> 0 bytes blinkywww/src/style.css | 54 +++++++++++++++++++++++ 3 files changed, 61 insertions(+), 15 deletions(-) delete mode 100644 blinkywww/src/progress_bar_30px_tall.png diff --git a/blinkywww/src/index.html b/blinkywww/src/index.html index 498e2f6..5e80205 100644 --- a/blinkywww/src/index.html +++ b/blinkywww/src/index.html @@ -289,7 +289,7 @@ function stop_dump() { transmitting = 0; - document.getElementById("progressImg").width = 0; + document.getElementById("progressBar").value = 0; document.getElementById("progressSpan").innerHTML = "Canceled"; } @@ -384,21 +384,11 @@ set_color_data("white"); index++; - // record progress - not added because it's too difficult - /* - var byte_ix = index / 8; - if (xmit_raw.length - index <= (8 * 21)) - var progress = 0; // at the end of transmission - else - var progress = (byte_ix % 21) / 21; // in the middle of transmission - document.getElementById("recordProgressImg").width = progress_max_width * progress; - document.getElementById("recordProgressSpan").innerHTML = Math.round(100 * progress) + "%"; - */ // overall progress var progress = index / xmit_raw.length; var seconds_remaining = Math.round(((xmit_raw.length - index) * (delay * 2)) / 1000.0); - document.getElementById("progressImg").width = progress_max_width * progress; + document.getElementById("progressBar").value = progress * 100; document.getElementById("progressSpan").innerHTML = Math.round(100 * progress) + "% (" + seconds_remaining + "s left)"; setTimeout('toggle_clock()', delay); @@ -484,9 +474,11 @@

2. Programming

Please note! On 2011/09/10 we switched the locations of the clock and data squares below to make it easier to hold the Blinky device while programming, based on customer feed back we have received over the past few months. This requires no change in the Blinky firmware, only a change in how you align the sensors on the screen. Please let us know if this change is a dealbreaker.

-
- -
Progress:
+
+
+ + +
Progress: Ready

diff --git a/blinkywww/src/progress_bar_30px_tall.png b/blinkywww/src/progress_bar_30px_tall.png deleted file mode 100644 index 9489bab7d1ff4f40a0a8d5a7f3c2bb87cd71f65a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 182 zcmeAS@N?(olHy`uVBq!ia0vp^j6f{M!2~3ezCWG-5-1LGcVbv~PUa<$!O>_%)r1c48n{Iv*t(u1=&kHeO=jaadY!?Dld;*_a7)ES>hT|;+&tG zo0?a`;9QiNSdyBeP@Y+mq2TW68xY>eCk|93=;`7Z!f`!0AtNCnDPe&%8-u3+!|pFL R6S;ws44$rjF6*2UngFA@FR%ar diff --git a/blinkywww/src/style.css b/blinkywww/src/style.css index 2fd51a9..61c1dca 100644 --- a/blinkywww/src/style.css +++ b/blinkywww/src/style.css @@ -9,6 +9,18 @@ font-family: system-ui, sans-serif; } +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border: 0; +} + body { line-height: 1.5; min-height: 100vh; @@ -61,4 +73,46 @@ textarea, select { position: relative; border: 1px solid white; width: 575px; +} + +/* Native progress element styling */ +.progress-wrapper { + width: 301px; + margin-bottom: 10px; +} + +progress { + width: 100%; + height: 30px; +} + +/* WebKit browsers (Chrome, Safari) */ +progress::-webkit-progress-bar { + background-color: #f0f0f0; + border: 2px solid white; + border-radius: 2px; +} + +progress::-webkit-progress-value { + background-color: #4CAF50; + border-radius: 0; + transition: width 0.3s ease; + appearance: none; + -webkit-appearance: none; +} + +/* Firefox */ +progress::-moz-progress-bar { + background-color: #4CAF50; + border-radius: 0; + transition: width 0.3s ease; +} + +progress { + color: #4CAF50; +} + +.progress-status { + padding: 5px 0; + font-weight: bold; } \ No newline at end of file From cf03072d340b06270d268b4ffb3f25039806038f Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Wed, 25 Jun 2025 15:49:52 -0500 Subject: [PATCH 14/23] Update TODO items --- blinkywww/README | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/blinkywww/README b/blinkywww/README index eaba1f8..187db50 100644 --- a/blinkywww/README +++ b/blinkywww/README @@ -15,3 +15,18 @@ You can serve the site locally with `npm run start` which starts a local web ser We use Playwright. You can run `npm run test` but you can also use `npx playwright test` to run the tests directly. +# TODO (before merging this in) + +* Flexbox for the layout +* Remove all floats +* Test on small screens +* Try requestAnimationFrame instead of setTimeout +* Adjust text, visual design +* Maybe pull the images for LED and use background color on round divs instead? +* Add more pixel mode tests + * multiple messages + * mixed pixel and text messages + * all the pixel options + * delete some lines, add some lines + * make sure the clicked pixels are "on" and the others are off +* Test on a real Blinky From fa57bc338b0b7111dc5d2167e7687a9b404d9dec Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Thu, 26 Jun 2025 07:45:04 -0500 Subject: [PATCH 15/23] Whitespace --- blinkywww/src/index.html | 991 ++++++++++++++++++++------------------- blinkywww/src/style.css | 18 +- 2 files changed, 511 insertions(+), 498 deletions(-) diff --git a/blinkywww/src/index.html b/blinkywww/src/index.html index 5e80205..f7458cb 100644 --- a/blinkywww/src/index.html +++ b/blinkywww/src/index.html @@ -1,518 +1,531 @@ -Blinky POV and Blinky GRID Programming - - - - - + - + { + var rv = -1; // Return value assumes failure. + if (navigator.appName == 'Microsoft Internet Explorer') { + var ua = navigator.userAgent; + var re = new RegExp("MSIE ([0-9]{1,}[\.0-9]{0,})"); + if (re.exec(ua) != null) + rv = parseFloat(RegExp.$1); + } + return rv; + } + + disp_ie_message = "none"; + +
-

Blinky POV and Blinky GRID Programming

-

This webpage will let you design and upload new messages to your Blinky POV or Blinky GRID kit. First, you should create one or more messages in the first section below. Once your messages are prepared, section 2 below will let you transmit the new message(s) to your Blinky kit.

- -

1. Create Messages

-

You can store multiple messages on your Blinky kit. Messages can be either text-based or pixel-based. Font-based messages take up much less memory than pixel-based messages. Marquee/Animation only matters on Blinky GRID. For delay, 0 is the fastest value and 15 is the slowest value. Drag the arrow icons to reorder your messages. Use the garbage can to remove a message.

-
-
- - - -

2. Programming

-

Enter programming mode: Hold down the button while turning on the power switch. Release the button. Hold the Blinky up to the screen. Be sure to align the sensors correctly: The sensor labeled C points to clock, and D points to data. Press and release the button when you're ready to begin transmission. Press the Go button to start.

-


Check out this awesome animated guide to programming!

-

Please note! On 2011/09/10 we switched the locations of the clock and data squares below to make it easier to hold the Blinky device while programming, based on customer feed back we have received over the past few months. This requires no change in the Blinky firmware, only a change in how you align the sensors on the screen. Please let us know if this change is a dealbreaker.

-
-
-
-
- - -
Progress: Ready
+

Blinky POV and Blinky GRID Programming

+

This webpage will let you design and upload new messages to your Blinky POV or Blinky GRID kit. First, you should create one or + more messages in the first section below. Once your messages are prepared, section 2 below will let you transmit + the new message(s) to your Blinky kit.

+ +

1. Create Messages

+

You can store multiple messages on your Blinky kit. Messages can be either text-based or pixel-based. Font-based + messages take up much less memory than pixel-based messages. Marquee/Animation only matters on Blinky GRID. For + delay, 0 is the fastest value and 15 is the slowest value. Drag the arrow icons to reorder your messages. Use + the garbage can to remove a message.

+
+
+ + + +

2. Programming

+

Enter programming mode: Hold down the button while turning on the power switch. Release the button. Hold the + Blinky up to the screen. Be sure to align the sensors correctly: The sensor labeled C points to clock, and D + points to data. Press and release the button when you're ready to begin transmission. Press the Go button to + start.

+


Check out this awesome + animated guide to programming!

+

Please note! On 2011/09/10 we switched the locations of the clock and data squares below to make + it easier to hold the Blinky device while programming, based on customer feed back we have received over the + past few months. This requires no change in the Blinky firmware, only a change in how you align the sensors on + the screen. Please let us know if this change is a dealbreaker.

+
+
+
+
+ + +
Progress: Ready
+
+
+
+ + + + + +
+ + Delay (ms):
+
+
+
Data
+
Clock
-
- - -
- Delay (ms):
-
-
Data
-
Clock
+

3. Troubleshooting

+

For the delay value, smaller values equal quicker data transmissions, but too small/fast might cause errors! The + smallest reliable value depends on your monitor, browser, and computer graphics hardware. Start with a value of + 40, and make sure it can reliably transfer your messages. You can then gradually reduce the delay time until you + start having invalid transmissions, indicated by alternating flashing of the third and fourth LEDs on your + Blinky board. We've have good luck with values of 25-40 on various systems we've tested.

+

During normal transmission, the second LED will flash once for each byte of data received. When the transmission + is finished, the blinky should immediately start displaying your messages. The blinky data is transmitted as a + series of one or more records. After each record is transmitted, the blinky will double-check the data + to make sure it was properly transmitted (using something called a checksum). If the checksum is invalid, meaning that + there was a data transmission problem, the third and fourth LEDs on the blinky will flash rapidly in an + alternating pattern. If this happens, press the Stop button, reset your blinky into the programming (bootloader) + mode, and try again. If you continue to experience trouble, try increasing the value in the delay box and set + your monitor's brightness to 100%. If nothing works, try the forums.

+ +
+
+ Debug information:
+ message_data:
+
+ message_data hex:
+
+
+ xmit_data:
+
+ xmit_data hex:
+
+
+ +
+ xmit_raw:
+
-
-

3. Troubleshooting

-

For the delay value, smaller values equal quicker data transmissions, but too small/fast might cause errors! The smallest reliable value depends on your monitor, browser, and computer graphics hardware. Start with a value of 40, and make sure it can reliably transfer your messages. You can then gradually reduce the delay time until you start having invalid transmissions, indicated by alternating flashing of the third and fourth LEDs on your Blinky board. We've have good luck with values of 25-40 on various systems we've tested.

-

During normal transmission, the second LED will flash once for each byte of data received. When the transmission is finished, the blinky should immediately start displaying your messages. The blinky data is transmitted as a series of one or more records. After each record is transmitted, the blinky will double-check the data to make sure it was properly transmitted (using something called a checksum). If the checksum is invalid, meaning that there was a data transmission problem, the third and fourth LEDs on the blinky will flash rapidly in an alternating pattern. If this happens, press the Stop button, reset your blinky into the programming (bootloader) mode, and try again. If you continue to experience trouble, try increasing the value in the delay box and set your monitor's brightness to 100%. If nothing works, try the forums.

- -
-
-Debug information:
-message_data:
-
-message_data hex:
-
-
-xmit_data:
-
-xmit_data hex:
-
-
- -
-xmit_raw:
- -
diff --git a/blinkywww/src/style.css b/blinkywww/src/style.css index 61c1dca..7153f63 100644 --- a/blinkywww/src/style.css +++ b/blinkywww/src/style.css @@ -1,12 +1,12 @@ *, *::before, *::after { - box-sizing: border-box; + box-sizing: border-box; } * { - margin: 0; - font-family: system-ui, sans-serif; + margin: 0; + font-family: system-ui, sans-serif; } .visually-hidden { @@ -33,20 +33,20 @@ main { } p { - text-wrap: pretty; - margin-bottom: 1em; + text-wrap: pretty; + margin-bottom: 1em; } h1, h2, h3, h4, h5, h6 { - text-wrap: balance; - margin-bottom: 0.5em; + text-wrap: balance; + margin-bottom: 0.5em; } /* Inherit fonts for inputs and buttons */ input, button, textarea, select { - font-family: inherit; - font-size: inherit; + font-family: inherit; + font-size: inherit; } [id$="_pixel_leds"] { From 05bfd09bbf1c9bf4bf15979d97354dd43aa233f7 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Thu, 26 Jun 2025 07:54:28 -0500 Subject: [PATCH 16/23] Extract styles into CSS; inputs to buttons --- blinkywww/src/index.html | 13 ++++++------- blinkywww/src/style.css | 7 +++++++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/blinkywww/src/index.html b/blinkywww/src/index.html index f7458cb..e15ff91 100644 --- a/blinkywww/src/index.html +++ b/blinkywww/src/index.html @@ -466,9 +466,8 @@

2. Programming

it easier to hold the Blinky device while programming, based on customer feed back we have received over the past few months. This requires no change in the Blinky firmware, only a change in how you align the sensors on the screen. Please let us know if this change is a dealbreaker.

-
-
-
+
+
@@ -477,11 +476,11 @@

2. Programming


- + diff --git a/blinkywww/src/style.css b/blinkywww/src/style.css index 7153f63..9b4229e 100644 --- a/blinkywww/src/style.css +++ b/blinkywww/src/style.css @@ -115,4 +115,11 @@ progress { .progress-status { padding: 5px 0; font-weight: bold; +} + +.programming-panel button { + height: 50px; + width: 60px; + font-size: 100%; + font-weight: bold; } \ No newline at end of file From 5c825e5fbec27b57a628ef3ba445221f080e67d7 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Thu, 26 Jun 2025 09:27:15 -0500 Subject: [PATCH 17/23] Whitespace --- blinkywww/src/index.html | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/blinkywww/src/index.html b/blinkywww/src/index.html index e15ff91..4ff530c 100644 --- a/blinkywww/src/index.html +++ b/blinkywww/src/index.html @@ -459,14 +459,17 @@

2. Programming

Blinky up to the screen. Be sure to align the sensors correctly: The sensor labeled C points to clock, and D points to data. Press and release the button when you're ready to begin transmission. Press the Go button to start.

-


Check out this awesome - animated guide to programming!

-

Please note! On 2011/09/10 we switched the locations of the clock and data squares below to make - it easier to hold the Blinky device while programming, based on customer feed back we have received over the - past few months. This requires no change in the Blinky firmware, only a change in how you align the sensors on - the screen. Please let us know if this change is a dealbreaker.

-
+

+ + +
+ Check out this awesome animated guide to programming! +
+

+
From f9160be1bb7608a1e49e10a3a5809493779d26b4 Mon Sep 17 00:00:00 2001 From: Adam Wolf Date: Thu, 26 Jun 2025 09:27:27 -0500 Subject: [PATCH 18/23] Add mobile viewport width to head --- blinkywww/src/index.html | 1 + 1 file changed, 1 insertion(+) diff --git a/blinkywww/src/index.html b/blinkywww/src/index.html index 4ff530c..1cd025e 100644 --- a/blinkywww/src/index.html +++ b/blinkywww/src/index.html @@ -4,6 +4,7 @@ Blinky POV and Blinky GRID Programming +
- + Delay (ms):