blonk is a radar for your web, where you follow vibes for cool blips on the radar

Add in a basic webpage

It runs on localhost:3000

We have a classic del.icio.us look and have added some tagging features.

There is a /submit page to submit a blip

You can also filter by tag now.

Claude has added some extra fluff, let's see what needs redlined.

+41
CLAUDE_NOTES.md
··· 2 2 3 3 ## Session 1: Initial Setup & Renaming Posts to Blips 4 4 5 + ## Session 2: Web Interface with del.icio.us Aesthetic 6 + 7 + ### Why This Step 8 + - User wanted a web interface inspired by del.icio.us 9 + - del.icio.us was perfect inspiration: minimalist, content-focused, tag-based 10 + - Fits the "vibe radar" concept with simple signal transmission 11 + 12 + ### Implementation Details 13 + - Express server with EJS templating 14 + - Minimalist CSS mimicking del.icio.us style: 15 + - Signature blue (#3366cc) accent color 16 + - Verdana 11px font for that classic 2000s web feel 17 + - Clean list-based layout 18 + - Tag system for categorization 19 + - Routes: 20 + - `/` - Recent blips list 21 + - `/submit` - Transmit new blips 22 + - `/tag/:tag` - Filter by tag 23 + - Added tags to BlonkBlip schema 24 + - "Transmit" instead of "Submit" for radar theme 25 + 26 + ### Thoughts So Far 27 + **Going Well:** 28 + - The del.icio.us aesthetic works perfectly with the radar concept 29 + - Tag system adds discoverability without complexity 30 + - Clean separation between AT Protocol layer and web layer 31 + 32 + **Potential Pitfalls:** 33 + 1. **Multi-user**: Currently only shows blips from the configured account. Need to aggregate from multiple users. 34 + 2. **Real-time updates**: No websockets yet, requires page refresh 35 + 3. **Fluff interactions**: Can display fluff count but can't vote yet 36 + 4. **Performance**: Loading all blips then filtering in memory won't scale 37 + 38 + **Ideas for Next Steps:** 39 + - Add fluff (upvote) functionality with AJAX 40 + - User profiles showing their blip history 41 + - Popular/trending radar view based on fluff velocity 42 + - Tag clouds showing popular topics 43 + - RSS feeds for tags 44 + - Bookmarklet for quick blip submission (very del.icio.us!) 45 + 5 46 ### Renaming Complete ✅ 6 47 Successfully renamed all terminology: 7 48 - Posts → Blips
+1159
package-lock.json
··· 11 11 "dependencies": { 12 12 "@atproto/api": "^0.15.16", 13 13 "@atproto/lexicon": "^0.4.11", 14 + "@types/express": "^5.0.3", 15 + "@types/express-session": "^1.18.2", 14 16 "@types/node": "^24.0.3", 15 17 "dotenv": "^16.5.0", 18 + "ejs": "^3.1.10", 19 + "express": "^5.1.0", 20 + "express-ejs-layouts": "^2.5.1", 21 + "express-session": "^1.18.1", 16 22 "tsx": "^4.20.3", 17 23 "typescript": "^5.8.3" 18 24 } ··· 474 480 "node": ">=18" 475 481 } 476 482 }, 483 + "node_modules/@types/body-parser": { 484 + "version": "1.19.6", 485 + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", 486 + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", 487 + "license": "MIT", 488 + "dependencies": { 489 + "@types/connect": "*", 490 + "@types/node": "*" 491 + } 492 + }, 493 + "node_modules/@types/connect": { 494 + "version": "3.4.38", 495 + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", 496 + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", 497 + "license": "MIT", 498 + "dependencies": { 499 + "@types/node": "*" 500 + } 501 + }, 502 + "node_modules/@types/express": { 503 + "version": "5.0.3", 504 + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.3.tgz", 505 + "integrity": "sha512-wGA0NX93b19/dZC1J18tKWVIYWyyF2ZjT9vin/NRu0qzzvfVzWjs04iq2rQ3H65vCTQYlRqs3YHfY7zjdV+9Kw==", 506 + "license": "MIT", 507 + "dependencies": { 508 + "@types/body-parser": "*", 509 + "@types/express-serve-static-core": "^5.0.0", 510 + "@types/serve-static": "*" 511 + } 512 + }, 513 + "node_modules/@types/express-serve-static-core": { 514 + "version": "5.0.6", 515 + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.0.6.tgz", 516 + "integrity": "sha512-3xhRnjJPkULekpSzgtoNYYcTWgEZkp4myc+Saevii5JPnHNvHMRlBSHDbs7Bh1iPPoVTERHEZXyhyLbMEsExsA==", 517 + "license": "MIT", 518 + "dependencies": { 519 + "@types/node": "*", 520 + "@types/qs": "*", 521 + "@types/range-parser": "*", 522 + "@types/send": "*" 523 + } 524 + }, 525 + "node_modules/@types/express-session": { 526 + "version": "1.18.2", 527 + "resolved": "https://registry.npmjs.org/@types/express-session/-/express-session-1.18.2.tgz", 528 + "integrity": "sha512-k+I0BxwVXsnEU2hV77cCobC08kIsn4y44C3gC0b46uxZVMaXA04lSPgRLR/bSL2w0t0ShJiG8o4jPzRG/nscFg==", 529 + "license": "MIT", 530 + "dependencies": { 531 + "@types/express": "*" 532 + } 533 + }, 534 + "node_modules/@types/http-errors": { 535 + "version": "2.0.5", 536 + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", 537 + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", 538 + "license": "MIT" 539 + }, 540 + "node_modules/@types/mime": { 541 + "version": "1.3.5", 542 + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", 543 + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", 544 + "license": "MIT" 545 + }, 477 546 "node_modules/@types/node": { 478 547 "version": "24.0.3", 479 548 "resolved": "https://registry.npmjs.org/@types/node/-/node-24.0.3.tgz", ··· 483 552 "undici-types": "~7.8.0" 484 553 } 485 554 }, 555 + "node_modules/@types/qs": { 556 + "version": "6.14.0", 557 + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", 558 + "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", 559 + "license": "MIT" 560 + }, 561 + "node_modules/@types/range-parser": { 562 + "version": "1.2.7", 563 + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", 564 + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", 565 + "license": "MIT" 566 + }, 567 + "node_modules/@types/send": { 568 + "version": "0.17.5", 569 + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.5.tgz", 570 + "integrity": "sha512-z6F2D3cOStZvuk2SaP6YrwkNO65iTZcwA2ZkSABegdkAh/lf+Aa/YQndZVfmEXT5vgAp6zv06VQ3ejSVjAny4w==", 571 + "license": "MIT", 572 + "dependencies": { 573 + "@types/mime": "^1", 574 + "@types/node": "*" 575 + } 576 + }, 577 + "node_modules/@types/serve-static": { 578 + "version": "1.15.8", 579 + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.8.tgz", 580 + "integrity": "sha512-roei0UY3LhpOJvjbIP6ZZFngyLKl5dskOtDhxY5THRSpO+ZI+nzJ+m5yUMzGrp89YRa7lvknKkMYjqQFGwA7Sg==", 581 + "license": "MIT", 582 + "dependencies": { 583 + "@types/http-errors": "*", 584 + "@types/node": "*", 585 + "@types/send": "*" 586 + } 587 + }, 588 + "node_modules/accepts": { 589 + "version": "2.0.0", 590 + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", 591 + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", 592 + "license": "MIT", 593 + "dependencies": { 594 + "mime-types": "^3.0.0", 595 + "negotiator": "^1.0.0" 596 + }, 597 + "engines": { 598 + "node": ">= 0.6" 599 + } 600 + }, 601 + "node_modules/ansi-styles": { 602 + "version": "4.3.0", 603 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 604 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 605 + "license": "MIT", 606 + "dependencies": { 607 + "color-convert": "^2.0.1" 608 + }, 609 + "engines": { 610 + "node": ">=8" 611 + }, 612 + "funding": { 613 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 614 + } 615 + }, 616 + "node_modules/async": { 617 + "version": "3.2.6", 618 + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", 619 + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", 620 + "license": "MIT" 621 + }, 486 622 "node_modules/await-lock": { 487 623 "version": "2.2.2", 488 624 "resolved": "https://registry.npmjs.org/await-lock/-/await-lock-2.2.2.tgz", 489 625 "integrity": "sha512-aDczADvlvTGajTDjcjpJMqRkOF6Qdz3YbPZm/PyW6tKPkx2hlYBzxMhEywM/tU72HrVZjgl5VCdRuMlA7pZ8Gw==", 490 626 "license": "MIT" 491 627 }, 628 + "node_modules/balanced-match": { 629 + "version": "1.0.2", 630 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 631 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 632 + "license": "MIT" 633 + }, 634 + "node_modules/body-parser": { 635 + "version": "2.2.0", 636 + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", 637 + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", 638 + "license": "MIT", 639 + "dependencies": { 640 + "bytes": "^3.1.2", 641 + "content-type": "^1.0.5", 642 + "debug": "^4.4.0", 643 + "http-errors": "^2.0.0", 644 + "iconv-lite": "^0.6.3", 645 + "on-finished": "^2.4.1", 646 + "qs": "^6.14.0", 647 + "raw-body": "^3.0.0", 648 + "type-is": "^2.0.0" 649 + }, 650 + "engines": { 651 + "node": ">=18" 652 + } 653 + }, 654 + "node_modules/brace-expansion": { 655 + "version": "1.1.12", 656 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", 657 + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", 658 + "license": "MIT", 659 + "dependencies": { 660 + "balanced-match": "^1.0.0", 661 + "concat-map": "0.0.1" 662 + } 663 + }, 664 + "node_modules/bytes": { 665 + "version": "3.1.2", 666 + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", 667 + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", 668 + "license": "MIT", 669 + "engines": { 670 + "node": ">= 0.8" 671 + } 672 + }, 673 + "node_modules/call-bind-apply-helpers": { 674 + "version": "1.0.2", 675 + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 676 + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 677 + "license": "MIT", 678 + "dependencies": { 679 + "es-errors": "^1.3.0", 680 + "function-bind": "^1.1.2" 681 + }, 682 + "engines": { 683 + "node": ">= 0.4" 684 + } 685 + }, 686 + "node_modules/call-bound": { 687 + "version": "1.0.4", 688 + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 689 + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 690 + "license": "MIT", 691 + "dependencies": { 692 + "call-bind-apply-helpers": "^1.0.2", 693 + "get-intrinsic": "^1.3.0" 694 + }, 695 + "engines": { 696 + "node": ">= 0.4" 697 + }, 698 + "funding": { 699 + "url": "https://github.com/sponsors/ljharb" 700 + } 701 + }, 702 + "node_modules/chalk": { 703 + "version": "4.1.2", 704 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 705 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 706 + "license": "MIT", 707 + "dependencies": { 708 + "ansi-styles": "^4.1.0", 709 + "supports-color": "^7.1.0" 710 + }, 711 + "engines": { 712 + "node": ">=10" 713 + }, 714 + "funding": { 715 + "url": "https://github.com/chalk/chalk?sponsor=1" 716 + } 717 + }, 718 + "node_modules/color-convert": { 719 + "version": "2.0.1", 720 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 721 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 722 + "license": "MIT", 723 + "dependencies": { 724 + "color-name": "~1.1.4" 725 + }, 726 + "engines": { 727 + "node": ">=7.0.0" 728 + } 729 + }, 730 + "node_modules/color-name": { 731 + "version": "1.1.4", 732 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 733 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 734 + "license": "MIT" 735 + }, 736 + "node_modules/concat-map": { 737 + "version": "0.0.1", 738 + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 739 + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", 740 + "license": "MIT" 741 + }, 742 + "node_modules/content-disposition": { 743 + "version": "1.0.0", 744 + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", 745 + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", 746 + "license": "MIT", 747 + "dependencies": { 748 + "safe-buffer": "5.2.1" 749 + }, 750 + "engines": { 751 + "node": ">= 0.6" 752 + } 753 + }, 754 + "node_modules/content-type": { 755 + "version": "1.0.5", 756 + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", 757 + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", 758 + "license": "MIT", 759 + "engines": { 760 + "node": ">= 0.6" 761 + } 762 + }, 763 + "node_modules/cookie": { 764 + "version": "0.7.2", 765 + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", 766 + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", 767 + "license": "MIT", 768 + "engines": { 769 + "node": ">= 0.6" 770 + } 771 + }, 772 + "node_modules/cookie-signature": { 773 + "version": "1.2.2", 774 + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", 775 + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", 776 + "license": "MIT", 777 + "engines": { 778 + "node": ">=6.6.0" 779 + } 780 + }, 781 + "node_modules/debug": { 782 + "version": "4.4.1", 783 + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", 784 + "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", 785 + "license": "MIT", 786 + "dependencies": { 787 + "ms": "^2.1.3" 788 + }, 789 + "engines": { 790 + "node": ">=6.0" 791 + }, 792 + "peerDependenciesMeta": { 793 + "supports-color": { 794 + "optional": true 795 + } 796 + } 797 + }, 798 + "node_modules/depd": { 799 + "version": "2.0.0", 800 + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", 801 + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", 802 + "license": "MIT", 803 + "engines": { 804 + "node": ">= 0.8" 805 + } 806 + }, 492 807 "node_modules/dotenv": { 493 808 "version": "16.5.0", 494 809 "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", ··· 501 816 "url": "https://dotenvx.com" 502 817 } 503 818 }, 819 + "node_modules/dunder-proto": { 820 + "version": "1.0.1", 821 + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 822 + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 823 + "license": "MIT", 824 + "dependencies": { 825 + "call-bind-apply-helpers": "^1.0.1", 826 + "es-errors": "^1.3.0", 827 + "gopd": "^1.2.0" 828 + }, 829 + "engines": { 830 + "node": ">= 0.4" 831 + } 832 + }, 833 + "node_modules/ee-first": { 834 + "version": "1.1.1", 835 + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 836 + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 837 + "license": "MIT" 838 + }, 839 + "node_modules/ejs": { 840 + "version": "3.1.10", 841 + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", 842 + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", 843 + "license": "Apache-2.0", 844 + "dependencies": { 845 + "jake": "^10.8.5" 846 + }, 847 + "bin": { 848 + "ejs": "bin/cli.js" 849 + }, 850 + "engines": { 851 + "node": ">=0.10.0" 852 + } 853 + }, 854 + "node_modules/encodeurl": { 855 + "version": "2.0.0", 856 + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", 857 + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", 858 + "license": "MIT", 859 + "engines": { 860 + "node": ">= 0.8" 861 + } 862 + }, 863 + "node_modules/es-define-property": { 864 + "version": "1.0.1", 865 + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 866 + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 867 + "license": "MIT", 868 + "engines": { 869 + "node": ">= 0.4" 870 + } 871 + }, 872 + "node_modules/es-errors": { 873 + "version": "1.3.0", 874 + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 875 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 876 + "license": "MIT", 877 + "engines": { 878 + "node": ">= 0.4" 879 + } 880 + }, 881 + "node_modules/es-object-atoms": { 882 + "version": "1.1.1", 883 + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 884 + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 885 + "license": "MIT", 886 + "dependencies": { 887 + "es-errors": "^1.3.0" 888 + }, 889 + "engines": { 890 + "node": ">= 0.4" 891 + } 892 + }, 504 893 "node_modules/esbuild": { 505 894 "version": "0.25.5", 506 895 "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", ··· 541 930 "@esbuild/win32-x64": "0.25.5" 542 931 } 543 932 }, 933 + "node_modules/escape-html": { 934 + "version": "1.0.3", 935 + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 936 + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", 937 + "license": "MIT" 938 + }, 939 + "node_modules/etag": { 940 + "version": "1.8.1", 941 + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 942 + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", 943 + "license": "MIT", 944 + "engines": { 945 + "node": ">= 0.6" 946 + } 947 + }, 948 + "node_modules/express": { 949 + "version": "5.1.0", 950 + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", 951 + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", 952 + "license": "MIT", 953 + "dependencies": { 954 + "accepts": "^2.0.0", 955 + "body-parser": "^2.2.0", 956 + "content-disposition": "^1.0.0", 957 + "content-type": "^1.0.5", 958 + "cookie": "^0.7.1", 959 + "cookie-signature": "^1.2.1", 960 + "debug": "^4.4.0", 961 + "encodeurl": "^2.0.0", 962 + "escape-html": "^1.0.3", 963 + "etag": "^1.8.1", 964 + "finalhandler": "^2.1.0", 965 + "fresh": "^2.0.0", 966 + "http-errors": "^2.0.0", 967 + "merge-descriptors": "^2.0.0", 968 + "mime-types": "^3.0.0", 969 + "on-finished": "^2.4.1", 970 + "once": "^1.4.0", 971 + "parseurl": "^1.3.3", 972 + "proxy-addr": "^2.0.7", 973 + "qs": "^6.14.0", 974 + "range-parser": "^1.2.1", 975 + "router": "^2.2.0", 976 + "send": "^1.1.0", 977 + "serve-static": "^2.2.0", 978 + "statuses": "^2.0.1", 979 + "type-is": "^2.0.1", 980 + "vary": "^1.1.2" 981 + }, 982 + "engines": { 983 + "node": ">= 18" 984 + }, 985 + "funding": { 986 + "type": "opencollective", 987 + "url": "https://opencollective.com/express" 988 + } 989 + }, 990 + "node_modules/express-ejs-layouts": { 991 + "version": "2.5.1", 992 + "resolved": "https://registry.npmjs.org/express-ejs-layouts/-/express-ejs-layouts-2.5.1.tgz", 993 + "integrity": "sha512-IXROv9n3xKga7FowT06n1Qn927JR8ZWDn5Dc9CJQoiiaaDqbhW5PDmWShzbpAa2wjWT1vJqaIM1S6vJwwX11gA==" 994 + }, 995 + "node_modules/express-session": { 996 + "version": "1.18.1", 997 + "resolved": "https://registry.npmjs.org/express-session/-/express-session-1.18.1.tgz", 998 + "integrity": "sha512-a5mtTqEaZvBCL9A9aqkrtfz+3SMDhOVUnjafjo+s7A9Txkq+SVX2DLvSp1Zrv4uCXa3lMSK3viWnh9Gg07PBUA==", 999 + "license": "MIT", 1000 + "dependencies": { 1001 + "cookie": "0.7.2", 1002 + "cookie-signature": "1.0.7", 1003 + "debug": "2.6.9", 1004 + "depd": "~2.0.0", 1005 + "on-headers": "~1.0.2", 1006 + "parseurl": "~1.3.3", 1007 + "safe-buffer": "5.2.1", 1008 + "uid-safe": "~2.1.5" 1009 + }, 1010 + "engines": { 1011 + "node": ">= 0.8.0" 1012 + } 1013 + }, 1014 + "node_modules/express-session/node_modules/cookie-signature": { 1015 + "version": "1.0.7", 1016 + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", 1017 + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", 1018 + "license": "MIT" 1019 + }, 1020 + "node_modules/express-session/node_modules/debug": { 1021 + "version": "2.6.9", 1022 + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 1023 + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 1024 + "license": "MIT", 1025 + "dependencies": { 1026 + "ms": "2.0.0" 1027 + } 1028 + }, 1029 + "node_modules/express-session/node_modules/ms": { 1030 + "version": "2.0.0", 1031 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 1032 + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", 1033 + "license": "MIT" 1034 + }, 1035 + "node_modules/filelist": { 1036 + "version": "1.0.4", 1037 + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", 1038 + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", 1039 + "license": "Apache-2.0", 1040 + "dependencies": { 1041 + "minimatch": "^5.0.1" 1042 + } 1043 + }, 1044 + "node_modules/filelist/node_modules/brace-expansion": { 1045 + "version": "2.0.2", 1046 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 1047 + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 1048 + "license": "MIT", 1049 + "dependencies": { 1050 + "balanced-match": "^1.0.0" 1051 + } 1052 + }, 1053 + "node_modules/filelist/node_modules/minimatch": { 1054 + "version": "5.1.6", 1055 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", 1056 + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", 1057 + "license": "ISC", 1058 + "dependencies": { 1059 + "brace-expansion": "^2.0.1" 1060 + }, 1061 + "engines": { 1062 + "node": ">=10" 1063 + } 1064 + }, 1065 + "node_modules/finalhandler": { 1066 + "version": "2.1.0", 1067 + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", 1068 + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", 1069 + "license": "MIT", 1070 + "dependencies": { 1071 + "debug": "^4.4.0", 1072 + "encodeurl": "^2.0.0", 1073 + "escape-html": "^1.0.3", 1074 + "on-finished": "^2.4.1", 1075 + "parseurl": "^1.3.3", 1076 + "statuses": "^2.0.1" 1077 + }, 1078 + "engines": { 1079 + "node": ">= 0.8" 1080 + } 1081 + }, 1082 + "node_modules/forwarded": { 1083 + "version": "0.2.0", 1084 + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", 1085 + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", 1086 + "license": "MIT", 1087 + "engines": { 1088 + "node": ">= 0.6" 1089 + } 1090 + }, 1091 + "node_modules/fresh": { 1092 + "version": "2.0.0", 1093 + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", 1094 + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", 1095 + "license": "MIT", 1096 + "engines": { 1097 + "node": ">= 0.8" 1098 + } 1099 + }, 544 1100 "node_modules/fsevents": { 545 1101 "version": "2.3.3", 546 1102 "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", ··· 555 1111 "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 556 1112 } 557 1113 }, 1114 + "node_modules/function-bind": { 1115 + "version": "1.1.2", 1116 + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 1117 + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 1118 + "license": "MIT", 1119 + "funding": { 1120 + "url": "https://github.com/sponsors/ljharb" 1121 + } 1122 + }, 1123 + "node_modules/get-intrinsic": { 1124 + "version": "1.3.0", 1125 + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 1126 + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 1127 + "license": "MIT", 1128 + "dependencies": { 1129 + "call-bind-apply-helpers": "^1.0.2", 1130 + "es-define-property": "^1.0.1", 1131 + "es-errors": "^1.3.0", 1132 + "es-object-atoms": "^1.1.1", 1133 + "function-bind": "^1.1.2", 1134 + "get-proto": "^1.0.1", 1135 + "gopd": "^1.2.0", 1136 + "has-symbols": "^1.1.0", 1137 + "hasown": "^2.0.2", 1138 + "math-intrinsics": "^1.1.0" 1139 + }, 1140 + "engines": { 1141 + "node": ">= 0.4" 1142 + }, 1143 + "funding": { 1144 + "url": "https://github.com/sponsors/ljharb" 1145 + } 1146 + }, 1147 + "node_modules/get-proto": { 1148 + "version": "1.0.1", 1149 + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 1150 + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 1151 + "license": "MIT", 1152 + "dependencies": { 1153 + "dunder-proto": "^1.0.1", 1154 + "es-object-atoms": "^1.0.0" 1155 + }, 1156 + "engines": { 1157 + "node": ">= 0.4" 1158 + } 1159 + }, 558 1160 "node_modules/get-tsconfig": { 559 1161 "version": "4.10.1", 560 1162 "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.10.1.tgz", ··· 567 1169 "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" 568 1170 } 569 1171 }, 1172 + "node_modules/gopd": { 1173 + "version": "1.2.0", 1174 + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 1175 + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 1176 + "license": "MIT", 1177 + "engines": { 1178 + "node": ">= 0.4" 1179 + }, 1180 + "funding": { 1181 + "url": "https://github.com/sponsors/ljharb" 1182 + } 1183 + }, 570 1184 "node_modules/graphemer": { 571 1185 "version": "1.4.0", 572 1186 "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", 573 1187 "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 574 1188 "license": "MIT" 575 1189 }, 1190 + "node_modules/has-flag": { 1191 + "version": "4.0.0", 1192 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 1193 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 1194 + "license": "MIT", 1195 + "engines": { 1196 + "node": ">=8" 1197 + } 1198 + }, 1199 + "node_modules/has-symbols": { 1200 + "version": "1.1.0", 1201 + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 1202 + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 1203 + "license": "MIT", 1204 + "engines": { 1205 + "node": ">= 0.4" 1206 + }, 1207 + "funding": { 1208 + "url": "https://github.com/sponsors/ljharb" 1209 + } 1210 + }, 1211 + "node_modules/hasown": { 1212 + "version": "2.0.2", 1213 + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 1214 + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 1215 + "license": "MIT", 1216 + "dependencies": { 1217 + "function-bind": "^1.1.2" 1218 + }, 1219 + "engines": { 1220 + "node": ">= 0.4" 1221 + } 1222 + }, 1223 + "node_modules/http-errors": { 1224 + "version": "2.0.0", 1225 + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", 1226 + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1227 + "license": "MIT", 1228 + "dependencies": { 1229 + "depd": "2.0.0", 1230 + "inherits": "2.0.4", 1231 + "setprototypeof": "1.2.0", 1232 + "statuses": "2.0.1", 1233 + "toidentifier": "1.0.1" 1234 + }, 1235 + "engines": { 1236 + "node": ">= 0.8" 1237 + } 1238 + }, 1239 + "node_modules/http-errors/node_modules/statuses": { 1240 + "version": "2.0.1", 1241 + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", 1242 + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", 1243 + "license": "MIT", 1244 + "engines": { 1245 + "node": ">= 0.8" 1246 + } 1247 + }, 1248 + "node_modules/iconv-lite": { 1249 + "version": "0.6.3", 1250 + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", 1251 + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", 1252 + "license": "MIT", 1253 + "dependencies": { 1254 + "safer-buffer": ">= 2.1.2 < 3.0.0" 1255 + }, 1256 + "engines": { 1257 + "node": ">=0.10.0" 1258 + } 1259 + }, 1260 + "node_modules/inherits": { 1261 + "version": "2.0.4", 1262 + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", 1263 + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", 1264 + "license": "ISC" 1265 + }, 1266 + "node_modules/ipaddr.js": { 1267 + "version": "1.9.1", 1268 + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", 1269 + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", 1270 + "license": "MIT", 1271 + "engines": { 1272 + "node": ">= 0.10" 1273 + } 1274 + }, 1275 + "node_modules/is-promise": { 1276 + "version": "4.0.0", 1277 + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", 1278 + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", 1279 + "license": "MIT" 1280 + }, 576 1281 "node_modules/iso-datestring-validator": { 577 1282 "version": "2.2.2", 578 1283 "resolved": "https://registry.npmjs.org/iso-datestring-validator/-/iso-datestring-validator-2.2.2.tgz", 579 1284 "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==", 580 1285 "license": "MIT" 581 1286 }, 1287 + "node_modules/jake": { 1288 + "version": "10.9.2", 1289 + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", 1290 + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", 1291 + "license": "Apache-2.0", 1292 + "dependencies": { 1293 + "async": "^3.2.3", 1294 + "chalk": "^4.0.2", 1295 + "filelist": "^1.0.4", 1296 + "minimatch": "^3.1.2" 1297 + }, 1298 + "bin": { 1299 + "jake": "bin/cli.js" 1300 + }, 1301 + "engines": { 1302 + "node": ">=10" 1303 + } 1304 + }, 1305 + "node_modules/math-intrinsics": { 1306 + "version": "1.1.0", 1307 + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 1308 + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 1309 + "license": "MIT", 1310 + "engines": { 1311 + "node": ">= 0.4" 1312 + } 1313 + }, 1314 + "node_modules/media-typer": { 1315 + "version": "1.1.0", 1316 + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", 1317 + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", 1318 + "license": "MIT", 1319 + "engines": { 1320 + "node": ">= 0.8" 1321 + } 1322 + }, 1323 + "node_modules/merge-descriptors": { 1324 + "version": "2.0.0", 1325 + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", 1326 + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", 1327 + "license": "MIT", 1328 + "engines": { 1329 + "node": ">=18" 1330 + }, 1331 + "funding": { 1332 + "url": "https://github.com/sponsors/sindresorhus" 1333 + } 1334 + }, 1335 + "node_modules/mime-db": { 1336 + "version": "1.54.0", 1337 + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", 1338 + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", 1339 + "license": "MIT", 1340 + "engines": { 1341 + "node": ">= 0.6" 1342 + } 1343 + }, 1344 + "node_modules/mime-types": { 1345 + "version": "3.0.1", 1346 + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", 1347 + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", 1348 + "license": "MIT", 1349 + "dependencies": { 1350 + "mime-db": "^1.54.0" 1351 + }, 1352 + "engines": { 1353 + "node": ">= 0.6" 1354 + } 1355 + }, 1356 + "node_modules/minimatch": { 1357 + "version": "3.1.2", 1358 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", 1359 + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", 1360 + "license": "ISC", 1361 + "dependencies": { 1362 + "brace-expansion": "^1.1.7" 1363 + }, 1364 + "engines": { 1365 + "node": "*" 1366 + } 1367 + }, 1368 + "node_modules/ms": { 1369 + "version": "2.1.3", 1370 + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 1371 + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 1372 + "license": "MIT" 1373 + }, 582 1374 "node_modules/multiformats": { 583 1375 "version": "9.9.0", 584 1376 "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", 585 1377 "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 586 1378 "license": "(Apache-2.0 AND MIT)" 587 1379 }, 1380 + "node_modules/negotiator": { 1381 + "version": "1.0.0", 1382 + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", 1383 + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", 1384 + "license": "MIT", 1385 + "engines": { 1386 + "node": ">= 0.6" 1387 + } 1388 + }, 1389 + "node_modules/object-inspect": { 1390 + "version": "1.13.4", 1391 + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 1392 + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 1393 + "license": "MIT", 1394 + "engines": { 1395 + "node": ">= 0.4" 1396 + }, 1397 + "funding": { 1398 + "url": "https://github.com/sponsors/ljharb" 1399 + } 1400 + }, 1401 + "node_modules/on-finished": { 1402 + "version": "2.4.1", 1403 + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", 1404 + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", 1405 + "license": "MIT", 1406 + "dependencies": { 1407 + "ee-first": "1.1.1" 1408 + }, 1409 + "engines": { 1410 + "node": ">= 0.8" 1411 + } 1412 + }, 1413 + "node_modules/on-headers": { 1414 + "version": "1.0.2", 1415 + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.0.2.tgz", 1416 + "integrity": "sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==", 1417 + "license": "MIT", 1418 + "engines": { 1419 + "node": ">= 0.8" 1420 + } 1421 + }, 1422 + "node_modules/once": { 1423 + "version": "1.4.0", 1424 + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1425 + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", 1426 + "license": "ISC", 1427 + "dependencies": { 1428 + "wrappy": "1" 1429 + } 1430 + }, 1431 + "node_modules/parseurl": { 1432 + "version": "1.3.3", 1433 + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", 1434 + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", 1435 + "license": "MIT", 1436 + "engines": { 1437 + "node": ">= 0.8" 1438 + } 1439 + }, 1440 + "node_modules/path-to-regexp": { 1441 + "version": "8.2.0", 1442 + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.2.0.tgz", 1443 + "integrity": "sha512-TdrF7fW9Rphjq4RjrW0Kp2AW0Ahwu9sRGTkS6bvDi0SCwZlEZYmcfDbEsTz8RVk0EHIS/Vd1bv3JhG+1xZuAyQ==", 1444 + "license": "MIT", 1445 + "engines": { 1446 + "node": ">=16" 1447 + } 1448 + }, 1449 + "node_modules/proxy-addr": { 1450 + "version": "2.0.7", 1451 + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", 1452 + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", 1453 + "license": "MIT", 1454 + "dependencies": { 1455 + "forwarded": "0.2.0", 1456 + "ipaddr.js": "1.9.1" 1457 + }, 1458 + "engines": { 1459 + "node": ">= 0.10" 1460 + } 1461 + }, 1462 + "node_modules/qs": { 1463 + "version": "6.14.0", 1464 + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", 1465 + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", 1466 + "license": "BSD-3-Clause", 1467 + "dependencies": { 1468 + "side-channel": "^1.1.0" 1469 + }, 1470 + "engines": { 1471 + "node": ">=0.6" 1472 + }, 1473 + "funding": { 1474 + "url": "https://github.com/sponsors/ljharb" 1475 + } 1476 + }, 1477 + "node_modules/random-bytes": { 1478 + "version": "1.0.0", 1479 + "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", 1480 + "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==", 1481 + "license": "MIT", 1482 + "engines": { 1483 + "node": ">= 0.8" 1484 + } 1485 + }, 1486 + "node_modules/range-parser": { 1487 + "version": "1.2.1", 1488 + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", 1489 + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", 1490 + "license": "MIT", 1491 + "engines": { 1492 + "node": ">= 0.6" 1493 + } 1494 + }, 1495 + "node_modules/raw-body": { 1496 + "version": "3.0.0", 1497 + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", 1498 + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", 1499 + "license": "MIT", 1500 + "dependencies": { 1501 + "bytes": "3.1.2", 1502 + "http-errors": "2.0.0", 1503 + "iconv-lite": "0.6.3", 1504 + "unpipe": "1.0.0" 1505 + }, 1506 + "engines": { 1507 + "node": ">= 0.8" 1508 + } 1509 + }, 588 1510 "node_modules/resolve-pkg-maps": { 589 1511 "version": "1.0.0", 590 1512 "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", ··· 594 1516 "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" 595 1517 } 596 1518 }, 1519 + "node_modules/router": { 1520 + "version": "2.2.0", 1521 + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", 1522 + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", 1523 + "license": "MIT", 1524 + "dependencies": { 1525 + "debug": "^4.4.0", 1526 + "depd": "^2.0.0", 1527 + "is-promise": "^4.0.0", 1528 + "parseurl": "^1.3.3", 1529 + "path-to-regexp": "^8.0.0" 1530 + }, 1531 + "engines": { 1532 + "node": ">= 18" 1533 + } 1534 + }, 1535 + "node_modules/safe-buffer": { 1536 + "version": "5.2.1", 1537 + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", 1538 + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", 1539 + "funding": [ 1540 + { 1541 + "type": "github", 1542 + "url": "https://github.com/sponsors/feross" 1543 + }, 1544 + { 1545 + "type": "patreon", 1546 + "url": "https://www.patreon.com/feross" 1547 + }, 1548 + { 1549 + "type": "consulting", 1550 + "url": "https://feross.org/support" 1551 + } 1552 + ], 1553 + "license": "MIT" 1554 + }, 1555 + "node_modules/safer-buffer": { 1556 + "version": "2.1.2", 1557 + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1558 + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1559 + "license": "MIT" 1560 + }, 1561 + "node_modules/send": { 1562 + "version": "1.2.0", 1563 + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", 1564 + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", 1565 + "license": "MIT", 1566 + "dependencies": { 1567 + "debug": "^4.3.5", 1568 + "encodeurl": "^2.0.0", 1569 + "escape-html": "^1.0.3", 1570 + "etag": "^1.8.1", 1571 + "fresh": "^2.0.0", 1572 + "http-errors": "^2.0.0", 1573 + "mime-types": "^3.0.1", 1574 + "ms": "^2.1.3", 1575 + "on-finished": "^2.4.1", 1576 + "range-parser": "^1.2.1", 1577 + "statuses": "^2.0.1" 1578 + }, 1579 + "engines": { 1580 + "node": ">= 18" 1581 + } 1582 + }, 1583 + "node_modules/serve-static": { 1584 + "version": "2.2.0", 1585 + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", 1586 + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", 1587 + "license": "MIT", 1588 + "dependencies": { 1589 + "encodeurl": "^2.0.0", 1590 + "escape-html": "^1.0.3", 1591 + "parseurl": "^1.3.3", 1592 + "send": "^1.2.0" 1593 + }, 1594 + "engines": { 1595 + "node": ">= 18" 1596 + } 1597 + }, 1598 + "node_modules/setprototypeof": { 1599 + "version": "1.2.0", 1600 + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", 1601 + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", 1602 + "license": "ISC" 1603 + }, 1604 + "node_modules/side-channel": { 1605 + "version": "1.1.0", 1606 + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 1607 + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 1608 + "license": "MIT", 1609 + "dependencies": { 1610 + "es-errors": "^1.3.0", 1611 + "object-inspect": "^1.13.3", 1612 + "side-channel-list": "^1.0.0", 1613 + "side-channel-map": "^1.0.1", 1614 + "side-channel-weakmap": "^1.0.2" 1615 + }, 1616 + "engines": { 1617 + "node": ">= 0.4" 1618 + }, 1619 + "funding": { 1620 + "url": "https://github.com/sponsors/ljharb" 1621 + } 1622 + }, 1623 + "node_modules/side-channel-list": { 1624 + "version": "1.0.0", 1625 + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 1626 + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1627 + "license": "MIT", 1628 + "dependencies": { 1629 + "es-errors": "^1.3.0", 1630 + "object-inspect": "^1.13.3" 1631 + }, 1632 + "engines": { 1633 + "node": ">= 0.4" 1634 + }, 1635 + "funding": { 1636 + "url": "https://github.com/sponsors/ljharb" 1637 + } 1638 + }, 1639 + "node_modules/side-channel-map": { 1640 + "version": "1.0.1", 1641 + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 1642 + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 1643 + "license": "MIT", 1644 + "dependencies": { 1645 + "call-bound": "^1.0.2", 1646 + "es-errors": "^1.3.0", 1647 + "get-intrinsic": "^1.2.5", 1648 + "object-inspect": "^1.13.3" 1649 + }, 1650 + "engines": { 1651 + "node": ">= 0.4" 1652 + }, 1653 + "funding": { 1654 + "url": "https://github.com/sponsors/ljharb" 1655 + } 1656 + }, 1657 + "node_modules/side-channel-weakmap": { 1658 + "version": "1.0.2", 1659 + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 1660 + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 1661 + "license": "MIT", 1662 + "dependencies": { 1663 + "call-bound": "^1.0.2", 1664 + "es-errors": "^1.3.0", 1665 + "get-intrinsic": "^1.2.5", 1666 + "object-inspect": "^1.13.3", 1667 + "side-channel-map": "^1.0.1" 1668 + }, 1669 + "engines": { 1670 + "node": ">= 0.4" 1671 + }, 1672 + "funding": { 1673 + "url": "https://github.com/sponsors/ljharb" 1674 + } 1675 + }, 1676 + "node_modules/statuses": { 1677 + "version": "2.0.2", 1678 + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", 1679 + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", 1680 + "license": "MIT", 1681 + "engines": { 1682 + "node": ">= 0.8" 1683 + } 1684 + }, 1685 + "node_modules/supports-color": { 1686 + "version": "7.2.0", 1687 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 1688 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 1689 + "license": "MIT", 1690 + "dependencies": { 1691 + "has-flag": "^4.0.0" 1692 + }, 1693 + "engines": { 1694 + "node": ">=8" 1695 + } 1696 + }, 597 1697 "node_modules/tlds": { 598 1698 "version": "1.259.0", 599 1699 "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.259.0.tgz", ··· 603 1703 "tlds": "bin.js" 604 1704 } 605 1705 }, 1706 + "node_modules/toidentifier": { 1707 + "version": "1.0.1", 1708 + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", 1709 + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", 1710 + "license": "MIT", 1711 + "engines": { 1712 + "node": ">=0.6" 1713 + } 1714 + }, 606 1715 "node_modules/tsx": { 607 1716 "version": "4.20.3", 608 1717 "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.20.3.tgz", ··· 620 1729 }, 621 1730 "optionalDependencies": { 622 1731 "fsevents": "~2.3.3" 1732 + } 1733 + }, 1734 + "node_modules/type-is": { 1735 + "version": "2.0.1", 1736 + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", 1737 + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", 1738 + "license": "MIT", 1739 + "dependencies": { 1740 + "content-type": "^1.0.5", 1741 + "media-typer": "^1.1.0", 1742 + "mime-types": "^3.0.0" 1743 + }, 1744 + "engines": { 1745 + "node": ">= 0.6" 623 1746 } 624 1747 }, 625 1748 "node_modules/typescript": { ··· 635 1758 "node": ">=14.17" 636 1759 } 637 1760 }, 1761 + "node_modules/uid-safe": { 1762 + "version": "2.1.5", 1763 + "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", 1764 + "integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==", 1765 + "license": "MIT", 1766 + "dependencies": { 1767 + "random-bytes": "~1.0.0" 1768 + }, 1769 + "engines": { 1770 + "node": ">= 0.8" 1771 + } 1772 + }, 638 1773 "node_modules/uint8arrays": { 639 1774 "version": "3.0.0", 640 1775 "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.0.0.tgz", ··· 649 1784 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.8.0.tgz", 650 1785 "integrity": "sha512-9UJ2xGDvQ43tYyVMpuHlsgApydB8ZKfVYTsLDhXkFL/6gfkp+U8xTGdh8pMJv1SpZna0zxG1DwsKZsreLbXBxw==", 651 1786 "license": "MIT" 1787 + }, 1788 + "node_modules/unpipe": { 1789 + "version": "1.0.0", 1790 + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 1791 + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", 1792 + "license": "MIT", 1793 + "engines": { 1794 + "node": ">= 0.8" 1795 + } 1796 + }, 1797 + "node_modules/vary": { 1798 + "version": "1.1.2", 1799 + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 1800 + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", 1801 + "license": "MIT", 1802 + "engines": { 1803 + "node": ">= 0.8" 1804 + } 1805 + }, 1806 + "node_modules/wrappy": { 1807 + "version": "1.0.2", 1808 + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1809 + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", 1810 + "license": "ISC" 652 1811 }, 653 1812 "node_modules/zod": { 654 1813 "version": "3.25.67",
+7
package.json
··· 4 4 "main": "index.js", 5 5 "scripts": { 6 6 "dev": "tsx watch src/index.ts", 7 + "web": "tsx watch src/server.ts", 7 8 "build": "tsc", 8 9 "start": "node dist/index.js", 9 10 "test": "echo \"Error: no test specified\" && exit 1" ··· 15 16 "dependencies": { 16 17 "@atproto/api": "^0.15.16", 17 18 "@atproto/lexicon": "^0.4.11", 19 + "@types/express": "^5.0.3", 20 + "@types/express-session": "^1.18.2", 18 21 "@types/node": "^24.0.3", 19 22 "dotenv": "^16.5.0", 23 + "ejs": "^3.1.10", 24 + "express": "^5.1.0", 25 + "express-ejs-layouts": "^2.5.1", 26 + "express-session": "^1.18.1", 20 27 "tsx": "^4.20.3", 21 28 "typescript": "^5.8.3" 22 29 }
+203
public/css/style.css
··· 1 + :root { 2 + --blonk-blue: #3366cc; 3 + --blonk-light-blue: #6699ff; 4 + --blonk-gray: #666666; 5 + --blonk-light-gray: #cccccc; 6 + --blonk-bg: #f8f8f8; 7 + --blonk-white: #ffffff; 8 + } 9 + 10 + * { 11 + margin: 0; 12 + padding: 0; 13 + box-sizing: border-box; 14 + } 15 + 16 + body { 17 + font-family: Verdana, Arial, sans-serif; 18 + font-size: 11px; 19 + line-height: 1.4; 20 + color: #333; 21 + background-color: var(--blonk-bg); 22 + } 23 + 24 + .container { 25 + max-width: 800px; 26 + margin: 0 auto; 27 + padding: 20px; 28 + } 29 + 30 + header { 31 + background-color: var(--blonk-blue); 32 + color: var(--blonk-white); 33 + padding: 10px 0; 34 + margin-bottom: 20px; 35 + } 36 + 37 + header .container { 38 + display: flex; 39 + justify-content: space-between; 40 + align-items: center; 41 + padding: 0 20px; 42 + } 43 + 44 + header h1 { 45 + font-size: 24px; 46 + font-weight: normal; 47 + letter-spacing: -1px; 48 + } 49 + 50 + header nav a { 51 + color: var(--blonk-white); 52 + text-decoration: none; 53 + margin-left: 15px; 54 + font-size: 12px; 55 + } 56 + 57 + header nav a:hover { 58 + text-decoration: underline; 59 + } 60 + 61 + .blip-list { 62 + background-color: var(--blonk-white); 63 + border: 1px solid var(--blonk-light-gray); 64 + } 65 + 66 + .blip-item { 67 + padding: 8px 10px; 68 + border-bottom: 1px solid #f0f0f0; 69 + display: flex; 70 + align-items: start; 71 + gap: 10px; 72 + } 73 + 74 + .blip-item:last-child { 75 + border-bottom: none; 76 + } 77 + 78 + .blip-fluffs { 79 + color: var(--blonk-gray); 80 + font-size: 10px; 81 + min-width: 30px; 82 + text-align: right; 83 + padding-top: 2px; 84 + } 85 + 86 + .blip-content { 87 + flex: 1; 88 + } 89 + 90 + .blip-title { 91 + color: var(--blonk-blue); 92 + text-decoration: none; 93 + font-size: 12px; 94 + font-weight: normal; 95 + } 96 + 97 + .blip-title:hover { 98 + text-decoration: underline; 99 + } 100 + 101 + .blip-meta { 102 + color: var(--blonk-gray); 103 + font-size: 10px; 104 + margin-top: 2px; 105 + } 106 + 107 + .blip-tags { 108 + display: inline; 109 + margin-left: 5px; 110 + } 111 + 112 + .tag { 113 + color: var(--blonk-gray); 114 + text-decoration: none; 115 + margin-right: 5px; 116 + font-size: 10px; 117 + } 118 + 119 + .tag:hover { 120 + color: var(--blonk-blue); 121 + text-decoration: underline; 122 + } 123 + 124 + .tag:before { 125 + content: ''; 126 + } 127 + 128 + .submit-form { 129 + background-color: var(--blonk-white); 130 + border: 1px solid var(--blonk-light-gray); 131 + padding: 20px; 132 + margin-top: 20px; 133 + } 134 + 135 + .form-group { 136 + margin-bottom: 15px; 137 + } 138 + 139 + .form-group label { 140 + display: block; 141 + color: var(--blonk-gray); 142 + margin-bottom: 3px; 143 + font-size: 11px; 144 + } 145 + 146 + .form-group input[type="text"], 147 + .form-group input[type="url"], 148 + .form-group textarea { 149 + width: 100%; 150 + padding: 4px 6px; 151 + border: 1px solid var(--blonk-light-gray); 152 + font-family: Verdana, Arial, sans-serif; 153 + font-size: 11px; 154 + } 155 + 156 + .form-group textarea { 157 + height: 60px; 158 + resize: vertical; 159 + } 160 + 161 + .form-note { 162 + color: var(--blonk-gray); 163 + font-size: 10px; 164 + margin-top: 2px; 165 + } 166 + 167 + .submit-button { 168 + background-color: var(--blonk-blue); 169 + color: var(--blonk-white); 170 + border: none; 171 + padding: 5px 15px; 172 + font-size: 11px; 173 + cursor: pointer; 174 + } 175 + 176 + .submit-button:hover { 177 + background-color: var(--blonk-light-blue); 178 + } 179 + 180 + .page-title { 181 + font-size: 16px; 182 + color: var(--blonk-gray); 183 + margin-bottom: 15px; 184 + font-weight: normal; 185 + } 186 + 187 + .empty-state { 188 + text-align: center; 189 + padding: 40px; 190 + color: var(--blonk-gray); 191 + } 192 + 193 + .radar-icon { 194 + display: inline-block; 195 + animation: pulse 2s infinite; 196 + margin-right: 5px; 197 + } 198 + 199 + @keyframes pulse { 200 + 0% { opacity: 1; } 201 + 50% { opacity: 0.5; } 202 + 100% { opacity: 1; } 203 + }
+2 -1
src/blips.ts
··· 4 4 export class BlipManager { 5 5 constructor(private agent: BskyAgent) {} 6 6 7 - async createBlip(title: string, body?: string, url?: string): Promise<string> { 7 + async createBlip(title: string, body?: string, url?: string, tags?: string[]): Promise<string> { 8 8 const blip: BlonkBlip = { 9 9 title, 10 10 body, 11 11 url, 12 + tags, 12 13 createdAt: new Date().toISOString(), 13 14 fluffs: 0, 14 15 };
+2 -1
src/index.ts
··· 16 16 const blipUri = await blipManager.createBlip( 17 17 'Welcome to the Blonk Vibe Radar!', 18 18 'This is the first blip on Blonk, where vibes are tracked on the radar.', 19 - 'https://atproto.com' 19 + 'https://atproto.com', 20 + ['welcome', 'atproto', 'blonk'] 20 21 ); 21 22 console.log(`Blip transmitted with URI: ${blipUri}\n`); 22 23
+1
src/schemas.ts
··· 6 6 title: string; 7 7 body?: string; 8 8 url?: string; 9 + tags?: string[]; 9 10 createdAt: string; 10 11 fluffs: number; 11 12 }
+85
src/server.ts
··· 1 + import express from 'express'; 2 + import session from 'express-session'; 3 + import expressLayouts from 'express-ejs-layouts'; 4 + import path from 'path'; 5 + import { BlonkAgent } from './agent'; 6 + import { BlipManager } from './blips'; 7 + import * as dotenv from 'dotenv'; 8 + 9 + dotenv.config(); 10 + 11 + const app = express(); 12 + const PORT = process.env.PORT || 3000; 13 + 14 + app.set('view engine', 'ejs'); 15 + app.set('views', path.join(__dirname, '../views')); 16 + app.use(expressLayouts); 17 + app.set('layout', 'layout'); 18 + app.use(express.static(path.join(__dirname, '../public'))); 19 + app.use(express.urlencoded({ extended: true })); 20 + app.use(express.json()); 21 + 22 + app.use(session({ 23 + secret: process.env.SESSION_SECRET || 'blonk-secret-key', 24 + resave: false, 25 + saveUninitialized: true, 26 + })); 27 + 28 + let agent: BlonkAgent; 29 + let blipManager: BlipManager; 30 + 31 + async function initializeAgent() { 32 + agent = new BlonkAgent(); 33 + await agent.login(); 34 + blipManager = new BlipManager(agent.getAgent()); 35 + console.log('✅ Connected to AT Protocol'); 36 + } 37 + 38 + app.get('/', async (req, res) => { 39 + try { 40 + const blips = await blipManager.getBlips(50); 41 + res.render('index', { blips, user: req.session.user }); 42 + } catch (error) { 43 + console.error('Error fetching blips:', error); 44 + res.render('index', { blips: [], user: req.session.user }); 45 + } 46 + }); 47 + 48 + app.get('/submit', (req, res) => { 49 + res.render('submit', { user: req.session.user }); 50 + }); 51 + 52 + app.post('/submit', async (req, res) => { 53 + try { 54 + const { title, url, body, tags } = req.body; 55 + const tagArray = tags ? tags.split(' ').filter((t: string) => t.length > 0) : []; 56 + 57 + await blipManager.createBlip(title, body, url, tagArray); 58 + res.redirect('/'); 59 + } catch (error) { 60 + console.error('Error creating blip:', error); 61 + res.render('submit', { error: 'Failed to create blip', user: req.session.user }); 62 + } 63 + }); 64 + 65 + app.get('/tag/:tag', async (req, res) => { 66 + try { 67 + const allBlips = await blipManager.getBlips(100); 68 + const taggedBlips = allBlips.filter(blip => 69 + blip.tags?.includes(req.params.tag) 70 + ); 71 + res.render('tag', { blips: taggedBlips, tag: req.params.tag, user: req.session.user }); 72 + } catch (error) { 73 + console.error('Error fetching tagged blips:', error); 74 + res.render('tag', { blips: [], tag: req.params.tag, user: req.session.user }); 75 + } 76 + }); 77 + 78 + initializeAgent().then(() => { 79 + app.listen(PORT, () => { 80 + console.log(`🌐 Blonk server running at http://localhost:${PORT}`); 81 + }); 82 + }).catch(error => { 83 + console.error('Failed to initialize:', error); 84 + process.exit(1); 85 + });
+42
views/index.ejs
··· 1 + <h2 class="page-title">recent blips on the radar</h2> 2 + 3 + <div class="blip-list"> 4 + <% if (blips.length === 0) { %> 5 + <div class="empty-state"> 6 + <p>No blips detected on the radar yet.</p> 7 + <p><a href="/submit">Transmit the first blip</a></p> 8 + </div> 9 + <% } else { %> 10 + <% blips.forEach(blip => { %> 11 + <div class="blip-item"> 12 + <div class="blip-fluffs"> 13 + <%= blip.fluffs %> 14 + </div> 15 + <div class="blip-content"> 16 + <% if (blip.url) { %> 17 + <a href="<%= blip.url %>" class="blip-title" target="_blank"><%= blip.title %></a> 18 + <% } else { %> 19 + <span class="blip-title"><%= blip.title %></span> 20 + <% } %> 21 + 22 + <% if (blip.body) { %> 23 + <div style="color: #666; font-size: 10px; margin-top: 2px;"> 24 + <%= blip.body.substring(0, 200) %><%= blip.body.length > 200 ? '...' : '' %> 25 + </div> 26 + <% } %> 27 + 28 + <div class="blip-meta"> 29 + <span><%= new Date(blip.createdAt).toLocaleString() %></span> 30 + <% if (blip.tags && blip.tags.length > 0) { %> 31 + <span class="blip-tags"> 32 + <% blip.tags.forEach(tag => { %> 33 + <a href="/tag/<%= tag %>" class="tag"><%= tag %></a> 34 + <% }) %> 35 + </span> 36 + <% } %> 37 + </div> 38 + </div> 39 + </div> 40 + <% }) %> 41 + <% } %> 42 + </div>
+24
views/layout.ejs
··· 1 + <!DOCTYPE html> 2 + <html lang="en"> 3 + <head> 4 + <meta charset="UTF-8"> 5 + <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 + <title>blonk / vibe radar</title> 7 + <link rel="stylesheet" href="/css/style.css"> 8 + </head> 9 + <body> 10 + <header> 11 + <div class="container"> 12 + <h1><span class="radar-icon">📡</span>blonk</h1> 13 + <nav> 14 + <a href="/">radar</a> 15 + <a href="/submit">transmit</a> 16 + </nav> 17 + </div> 18 + </header> 19 + 20 + <div class="container"> 21 + <%- body %> 22 + </div> 23 + </body> 24 + </html>
+31
views/submit.ejs
··· 1 + <h2 class="page-title">transmit a new blip</h2> 2 + 3 + <form action="/submit" method="POST" class="submit-form"> 4 + <div class="form-group"> 5 + <label for="title">signal</label> 6 + <input type="text" id="title" name="title" required placeholder="What's on the radar?"> 7 + </div> 8 + 9 + <div class="form-group"> 10 + <label for="url">frequency (optional)</label> 11 + <input type="url" id="url" name="url" placeholder="https://..."> 12 + <div class="form-note">link to external content</div> 13 + </div> 14 + 15 + <div class="form-group"> 16 + <label for="body">transmission details (optional)</label> 17 + <textarea id="body" name="body" placeholder="Additional context or thoughts..."></textarea> 18 + </div> 19 + 20 + <div class="form-group"> 21 + <label for="tags">tags</label> 22 + <input type="text" id="tags" name="tags" placeholder="space separated tags"> 23 + <div class="form-note">e.g., programming atproto bluesky</div> 24 + </div> 25 + 26 + <% if (locals.error) { %> 27 + <div style="color: red; margin-bottom: 10px;"><%= error %></div> 28 + <% } %> 29 + 30 + <button type="submit" class="submit-button">transmit blip</button> 31 + </form>
+42
views/tag.ejs
··· 1 + <h2 class="page-title">blips tagged: <%= tag %></h2> 2 + 3 + <div class="blip-list"> 4 + <% if (blips.length === 0) { %> 5 + <div class="empty-state"> 6 + <p>No blips with this tag detected.</p> 7 + <p><a href="/">Back to all blips</a></p> 8 + </div> 9 + <% } else { %> 10 + <% blips.forEach(blip => { %> 11 + <div class="blip-item"> 12 + <div class="blip-fluffs"> 13 + <%= blip.fluffs %> 14 + </div> 15 + <div class="blip-content"> 16 + <% if (blip.url) { %> 17 + <a href="<%= blip.url %>" class="blip-title" target="_blank"><%= blip.title %></a> 18 + <% } else { %> 19 + <span class="blip-title"><%= blip.title %></span> 20 + <% } %> 21 + 22 + <% if (blip.body) { %> 23 + <div style="color: #666; font-size: 10px; margin-top: 2px;"> 24 + <%= blip.body.substring(0, 200) %><%= blip.body.length > 200 ? '...' : '' %> 25 + </div> 26 + <% } %> 27 + 28 + <div class="blip-meta"> 29 + <span><%= new Date(blip.createdAt).toLocaleString() %></span> 30 + <% if (blip.tags && blip.tags.length > 0) { %> 31 + <span class="blip-tags"> 32 + <% blip.tags.forEach(tag => { %> 33 + <a href="/tag/<%= tag %>" class="tag"><%= tag %></a> 34 + <% }) %> 35 + </span> 36 + <% } %> 37 + </div> 38 + </div> 39 + </div> 40 + <% }) %> 41 + <% } %> 42 + </div>