A website for the ATmosphereConf

Compare changes

Choose any two refs to compare.

-13
README.md
··· 1 - # ATmosphereConf 2 - 3 - Main website: https://atmosphereconf.org> (currently redirects to our ticketing landing page) 4 - News & Updates: <https://news.atprotocol.org> (powered by Leaflet!) 5 - Save the Date with an RSVP on [Smoke Signal](https://smokesignal.events/did:plc:lehcqqkwzcwvjvw66uthu5oq/3lte3c7x43l2e) 6 - 7 - We will be building out a conference website with a handful of ATProto specific features. 8 - 9 - For open discussion, and for those who are joining as active volunteers, please visit the [community forum](https://discourse.atprotocol.community/c/atmosphereconf/25/none). 10 - 11 - ## Conference Profile 12 - For starters, we're going to have a custom conference profile. Attendees and speakers (and anyone else!) can login and create an extended profile. Inspired by [Discover Toronto](https://discover.toronto.inc/), we had an [initial discussion in the forum](https://discourse.atprotocol.community/t/conference-profiles/186) and are going to work on fleshing this out here with detailed issues. 13 - 14 1 # Astro ATProto OAuth Starter 15 2 16 3 A minimal [Astro](https://astro.build) starter template demonstrating OAuth authentication with AT Protocol (ATProto), the decentralized social networking protocol used by Bluesky and other services.
+156
lexicons/defs.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.label.defs", 4 + "defs": { 5 + "label": { 6 + "type": "object", 7 + "description": "Metadata tag on an atproto resource (eg, repo or record).", 8 + "required": ["src", "uri", "val", "cts"], 9 + "properties": { 10 + "ver": { 11 + "type": "integer", 12 + "description": "The AT Protocol version of the label object." 13 + }, 14 + "src": { 15 + "type": "string", 16 + "format": "did", 17 + "description": "DID of the actor who created this label." 18 + }, 19 + "uri": { 20 + "type": "string", 21 + "format": "uri", 22 + "description": "AT URI of the record, repository (account), or other resource that this label applies to." 23 + }, 24 + "cid": { 25 + "type": "string", 26 + "format": "cid", 27 + "description": "Optionally, CID specifying the specific version of 'uri' resource this label applies to." 28 + }, 29 + "val": { 30 + "type": "string", 31 + "maxLength": 128, 32 + "description": "The short string name of the value or type of this label." 33 + }, 34 + "neg": { 35 + "type": "boolean", 36 + "description": "If true, this is a negation label, overwriting a previous label." 37 + }, 38 + "cts": { 39 + "type": "string", 40 + "format": "datetime", 41 + "description": "Timestamp when this label was created." 42 + }, 43 + "exp": { 44 + "type": "string", 45 + "format": "datetime", 46 + "description": "Timestamp at which this label expires (no longer applies)." 47 + }, 48 + "sig": { 49 + "type": "bytes", 50 + "description": "Signature of dag-cbor encoded label." 51 + } 52 + } 53 + }, 54 + "selfLabels": { 55 + "type": "object", 56 + "description": "Metadata tags on an atproto record, published by the author within the record.", 57 + "required": ["values"], 58 + "properties": { 59 + "values": { 60 + "type": "array", 61 + "items": { "type": "ref", "ref": "#selfLabel" }, 62 + "maxLength": 10 63 + } 64 + } 65 + }, 66 + "selfLabel": { 67 + "type": "object", 68 + "description": "Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.", 69 + "required": ["val"], 70 + "properties": { 71 + "val": { 72 + "type": "string", 73 + "maxLength": 128, 74 + "description": "The short string name of the value or type of this label." 75 + } 76 + } 77 + }, 78 + "labelValueDefinition": { 79 + "type": "object", 80 + "description": "Declares a label value and its expected interpretations and behaviors.", 81 + "required": ["identifier", "severity", "blurs", "locales"], 82 + "properties": { 83 + "identifier": { 84 + "type": "string", 85 + "description": "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 86 + "maxLength": 100, 87 + "maxGraphemes": 100 88 + }, 89 + "severity": { 90 + "type": "string", 91 + "description": "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 92 + "knownValues": ["inform", "alert", "none"] 93 + }, 94 + "blurs": { 95 + "type": "string", 96 + "description": "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 97 + "knownValues": ["content", "media", "none"] 98 + }, 99 + "defaultSetting": { 100 + "type": "string", 101 + "description": "The default setting for this label.", 102 + "knownValues": ["ignore", "warn", "hide"], 103 + "default": "warn" 104 + }, 105 + "adultOnly": { 106 + "type": "boolean", 107 + "description": "Does the user need to have adult content enabled in order to configure this label?" 108 + }, 109 + "locales": { 110 + "type": "array", 111 + "items": { "type": "ref", "ref": "#labelValueDefinitionStrings" } 112 + } 113 + } 114 + }, 115 + "labelValueDefinitionStrings": { 116 + "type": "object", 117 + "description": "Strings which describe the label in the UI, localized into a specific language.", 118 + "required": ["lang", "name", "description"], 119 + "properties": { 120 + "lang": { 121 + "type": "string", 122 + "description": "The code of the language these strings are written in.", 123 + "format": "language" 124 + }, 125 + "name": { 126 + "type": "string", 127 + "description": "A short human-readable name for the label.", 128 + "maxGraphemes": 64, 129 + "maxLength": 640 130 + }, 131 + "description": { 132 + "type": "string", 133 + "description": "A longer description of what the label means and why it might be applied.", 134 + "maxGraphemes": 10000, 135 + "maxLength": 100000 136 + } 137 + } 138 + }, 139 + "labelValue": { 140 + "type": "string", 141 + "knownValues": [ 142 + "!hide", 143 + "!no-promote", 144 + "!warn", 145 + "!no-unauthenticated", 146 + "dmca-violation", 147 + "doxxing", 148 + "porn", 149 + "sexual", 150 + "nudity", 151 + "nsfl", 152 + "gore" 153 + ] 154 + } 155 + } 156 + }
+45
lexicons/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "org.atmosphereconf.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A conference attendee profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "properties": { 12 + "displayName": { 13 + "type": "string", 14 + "maxGraphemes": 64, 15 + "maxLength": 640 16 + }, 17 + "description": { 18 + "type": "string", 19 + "description": "Free-form profile description text.", 20 + "maxGraphemes": 256, 21 + "maxLength": 2560 22 + }, 23 + "avatar": { 24 + "type": "blob", 25 + "description": "Profile picture for conference attendee", 26 + "accept": ["image/png", "image/jpeg"], 27 + "maxSize": 1000000 28 + }, 29 + "banner": { 30 + "type": "blob", 31 + "description": "Larger horizontal image to display behind profile view.", 32 + "accept": ["image/png", "image/jpeg"], 33 + "maxSize": 1000000 34 + }, 35 + "labels": { 36 + "type": "union", 37 + "description": "Self-label values for the conference profile.", 38 + "refs": ["com.atproto.label.defs#selfLabels"] 39 + }, 40 + "createdAt": { "type": "string", "format": "datetime" } 41 + } 42 + } 43 + } 44 + } 45 + }
+15
lexicons/strongRef.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "com.atproto.repo.strongRef", 4 + "description": "A URI with a content-hash fingerprint.", 5 + "defs": { 6 + "main": { 7 + "type": "object", 8 + "required": ["uri", "cid"], 9 + "properties": { 10 + "uri": { "type": "string", "format": "at-uri" }, 11 + "cid": { "type": "string", "format": "cid" } 12 + } 13 + } 14 + } 15 + }
+344 -303
package-lock.json
··· 9 9 "version": "0.0.1", 10 10 "dependencies": { 11 11 "@astrojs/node": "^9.5.0", 12 - "@astrojs/tailwind": "^6.0.2", 13 12 "@atproto/api": "^0.17.4", 14 13 "@atproto/oauth-client-node": "^0.3.10", 15 14 "@tailwindcss/vite": "^4.1.16", ··· 17 16 "daisyui": "^5.3.9", 18 17 "dotenv": "^17.2.3", 19 18 "tailwindcss": "^4.1.16" 19 + }, 20 + "devDependencies": { 21 + "@atproto/lex-cli": "^0.9.6" 20 22 } 21 23 }, 22 24 "node_modules/@astrojs/compiler": { ··· 84 86 }, 85 87 "engines": { 86 88 "node": "18.20.8 || ^20.3.0 || >=22.0.0" 87 - } 88 - }, 89 - "node_modules/@astrojs/tailwind": { 90 - "version": "6.0.2", 91 - "resolved": "https://registry.npmjs.org/@astrojs/tailwind/-/tailwind-6.0.2.tgz", 92 - "integrity": "sha512-j3mhLNeugZq6A8dMNXVarUa8K6X9AW+QHU9u3lKNrPLMHhOQ0S7VeWhHwEeJFpEK1BTKEUY1U78VQv2gN6hNGg==", 93 - "license": "MIT", 94 - "dependencies": { 95 - "autoprefixer": "^10.4.21", 96 - "postcss": "^8.5.3", 97 - "postcss-load-config": "^4.0.2" 98 - }, 99 - "peerDependencies": { 100 - "astro": "^3.0.0 || ^4.0.0 || ^5.0.0", 101 - "tailwindcss": "^3.0.24" 102 89 } 103 90 }, 104 91 "node_modules/@astrojs/telemetry": { ··· 216 203 } 217 204 }, 218 205 "node_modules/@atproto/api": { 219 - "version": "0.17.4", 220 - "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.17.4.tgz", 221 - "integrity": "sha512-MRa0WdxyDiGF7fVKd/2ldvonsHQjsaLUOGw/PHrZ7J01lqlw/jaXLS25FNNYzjPGmGpnIyDCIg4Uucd/OblI9w==", 206 + "version": "0.17.6", 207 + "resolved": "https://registry.npmjs.org/@atproto/api/-/api-0.17.6.tgz", 208 + "integrity": "sha512-0iYCD8+LOsHjHjwJcqGPfJN/h4b+IpU3GjOV0TSLk0XdCaxpHBKNu3wgCJVst4DhVjXcgsr2qQoRZ3Jja2LupA==", 222 209 "license": "MIT", 223 210 "dependencies": { 224 211 "@atproto/common-web": "^0.4.3", ··· 281 268 "@atproto/jwk": "0.6.0", 282 269 "@atproto/jwk-jose": "0.1.11", 283 270 "zod": "^3.23.8" 271 + } 272 + }, 273 + "node_modules/@atproto/lex-cli": { 274 + "version": "0.9.6", 275 + "resolved": "https://registry.npmjs.org/@atproto/lex-cli/-/lex-cli-0.9.6.tgz", 276 + "integrity": "sha512-EedEKmURoSP735YwSDHsFrLOhZ4P2it8goCHv5ApWi/R9DFpOKOpmYfIXJ9MAprK8cw+yBnjDJbzpLJy7UXlTg==", 277 + "dev": true, 278 + "license": "MIT", 279 + "dependencies": { 280 + "@atproto/lexicon": "^0.5.1", 281 + "@atproto/syntax": "^0.4.1", 282 + "chalk": "^4.1.2", 283 + "commander": "^9.4.0", 284 + "prettier": "^3.2.5", 285 + "ts-morph": "^24.0.0", 286 + "yesno": "^0.4.0", 287 + "zod": "^3.23.8" 288 + }, 289 + "bin": { 290 + "lex": "dist/index.js" 291 + }, 292 + "engines": { 293 + "node": ">=18.7.0" 294 + } 295 + }, 296 + "node_modules/@atproto/lex-cli/node_modules/ansi-styles": { 297 + "version": "4.3.0", 298 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", 299 + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 300 + "dev": true, 301 + "license": "MIT", 302 + "dependencies": { 303 + "color-convert": "^2.0.1" 304 + }, 305 + "engines": { 306 + "node": ">=8" 307 + }, 308 + "funding": { 309 + "url": "https://github.com/chalk/ansi-styles?sponsor=1" 310 + } 311 + }, 312 + "node_modules/@atproto/lex-cli/node_modules/chalk": { 313 + "version": "4.1.2", 314 + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", 315 + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", 316 + "dev": true, 317 + "license": "MIT", 318 + "dependencies": { 319 + "ansi-styles": "^4.1.0", 320 + "supports-color": "^7.1.0" 321 + }, 322 + "engines": { 323 + "node": ">=10" 324 + }, 325 + "funding": { 326 + "url": "https://github.com/chalk/chalk?sponsor=1" 284 327 } 285 328 }, 286 329 "node_modules/@atproto/lexicon": { ··· 1642 1685 ] 1643 1686 }, 1644 1687 "node_modules/@shikijs/core": { 1645 - "version": "3.13.0", 1646 - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.13.0.tgz", 1647 - "integrity": "sha512-3P8rGsg2Eh2qIHekwuQjzWhKI4jV97PhvYjYUzGqjvJfqdQPz+nMlfWahU24GZAyW1FxFI1sYjyhfh5CoLmIUA==", 1688 + "version": "3.14.0", 1689 + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-3.14.0.tgz", 1690 + "integrity": "sha512-qRSeuP5vlYHCNUIrpEBQFO7vSkR7jn7Kv+5X3FO/zBKVDGQbcnlScD3XhkrHi/R8Ltz0kEjvFR9Szp/XMRbFMw==", 1648 1691 "license": "MIT", 1649 1692 "dependencies": { 1650 - "@shikijs/types": "3.13.0", 1693 + "@shikijs/types": "3.14.0", 1651 1694 "@shikijs/vscode-textmate": "^10.0.2", 1652 1695 "@types/hast": "^3.0.4", 1653 1696 "hast-util-to-html": "^9.0.5" 1654 1697 } 1655 1698 }, 1656 1699 "node_modules/@shikijs/engine-javascript": { 1657 - "version": "3.13.0", 1658 - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.13.0.tgz", 1659 - "integrity": "sha512-Ty7xv32XCp8u0eQt8rItpMs6rU9Ki6LJ1dQOW3V/56PKDcpvfHPnYFbsx5FFUP2Yim34m/UkazidamMNVR4vKg==", 1700 + "version": "3.14.0", 1701 + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-3.14.0.tgz", 1702 + "integrity": "sha512-3v1kAXI2TsWQuwv86cREH/+FK9Pjw3dorVEykzQDhwrZj0lwsHYlfyARaKmn6vr5Gasf8aeVpb8JkzeWspxOLQ==", 1660 1703 "license": "MIT", 1661 1704 "dependencies": { 1662 - "@shikijs/types": "3.13.0", 1705 + "@shikijs/types": "3.14.0", 1663 1706 "@shikijs/vscode-textmate": "^10.0.2", 1664 1707 "oniguruma-to-es": "^4.3.3" 1665 1708 } 1666 1709 }, 1667 1710 "node_modules/@shikijs/engine-oniguruma": { 1668 - "version": "3.13.0", 1669 - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.13.0.tgz", 1670 - "integrity": "sha512-O42rBGr4UDSlhT2ZFMxqM7QzIU+IcpoTMzb3W7AlziI1ZF7R8eS2M0yt5Ry35nnnTX/LTLXFPUjRFCIW+Operg==", 1711 + "version": "3.14.0", 1712 + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-3.14.0.tgz", 1713 + "integrity": "sha512-TNcYTYMbJyy+ZjzWtt0bG5y4YyMIWC2nyePz+CFMWqm+HnZZyy9SWMgo8Z6KBJVIZnx8XUXS8U2afO6Y0g1Oug==", 1671 1714 "license": "MIT", 1672 1715 "dependencies": { 1673 - "@shikijs/types": "3.13.0", 1716 + "@shikijs/types": "3.14.0", 1674 1717 "@shikijs/vscode-textmate": "^10.0.2" 1675 1718 } 1676 1719 }, 1677 1720 "node_modules/@shikijs/langs": { 1678 - "version": "3.13.0", 1679 - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.13.0.tgz", 1680 - "integrity": "sha512-672c3WAETDYHwrRP0yLy3W1QYB89Hbpj+pO4KhxK6FzIrDI2FoEXNiNCut6BQmEApYLfuYfpgOZaqbY+E9b8wQ==", 1721 + "version": "3.14.0", 1722 + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-3.14.0.tgz", 1723 + "integrity": "sha512-DIB2EQY7yPX1/ZH7lMcwrK5pl+ZkP/xoSpUzg9YC8R+evRCCiSQ7yyrvEyBsMnfZq4eBzLzBlugMyTAf13+pzg==", 1681 1724 "license": "MIT", 1682 1725 "dependencies": { 1683 - "@shikijs/types": "3.13.0" 1726 + "@shikijs/types": "3.14.0" 1684 1727 } 1685 1728 }, 1686 1729 "node_modules/@shikijs/themes": { 1687 - "version": "3.13.0", 1688 - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.13.0.tgz", 1689 - "integrity": "sha512-Vxw1Nm1/Od8jyA7QuAenaV78BG2nSr3/gCGdBkLpfLscddCkzkL36Q5b67SrLLfvAJTOUzW39x4FHVCFriPVgg==", 1730 + "version": "3.14.0", 1731 + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-3.14.0.tgz", 1732 + "integrity": "sha512-fAo/OnfWckNmv4uBoUu6dSlkcBc+SA1xzj5oUSaz5z3KqHtEbUypg/9xxgJARtM6+7RVm0Q6Xnty41xA1ma1IA==", 1690 1733 "license": "MIT", 1691 1734 "dependencies": { 1692 - "@shikijs/types": "3.13.0" 1735 + "@shikijs/types": "3.14.0" 1693 1736 } 1694 1737 }, 1695 1738 "node_modules/@shikijs/types": { 1696 - "version": "3.13.0", 1697 - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.13.0.tgz", 1698 - "integrity": "sha512-oM9P+NCFri/mmQ8LoFGVfVyemm5Hi27330zuOBp0annwJdKH1kOLndw3zCtAVDehPLg9fKqoEx3Ht/wNZxolfw==", 1739 + "version": "3.14.0", 1740 + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-3.14.0.tgz", 1741 + "integrity": "sha512-bQGgC6vrY8U/9ObG1Z/vTro+uclbjjD/uG58RvfxKZVD5p9Yc1ka3tVyEFy7BNJLzxuWyHH5NWynP9zZZS59eQ==", 1699 1742 "license": "MIT", 1700 1743 "dependencies": { 1701 1744 "@shikijs/vscode-textmate": "^10.0.2", ··· 1974 2017 "vite": "^5.2.0 || ^6 || ^7" 1975 2018 } 1976 2019 }, 2020 + "node_modules/@ts-morph/common": { 2021 + "version": "0.25.0", 2022 + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.25.0.tgz", 2023 + "integrity": "sha512-kMnZz+vGGHi4GoHnLmMhGNjm44kGtKUXGnOvrKmMwAuvNjM/PgKVGfUnL7IDvK7Jb2QQ82jq3Zmp04Gy+r3Dkg==", 2024 + "dev": true, 2025 + "license": "MIT", 2026 + "dependencies": { 2027 + "minimatch": "^9.0.4", 2028 + "path-browserify": "^1.0.1", 2029 + "tinyglobby": "^0.2.9" 2030 + } 2031 + }, 1977 2032 "node_modules/@types/debug": { 1978 2033 "version": "4.1.12", 1979 2034 "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", ··· 2032 2087 } 2033 2088 }, 2034 2089 "node_modules/@types/node": { 2035 - "version": "24.9.1", 2036 - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.1.tgz", 2037 - "integrity": "sha512-QoiaXANRkSXK6p0Duvt56W208du4P9Uye9hWLWgGMDTEoKPhuenzNcC4vGUmrNkiOKTlIrBoyNQYNpSwfEZXSg==", 2090 + "version": "24.9.2", 2091 + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", 2092 + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", 2038 2093 "license": "MIT", 2039 2094 "dependencies": { 2040 2095 "undici-types": "~7.16.0" ··· 2189 2244 } 2190 2245 }, 2191 2246 "node_modules/astro": { 2192 - "version": "5.15.1", 2193 - "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.1.tgz", 2194 - "integrity": "sha512-VM679M1qxOjGo6q3vKYDNDddkALGgMopG93IwbEXd3Buc2xVLuuPj4HNziNugSbPQx5S6UReMp5uzw10EJN81A==", 2247 + "version": "5.15.2", 2248 + "resolved": "https://registry.npmjs.org/astro/-/astro-5.15.2.tgz", 2249 + "integrity": "sha512-xQQ+PiYJ7WpUJrHJpAb52TQAUCFmSR8lAtQr3tFfSIZoTQiEMFx3HITJ01t3eDUpHjja8J6JcYqgAhr9xygKQg==", 2195 2250 "license": "MIT", 2196 2251 "dependencies": { 2197 2252 "@astrojs/compiler": "^2.12.2", ··· 2248 2303 "unist-util-visit": "^5.0.0", 2249 2304 "unstorage": "^1.17.0", 2250 2305 "vfile": "^6.0.3", 2251 - "vite": "^6.3.6", 2306 + "vite": "^6.4.1", 2252 2307 "vitefu": "^1.1.1", 2253 2308 "xxhash-wasm": "^1.1.0", 2254 2309 "yargs-parser": "^21.1.1", ··· 2273 2328 "sharp": "^0.34.0" 2274 2329 } 2275 2330 }, 2276 - "node_modules/autoprefixer": { 2277 - "version": "10.4.21", 2278 - "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", 2279 - "integrity": "sha512-O+A6LWV5LDHSJD3LjHYoNi4VLsj/Whi7k6zG12xTYaU4cQ8oxQGckXNX8cRHK5yOZ/ppVHe0ZBXGzSV9jXdVbQ==", 2280 - "funding": [ 2281 - { 2282 - "type": "opencollective", 2283 - "url": "https://opencollective.com/postcss/" 2284 - }, 2285 - { 2286 - "type": "tidelift", 2287 - "url": "https://tidelift.com/funding/github/npm/autoprefixer" 2288 - }, 2289 - { 2290 - "type": "github", 2291 - "url": "https://github.com/sponsors/ai" 2292 - } 2293 - ], 2331 + "node_modules/astro/node_modules/vite": { 2332 + "version": "6.4.1", 2333 + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", 2334 + "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", 2294 2335 "license": "MIT", 2295 2336 "dependencies": { 2296 - "browserslist": "^4.24.4", 2297 - "caniuse-lite": "^1.0.30001702", 2298 - "fraction.js": "^4.3.7", 2299 - "normalize-range": "^0.1.2", 2300 - "picocolors": "^1.1.1", 2301 - "postcss-value-parser": "^4.2.0" 2337 + "esbuild": "^0.25.0", 2338 + "fdir": "^6.4.4", 2339 + "picomatch": "^4.0.2", 2340 + "postcss": "^8.5.3", 2341 + "rollup": "^4.34.9", 2342 + "tinyglobby": "^0.2.13" 2302 2343 }, 2303 2344 "bin": { 2304 - "autoprefixer": "bin/autoprefixer" 2345 + "vite": "bin/vite.js" 2305 2346 }, 2306 2347 "engines": { 2307 - "node": "^10 || ^12 || >=14" 2348 + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 2349 + }, 2350 + "funding": { 2351 + "url": "https://github.com/vitejs/vite?sponsor=1" 2352 + }, 2353 + "optionalDependencies": { 2354 + "fsevents": "~2.3.3" 2308 2355 }, 2309 2356 "peerDependencies": { 2310 - "postcss": "^8.1.0" 2357 + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 2358 + "jiti": ">=1.21.0", 2359 + "less": "*", 2360 + "lightningcss": "^1.21.0", 2361 + "sass": "*", 2362 + "sass-embedded": "*", 2363 + "stylus": "*", 2364 + "sugarss": "*", 2365 + "terser": "^5.16.0", 2366 + "tsx": "^4.8.1", 2367 + "yaml": "^2.4.2" 2368 + }, 2369 + "peerDependenciesMeta": { 2370 + "@types/node": { 2371 + "optional": true 2372 + }, 2373 + "jiti": { 2374 + "optional": true 2375 + }, 2376 + "less": { 2377 + "optional": true 2378 + }, 2379 + "lightningcss": { 2380 + "optional": true 2381 + }, 2382 + "sass": { 2383 + "optional": true 2384 + }, 2385 + "sass-embedded": { 2386 + "optional": true 2387 + }, 2388 + "stylus": { 2389 + "optional": true 2390 + }, 2391 + "sugarss": { 2392 + "optional": true 2393 + }, 2394 + "terser": { 2395 + "optional": true 2396 + }, 2397 + "tsx": { 2398 + "optional": true 2399 + }, 2400 + "yaml": { 2401 + "optional": true 2402 + } 2311 2403 } 2312 2404 }, 2313 2405 "node_modules/await-lock": { ··· 2335 2427 "url": "https://github.com/sponsors/wooorm" 2336 2428 } 2337 2429 }, 2430 + "node_modules/balanced-match": { 2431 + "version": "1.0.2", 2432 + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", 2433 + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", 2434 + "dev": true, 2435 + "license": "MIT" 2436 + }, 2338 2437 "node_modules/base-64": { 2339 2438 "version": "1.0.0", 2340 2439 "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz", ··· 2361 2460 ], 2362 2461 "license": "MIT" 2363 2462 }, 2364 - "node_modules/baseline-browser-mapping": { 2365 - "version": "2.8.20", 2366 - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.20.tgz", 2367 - "integrity": "sha512-JMWsdF+O8Orq3EMukbUN1QfbLK9mX2CkUmQBcW2T0s8OmdAUL5LLM/6wFwSrqXzlXB13yhyK9gTKS1rIizOduQ==", 2368 - "license": "Apache-2.0", 2369 - "bin": { 2370 - "baseline-browser-mapping": "dist/cli.js" 2371 - } 2372 - }, 2373 2463 "node_modules/boxen": { 2374 2464 "version": "8.0.1", 2375 2465 "resolved": "https://registry.npmjs.org/boxen/-/boxen-8.0.1.tgz", ··· 2392 2482 "url": "https://github.com/sponsors/sindresorhus" 2393 2483 } 2394 2484 }, 2485 + "node_modules/brace-expansion": { 2486 + "version": "2.0.2", 2487 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", 2488 + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", 2489 + "dev": true, 2490 + "license": "MIT", 2491 + "dependencies": { 2492 + "balanced-match": "^1.0.0" 2493 + } 2494 + }, 2395 2495 "node_modules/brotli": { 2396 2496 "version": "1.3.3", 2397 2497 "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", ··· 2401 2501 "base64-js": "^1.1.2" 2402 2502 } 2403 2503 }, 2404 - "node_modules/browserslist": { 2405 - "version": "4.27.0", 2406 - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.27.0.tgz", 2407 - "integrity": "sha512-AXVQwdhot1eqLihwasPElhX2tAZiBjWdJ9i/Zcj2S6QYIjkx62OKSfnobkriB81C3l4w0rVy3Nt4jaTBltYEpw==", 2408 - "funding": [ 2409 - { 2410 - "type": "opencollective", 2411 - "url": "https://opencollective.com/browserslist" 2412 - }, 2413 - { 2414 - "type": "tidelift", 2415 - "url": "https://tidelift.com/funding/github/npm/browserslist" 2416 - }, 2417 - { 2418 - "type": "github", 2419 - "url": "https://github.com/sponsors/ai" 2420 - } 2421 - ], 2422 - "license": "MIT", 2423 - "dependencies": { 2424 - "baseline-browser-mapping": "^2.8.19", 2425 - "caniuse-lite": "^1.0.30001751", 2426 - "electron-to-chromium": "^1.5.238", 2427 - "node-releases": "^2.0.26", 2428 - "update-browserslist-db": "^1.1.4" 2429 - }, 2430 - "bin": { 2431 - "browserslist": "cli.js" 2432 - }, 2433 - "engines": { 2434 - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" 2435 - } 2436 - }, 2437 2504 "node_modules/camelcase": { 2438 2505 "version": "8.0.0", 2439 2506 "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-8.0.0.tgz", ··· 2445 2512 "funding": { 2446 2513 "url": "https://github.com/sponsors/sindresorhus" 2447 2514 } 2448 - }, 2449 - "node_modules/caniuse-lite": { 2450 - "version": "1.0.30001751", 2451 - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001751.tgz", 2452 - "integrity": "sha512-A0QJhug0Ly64Ii3eIqHu5X51ebln3k4yTUkY1j8drqpWHVreg/VLijN48cZ1bYPiqOQuqpkIKnzr/Ul8V+p6Cw==", 2453 - "funding": [ 2454 - { 2455 - "type": "opencollective", 2456 - "url": "https://opencollective.com/browserslist" 2457 - }, 2458 - { 2459 - "type": "tidelift", 2460 - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" 2461 - }, 2462 - { 2463 - "type": "github", 2464 - "url": "https://github.com/sponsors/ai" 2465 - } 2466 - ], 2467 - "license": "CC-BY-4.0" 2468 2515 }, 2469 2516 "node_modules/ccount": { 2470 2517 "version": "2.0.1", ··· 2578 2625 "node": ">=6" 2579 2626 } 2580 2627 }, 2628 + "node_modules/code-block-writer": { 2629 + "version": "13.0.3", 2630 + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-13.0.3.tgz", 2631 + "integrity": "sha512-Oofo0pq3IKnsFtuHqSF7TqBfr71aeyZDVJ0HpmqB7FBM2qEigL0iPONSCZSO9pE9dZTAxANe5XHG9Uy0YMv8cg==", 2632 + "dev": true, 2633 + "license": "MIT" 2634 + }, 2635 + "node_modules/color-convert": { 2636 + "version": "2.0.1", 2637 + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", 2638 + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", 2639 + "dev": true, 2640 + "license": "MIT", 2641 + "dependencies": { 2642 + "color-name": "~1.1.4" 2643 + }, 2644 + "engines": { 2645 + "node": ">=7.0.0" 2646 + } 2647 + }, 2648 + "node_modules/color-name": { 2649 + "version": "1.1.4", 2650 + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", 2651 + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", 2652 + "dev": true, 2653 + "license": "MIT" 2654 + }, 2581 2655 "node_modules/comma-separated-tokens": { 2582 2656 "version": "2.0.3", 2583 2657 "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", ··· 2586 2660 "funding": { 2587 2661 "type": "github", 2588 2662 "url": "https://github.com/sponsors/wooorm" 2663 + } 2664 + }, 2665 + "node_modules/commander": { 2666 + "version": "9.5.0", 2667 + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", 2668 + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", 2669 + "dev": true, 2670 + "license": "MIT", 2671 + "engines": { 2672 + "node": "^12.20.0 || >=14" 2589 2673 } 2590 2674 }, 2591 2675 "node_modules/common-ancestor-path": { ··· 2655 2739 } 2656 2740 }, 2657 2741 "node_modules/daisyui": { 2658 - "version": "5.3.9", 2659 - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.3.9.tgz", 2660 - "integrity": "sha512-741x1pGGSGHcrBYtdE7iKbqW1OoiijYdAZ8oJPZR9MhSKLcMBlHjKfN3YlM2/K7t5jd7O0sg4SqkVNPylalRFw==", 2742 + "version": "5.3.10", 2743 + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-5.3.10.tgz", 2744 + "integrity": "sha512-vmjyPmm0hvFhA95KB6uiGmWakziB2pBv6CUcs5Ka/3iMBMn9S+C3SZYx9G9l2JrgTZ1EFn61F/HrPcwaUm2kLQ==", 2661 2745 "license": "MIT", 2662 2746 "funding": { 2663 2747 "url": "https://github.com/saadeghi/daisyui?sponsor=1" ··· 2811 2895 "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", 2812 2896 "license": "MIT" 2813 2897 }, 2814 - "node_modules/electron-to-chromium": { 2815 - "version": "1.5.240", 2816 - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.240.tgz", 2817 - "integrity": "sha512-OBwbZjWgrCOH+g6uJsA2/7Twpas2OlepS9uvByJjR2datRDuKGYeD+nP8lBBks2qnB7bGJNHDUx7c/YLaT3QMQ==", 2818 - "license": "ISC" 2819 - }, 2820 2898 "node_modules/emoji-regex": { 2821 2899 "version": "10.6.0", 2822 2900 "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", ··· 2904 2982 "@esbuild/win32-x64": "0.25.11" 2905 2983 } 2906 2984 }, 2907 - "node_modules/escalade": { 2908 - "version": "3.2.0", 2909 - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", 2910 - "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", 2911 - "license": "MIT", 2912 - "engines": { 2913 - "node": ">=6" 2914 - } 2915 - }, 2916 2985 "node_modules/escape-html": { 2917 2986 "version": "1.0.3", 2918 2987 "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", ··· 3020 3089 "unicode-trie": "^2.0.0" 3021 3090 } 3022 3091 }, 3023 - "node_modules/fraction.js": { 3024 - "version": "4.3.7", 3025 - "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", 3026 - "integrity": "sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==", 3027 - "license": "MIT", 3028 - "engines": { 3029 - "node": "*" 3030 - }, 3031 - "funding": { 3032 - "type": "patreon", 3033 - "url": "https://github.com/sponsors/rawify" 3034 - } 3035 - }, 3036 3092 "node_modules/fresh": { 3037 3093 "version": "2.0.0", 3038 3094 "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", ··· 3101 3157 "radix3": "^1.1.2", 3102 3158 "ufo": "^1.6.1", 3103 3159 "uncrypto": "^0.1.3" 3160 + } 3161 + }, 3162 + "node_modules/has-flag": { 3163 + "version": "4.0.0", 3164 + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", 3165 + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", 3166 + "dev": true, 3167 + "license": "MIT", 3168 + "engines": { 3169 + "node": ">=8" 3104 3170 } 3105 3171 }, 3106 3172 "node_modules/hast-util-from-html": { ··· 3734 3800 "url": "https://opencollective.com/parcel" 3735 3801 } 3736 3802 }, 3737 - "node_modules/lilconfig": { 3738 - "version": "3.1.3", 3739 - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", 3740 - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", 3741 - "license": "MIT", 3742 - "engines": { 3743 - "node": ">=14" 3744 - }, 3745 - "funding": { 3746 - "url": "https://github.com/sponsors/antonk52" 3747 - } 3748 - }, 3749 3803 "node_modules/longest-streak": { 3750 3804 "version": "3.1.0", 3751 3805 "resolved": "https://registry.npmjs.org/longest-streak/-/longest-streak-3.1.0.tgz", ··· 4607 4661 "node": ">= 0.6" 4608 4662 } 4609 4663 }, 4664 + "node_modules/minimatch": { 4665 + "version": "9.0.5", 4666 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 4667 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 4668 + "dev": true, 4669 + "license": "ISC", 4670 + "dependencies": { 4671 + "brace-expansion": "^2.0.1" 4672 + }, 4673 + "engines": { 4674 + "node": ">=16 || 14 >=14.17" 4675 + }, 4676 + "funding": { 4677 + "url": "https://github.com/sponsors/isaacs" 4678 + } 4679 + }, 4610 4680 "node_modules/mrmime": { 4611 4681 "version": "2.0.1", 4612 4682 "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-2.0.1.tgz", ··· 4680 4750 "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", 4681 4751 "license": "MIT" 4682 4752 }, 4683 - "node_modules/node-releases": { 4684 - "version": "2.0.26", 4685 - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.26.tgz", 4686 - "integrity": "sha512-S2M9YimhSjBSvYnlr5/+umAnPHE++ODwt5e2Ij6FoX45HA/s4vHdkDx1eax2pAPeAOqu4s9b7ppahsyEFdVqQA==", 4687 - "license": "MIT" 4688 - }, 4689 4753 "node_modules/normalize-path": { 4690 4754 "version": "3.0.0", 4691 4755 "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", ··· 4695 4759 "node": ">=0.10.0" 4696 4760 } 4697 4761 }, 4698 - "node_modules/normalize-range": { 4699 - "version": "0.1.2", 4700 - "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", 4701 - "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", 4702 - "license": "MIT", 4703 - "engines": { 4704 - "node": ">=0.10.0" 4705 - } 4706 - }, 4707 4762 "node_modules/ofetch": { 4708 - "version": "1.4.1", 4709 - "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", 4710 - "integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==", 4763 + "version": "1.5.0", 4764 + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.0.tgz", 4765 + "integrity": "sha512-A7llJ7eZyziA5xq9//3ZurA8OhFqtS99K5/V1sLBJ5j137CM/OAjlbA/TEJXBuOWwOfLqih+oH5U3ran4za1FQ==", 4711 4766 "license": "MIT", 4712 4767 "dependencies": { 4713 - "destr": "^2.0.3", 4714 - "node-fetch-native": "^1.6.4", 4715 - "ufo": "^1.5.4" 4768 + "destr": "^2.0.5", 4769 + "node-fetch-native": "^1.6.7", 4770 + "ufo": "^1.6.1" 4716 4771 } 4717 4772 }, 4718 4773 "node_modules/ohash": { ··· 4835 4890 "url": "https://github.com/inikulin/parse5?sponsor=1" 4836 4891 } 4837 4892 }, 4893 + "node_modules/path-browserify": { 4894 + "version": "1.0.1", 4895 + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", 4896 + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==", 4897 + "dev": true, 4898 + "license": "MIT" 4899 + }, 4838 4900 "node_modules/picocolors": { 4839 4901 "version": "1.1.1", 4840 4902 "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", ··· 4881 4943 "node": "^10 || ^12 || >=14" 4882 4944 } 4883 4945 }, 4884 - "node_modules/postcss-load-config": { 4885 - "version": "4.0.2", 4886 - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", 4887 - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", 4888 - "funding": [ 4889 - { 4890 - "type": "opencollective", 4891 - "url": "https://opencollective.com/postcss/" 4892 - }, 4893 - { 4894 - "type": "github", 4895 - "url": "https://github.com/sponsors/ai" 4896 - } 4897 - ], 4946 + "node_modules/prettier": { 4947 + "version": "3.6.2", 4948 + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz", 4949 + "integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==", 4950 + "dev": true, 4898 4951 "license": "MIT", 4899 - "dependencies": { 4900 - "lilconfig": "^3.0.0", 4901 - "yaml": "^2.3.4" 4952 + "bin": { 4953 + "prettier": "bin/prettier.cjs" 4902 4954 }, 4903 4955 "engines": { 4904 - "node": ">= 14" 4956 + "node": ">=14" 4905 4957 }, 4906 - "peerDependencies": { 4907 - "postcss": ">=8.0.9", 4908 - "ts-node": ">=9.0.0" 4909 - }, 4910 - "peerDependenciesMeta": { 4911 - "postcss": { 4912 - "optional": true 4913 - }, 4914 - "ts-node": { 4915 - "optional": true 4916 - } 4958 + "funding": { 4959 + "url": "https://github.com/prettier/prettier?sponsor=1" 4917 4960 } 4918 - }, 4919 - "node_modules/postcss-value-parser": { 4920 - "version": "4.2.0", 4921 - "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", 4922 - "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", 4923 - "license": "MIT" 4924 4961 }, 4925 4962 "node_modules/prismjs": { 4926 4963 "version": "1.30.0", ··· 5346 5383 } 5347 5384 }, 5348 5385 "node_modules/shiki": { 5349 - "version": "3.13.0", 5350 - "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.13.0.tgz", 5351 - "integrity": "sha512-aZW4l8Og16CokuCLf8CF8kq+KK2yOygapU5m3+hoGw0Mdosc6fPitjM+ujYarppj5ZIKGyPDPP1vqmQhr+5/0g==", 5386 + "version": "3.14.0", 5387 + "resolved": "https://registry.npmjs.org/shiki/-/shiki-3.14.0.tgz", 5388 + "integrity": "sha512-J0yvpLI7LSig3Z3acIuDLouV5UCKQqu8qOArwMx+/yPVC3WRMgrP67beaG8F+j4xfEWE0eVC4GeBCIXeOPra1g==", 5352 5389 "license": "MIT", 5353 5390 "dependencies": { 5354 - "@shikijs/core": "3.13.0", 5355 - "@shikijs/engine-javascript": "3.13.0", 5356 - "@shikijs/engine-oniguruma": "3.13.0", 5357 - "@shikijs/langs": "3.13.0", 5358 - "@shikijs/themes": "3.13.0", 5359 - "@shikijs/types": "3.13.0", 5391 + "@shikijs/core": "3.14.0", 5392 + "@shikijs/engine-javascript": "3.14.0", 5393 + "@shikijs/engine-oniguruma": "3.14.0", 5394 + "@shikijs/langs": "3.14.0", 5395 + "@shikijs/themes": "3.14.0", 5396 + "@shikijs/types": "3.14.0", 5360 5397 "@shikijs/vscode-textmate": "^10.0.2", 5361 5398 "@types/hast": "^3.0.4" 5362 5399 } ··· 5453 5490 "url": "https://github.com/chalk/strip-ansi?sponsor=1" 5454 5491 } 5455 5492 }, 5493 + "node_modules/supports-color": { 5494 + "version": "7.2.0", 5495 + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", 5496 + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", 5497 + "dev": true, 5498 + "license": "MIT", 5499 + "dependencies": { 5500 + "has-flag": "^4.0.0" 5501 + }, 5502 + "engines": { 5503 + "node": ">=8" 5504 + } 5505 + }, 5456 5506 "node_modules/tailwindcss": { 5457 5507 "version": "4.1.16", 5458 5508 "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.16.tgz", ··· 5538 5588 "url": "https://github.com/sponsors/wooorm" 5539 5589 } 5540 5590 }, 5591 + "node_modules/ts-morph": { 5592 + "version": "24.0.0", 5593 + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-24.0.0.tgz", 5594 + "integrity": "sha512-2OAOg/Ob5yx9Et7ZX4CvTCc0UFoZHwLEJ+dpDPSUi5TgwwlTlX47w+iFRrEwzUZwYACjq83cgjS/Da50Ga37uw==", 5595 + "dev": true, 5596 + "license": "MIT", 5597 + "dependencies": { 5598 + "@ts-morph/common": "~0.25.0", 5599 + "code-block-writer": "^13.0.3" 5600 + } 5601 + }, 5541 5602 "node_modules/tsconfck": { 5542 5603 "version": "3.1.6", 5543 5604 "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.6.tgz", ··· 5574 5635 }, 5575 5636 "funding": { 5576 5637 "url": "https://github.com/sponsors/sindresorhus" 5638 + } 5639 + }, 5640 + "node_modules/typescript": { 5641 + "version": "5.9.3", 5642 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 5643 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 5644 + "license": "Apache-2.0", 5645 + "peer": true, 5646 + "bin": { 5647 + "tsc": "bin/tsc", 5648 + "tsserver": "bin/tsserver" 5649 + }, 5650 + "engines": { 5651 + "node": ">=14.17" 5577 5652 } 5578 5653 }, 5579 5654 "node_modules/ufo": { ··· 5887 5962 } 5888 5963 } 5889 5964 }, 5890 - "node_modules/update-browserslist-db": { 5891 - "version": "1.1.4", 5892 - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.4.tgz", 5893 - "integrity": "sha512-q0SPT4xyU84saUX+tomz1WLkxUbuaJnR1xWt17M7fJtEJigJeWUNGUqrauFXsHnqev9y9JTRGwk13tFBuKby4A==", 5894 - "funding": [ 5895 - { 5896 - "type": "opencollective", 5897 - "url": "https://opencollective.com/browserslist" 5898 - }, 5899 - { 5900 - "type": "tidelift", 5901 - "url": "https://tidelift.com/funding/github/npm/browserslist" 5902 - }, 5903 - { 5904 - "type": "github", 5905 - "url": "https://github.com/sponsors/ai" 5906 - } 5907 - ], 5908 - "license": "MIT", 5909 - "dependencies": { 5910 - "escalade": "^3.2.0", 5911 - "picocolors": "^1.1.1" 5912 - }, 5913 - "bin": { 5914 - "update-browserslist-db": "cli.js" 5915 - }, 5916 - "peerDependencies": { 5917 - "browserslist": ">= 4.21.0" 5918 - } 5919 - }, 5920 5965 "node_modules/vfile": { 5921 5966 "version": "6.0.3", 5922 5967 "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", ··· 5960 6005 } 5961 6006 }, 5962 6007 "node_modules/vite": { 5963 - "version": "6.4.1", 5964 - "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.1.tgz", 5965 - "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", 6008 + "version": "7.1.12", 6009 + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.12.tgz", 6010 + "integrity": "sha512-ZWyE8YXEXqJrrSLvYgrRP7p62OziLW7xI5HYGWFzOvupfAlrLvURSzv/FyGyy0eidogEM3ujU+kUG1zuHgb6Ug==", 5966 6011 "license": "MIT", 6012 + "peer": true, 5967 6013 "dependencies": { 5968 6014 "esbuild": "^0.25.0", 5969 - "fdir": "^6.4.4", 5970 - "picomatch": "^4.0.2", 5971 - "postcss": "^8.5.3", 5972 - "rollup": "^4.34.9", 5973 - "tinyglobby": "^0.2.13" 6015 + "fdir": "^6.5.0", 6016 + "picomatch": "^4.0.3", 6017 + "postcss": "^8.5.6", 6018 + "rollup": "^4.43.0", 6019 + "tinyglobby": "^0.2.15" 5974 6020 }, 5975 6021 "bin": { 5976 6022 "vite": "bin/vite.js" 5977 6023 }, 5978 6024 "engines": { 5979 - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" 6025 + "node": "^20.19.0 || >=22.12.0" 5980 6026 }, 5981 6027 "funding": { 5982 6028 "url": "https://github.com/vitejs/vite?sponsor=1" ··· 5985 6031 "fsevents": "~2.3.3" 5986 6032 }, 5987 6033 "peerDependencies": { 5988 - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", 6034 + "@types/node": "^20.19.0 || >=22.12.0", 5989 6035 "jiti": ">=1.21.0", 5990 - "less": "*", 6036 + "less": "^4.0.0", 5991 6037 "lightningcss": "^1.21.0", 5992 - "sass": "*", 5993 - "sass-embedded": "*", 5994 - "stylus": "*", 5995 - "sugarss": "*", 6038 + "sass": "^1.70.0", 6039 + "sass-embedded": "^1.70.0", 6040 + "stylus": ">=0.54.8", 6041 + "sugarss": "^5.0.0", 5996 6042 "terser": "^5.16.0", 5997 6043 "tsx": "^4.8.1", 5998 6044 "yaml": "^2.4.2" ··· 6109 6155 "integrity": "sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==", 6110 6156 "license": "MIT" 6111 6157 }, 6112 - "node_modules/yaml": { 6113 - "version": "2.8.1", 6114 - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", 6115 - "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", 6116 - "license": "ISC", 6117 - "bin": { 6118 - "yaml": "bin.mjs" 6119 - }, 6120 - "engines": { 6121 - "node": ">= 14.6" 6122 - } 6123 - }, 6124 6158 "node_modules/yargs-parser": { 6125 6159 "version": "21.1.1", 6126 6160 "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", ··· 6129 6163 "engines": { 6130 6164 "node": ">=12" 6131 6165 } 6166 + }, 6167 + "node_modules/yesno": { 6168 + "version": "0.4.0", 6169 + "resolved": "https://registry.npmjs.org/yesno/-/yesno-0.4.0.tgz", 6170 + "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==", 6171 + "dev": true, 6172 + "license": "BSD" 6132 6173 }, 6133 6174 "node_modules/yocto-queue": { 6134 6175 "version": "1.2.1",
+5 -2
package.json
··· 6 6 "dev": "astro dev", 7 7 "build": "astro build", 8 8 "preview": "astro preview", 9 - "astro": "astro" 9 + "astro": "astro", 10 + "lexgen": "lex gen-server ./src/lexicon ./lexicons/*" 10 11 }, 11 12 "dependencies": { 12 13 "@astrojs/node": "^9.5.0", 13 - "@astrojs/tailwind": "^6.0.2", 14 14 "@atproto/api": "^0.17.4", 15 15 "@atproto/oauth-client-node": "^0.3.10", 16 16 "@tailwindcss/vite": "^4.1.16", ··· 18 18 "daisyui": "^5.3.9", 19 19 "dotenv": "^17.2.3", 20 20 "tailwindcss": "^4.1.16" 21 + }, 22 + "devDependencies": { 23 + "@atproto/lex-cli": "^0.9.6" 21 24 } 22 25 }
+124
src/components/ProfileForm.astro
··· 1 + --- 2 + interface Props { 3 + displayName?: string 4 + description?: string 5 + avatar?: string 6 + banner?: string 7 + submitLabel?: string 8 + action?: string 9 + } 10 + 11 + const { 12 + displayName = '', 13 + description = '', 14 + avatar = '', 15 + banner = '', 16 + submitLabel = 'Create Profile', 17 + action = '/api/profile/create' 18 + } = Astro.props 19 + --- 20 + 21 + <form 22 + method="POST" 23 + action={action} 24 + enctype="multipart/form-data" 25 + class="space-y-4" 26 + > 27 + <div class="form-control"> 28 + <label class="label"> 29 + <span class="label-text">Display Name</span> 30 + <span class="label-text-alt">Max 64 characters</span> 31 + </label> 32 + <input 33 + type="text" 34 + name="displayName" 35 + placeholder="Enter your display name" 36 + class="input input-bordered w-full" 37 + value={displayName} 38 + maxlength="64" 39 + required 40 + /> 41 + </div> 42 + 43 + <div class="form-control"> 44 + <label class="label"> 45 + <span class="label-text">Description</span> 46 + <span class="label-text-alt">Max 256 characters</span> 47 + </label> 48 + <textarea 49 + name="description" 50 + placeholder="Tell us about yourself" 51 + class="textarea textarea-bordered h-24" 52 + maxlength="256" 53 + >{description}</textarea> 54 + </div> 55 + 56 + <div class="form-control"> 57 + <label class="label"> 58 + <span class="label-text">Avatar</span> 59 + <span class="label-text-alt">PNG or JPEG, max 1MB</span> 60 + </label> 61 + {avatar && ( 62 + <div class="avatar mb-2"> 63 + <div class="w-24 rounded-full"> 64 + <img src={avatar} alt="Current avatar" /> 65 + </div> 66 + </div> 67 + )} 68 + <input 69 + type="file" 70 + name="avatar" 71 + accept="image/png,image/jpeg" 72 + class="file-input file-input-bordered w-full" 73 + /> 74 + </div> 75 + 76 + <div class="form-control"> 77 + <label class="label"> 78 + <span class="label-text">Banner</span> 79 + <span class="label-text-alt">PNG or JPEG, max 1MB</span> 80 + </label> 81 + {banner && ( 82 + <div class="mb-2"> 83 + <img src={banner} alt="Current banner" class="w-full h-32 object-cover rounded-lg" /> 84 + </div> 85 + )} 86 + <input 87 + type="file" 88 + name="banner" 89 + accept="image/png,image/jpeg" 90 + class="file-input file-input-bordered w-full" 91 + /> 92 + </div> 93 + 94 + <div class="form-control mt-6"> 95 + <button type="submit" class="btn btn-primary w-full"> 96 + {submitLabel} 97 + </button> 98 + </div> 99 + </form> 100 + 101 + <script> 102 + // Client-side validation for file sizes 103 + const form = document.querySelector('form') 104 + if (form) { 105 + form.addEventListener('submit', (e) => { 106 + const avatarInput = form.querySelector('input[name="avatar"]') as HTMLInputElement 107 + const bannerInput = form.querySelector('input[name="banner"]') as HTMLInputElement 108 + 109 + const maxSize = 1000000 // 1MB 110 + 111 + if (avatarInput?.files?.[0] && avatarInput.files[0].size > maxSize) { 112 + e.preventDefault() 113 + alert('Avatar file size must be less than 1MB') 114 + return 115 + } 116 + 117 + if (bannerInput?.files?.[0] && bannerInput.files[0].size > maxSize) { 118 + e.preventDefault() 119 + alert('Banner file size must be less than 1MB') 120 + return 121 + } 122 + }) 123 + } 124 + </script>
+74
src/lexicon/index.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + type Auth, 6 + type Options as XrpcOptions, 7 + Server as XrpcServer, 8 + type StreamConfigOrHandler, 9 + type MethodConfigOrHandler, 10 + createServer as createXrpcServer, 11 + } from '@atproto/xrpc-server' 12 + import { schemas } from './lexicons.js' 13 + 14 + export function createServer(options?: XrpcOptions): Server { 15 + return new Server(options) 16 + } 17 + 18 + export class Server { 19 + xrpc: XrpcServer 20 + org: OrgNS 21 + com: ComNS 22 + 23 + constructor(options?: XrpcOptions) { 24 + this.xrpc = createXrpcServer(schemas, options) 25 + this.org = new OrgNS(this) 26 + this.com = new ComNS(this) 27 + } 28 + } 29 + 30 + export class OrgNS { 31 + _server: Server 32 + atmosphereconf: OrgAtmosphereconfNS 33 + 34 + constructor(server: Server) { 35 + this._server = server 36 + this.atmosphereconf = new OrgAtmosphereconfNS(server) 37 + } 38 + } 39 + 40 + export class OrgAtmosphereconfNS { 41 + _server: Server 42 + 43 + constructor(server: Server) { 44 + this._server = server 45 + } 46 + } 47 + 48 + export class ComNS { 49 + _server: Server 50 + atproto: ComAtprotoNS 51 + 52 + constructor(server: Server) { 53 + this._server = server 54 + this.atproto = new ComAtprotoNS(server) 55 + } 56 + } 57 + 58 + export class ComAtprotoNS { 59 + _server: Server 60 + repo: ComAtprotoRepoNS 61 + 62 + constructor(server: Server) { 63 + this._server = server 64 + this.repo = new ComAtprotoRepoNS(server) 65 + } 66 + } 67 + 68 + export class ComAtprotoRepoNS { 69 + _server: Server 70 + 71 + constructor(server: Server) { 72 + this._server = server 73 + } 74 + }
+298
src/lexicon/lexicons.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { 5 + type LexiconDoc, 6 + Lexicons, 7 + ValidationError, 8 + type ValidationResult, 9 + } from '@atproto/lexicon' 10 + import { type $Typed, is$typed, maybe$typed } from './util.js' 11 + 12 + export const schemaDict = { 13 + ComAtprotoLabelDefs: { 14 + lexicon: 1, 15 + id: 'com.atproto.label.defs', 16 + defs: { 17 + label: { 18 + type: 'object', 19 + description: 20 + 'Metadata tag on an atproto resource (eg, repo or record).', 21 + required: ['src', 'uri', 'val', 'cts'], 22 + properties: { 23 + ver: { 24 + type: 'integer', 25 + description: 'The AT Protocol version of the label object.', 26 + }, 27 + src: { 28 + type: 'string', 29 + format: 'did', 30 + description: 'DID of the actor who created this label.', 31 + }, 32 + uri: { 33 + type: 'string', 34 + format: 'uri', 35 + description: 36 + 'AT URI of the record, repository (account), or other resource that this label applies to.', 37 + }, 38 + cid: { 39 + type: 'string', 40 + format: 'cid', 41 + description: 42 + "Optionally, CID specifying the specific version of 'uri' resource this label applies to.", 43 + }, 44 + val: { 45 + type: 'string', 46 + maxLength: 128, 47 + description: 48 + 'The short string name of the value or type of this label.', 49 + }, 50 + neg: { 51 + type: 'boolean', 52 + description: 53 + 'If true, this is a negation label, overwriting a previous label.', 54 + }, 55 + cts: { 56 + type: 'string', 57 + format: 'datetime', 58 + description: 'Timestamp when this label was created.', 59 + }, 60 + exp: { 61 + type: 'string', 62 + format: 'datetime', 63 + description: 64 + 'Timestamp at which this label expires (no longer applies).', 65 + }, 66 + sig: { 67 + type: 'bytes', 68 + description: 'Signature of dag-cbor encoded label.', 69 + }, 70 + }, 71 + }, 72 + selfLabels: { 73 + type: 'object', 74 + description: 75 + 'Metadata tags on an atproto record, published by the author within the record.', 76 + required: ['values'], 77 + properties: { 78 + values: { 79 + type: 'array', 80 + items: { 81 + type: 'ref', 82 + ref: 'lex:com.atproto.label.defs#selfLabel', 83 + }, 84 + maxLength: 10, 85 + }, 86 + }, 87 + }, 88 + selfLabel: { 89 + type: 'object', 90 + description: 91 + 'Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel.', 92 + required: ['val'], 93 + properties: { 94 + val: { 95 + type: 'string', 96 + maxLength: 128, 97 + description: 98 + 'The short string name of the value or type of this label.', 99 + }, 100 + }, 101 + }, 102 + labelValueDefinition: { 103 + type: 'object', 104 + description: 105 + 'Declares a label value and its expected interpretations and behaviors.', 106 + required: ['identifier', 'severity', 'blurs', 'locales'], 107 + properties: { 108 + identifier: { 109 + type: 'string', 110 + description: 111 + "The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+).", 112 + maxLength: 100, 113 + maxGraphemes: 100, 114 + }, 115 + severity: { 116 + type: 'string', 117 + description: 118 + "How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing.", 119 + knownValues: ['inform', 'alert', 'none'], 120 + }, 121 + blurs: { 122 + type: 'string', 123 + description: 124 + "What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing.", 125 + knownValues: ['content', 'media', 'none'], 126 + }, 127 + defaultSetting: { 128 + type: 'string', 129 + description: 'The default setting for this label.', 130 + knownValues: ['ignore', 'warn', 'hide'], 131 + default: 'warn', 132 + }, 133 + adultOnly: { 134 + type: 'boolean', 135 + description: 136 + 'Does the user need to have adult content enabled in order to configure this label?', 137 + }, 138 + locales: { 139 + type: 'array', 140 + items: { 141 + type: 'ref', 142 + ref: 'lex:com.atproto.label.defs#labelValueDefinitionStrings', 143 + }, 144 + }, 145 + }, 146 + }, 147 + labelValueDefinitionStrings: { 148 + type: 'object', 149 + description: 150 + 'Strings which describe the label in the UI, localized into a specific language.', 151 + required: ['lang', 'name', 'description'], 152 + properties: { 153 + lang: { 154 + type: 'string', 155 + description: 156 + 'The code of the language these strings are written in.', 157 + format: 'language', 158 + }, 159 + name: { 160 + type: 'string', 161 + description: 'A short human-readable name for the label.', 162 + maxGraphemes: 64, 163 + maxLength: 640, 164 + }, 165 + description: { 166 + type: 'string', 167 + description: 168 + 'A longer description of what the label means and why it might be applied.', 169 + maxGraphemes: 10000, 170 + maxLength: 100000, 171 + }, 172 + }, 173 + }, 174 + labelValue: { 175 + type: 'string', 176 + knownValues: [ 177 + '!hide', 178 + '!no-promote', 179 + '!warn', 180 + '!no-unauthenticated', 181 + 'dmca-violation', 182 + 'doxxing', 183 + 'porn', 184 + 'sexual', 185 + 'nudity', 186 + 'nsfl', 187 + 'gore', 188 + ], 189 + }, 190 + }, 191 + }, 192 + OrgAtmosphereconfProfile: { 193 + lexicon: 1, 194 + id: 'org.atmosphereconf.profile', 195 + defs: { 196 + main: { 197 + type: 'record', 198 + description: 'A conference attendee profile.', 199 + key: 'literal:self', 200 + record: { 201 + type: 'object', 202 + properties: { 203 + displayName: { 204 + type: 'string', 205 + maxGraphemes: 64, 206 + maxLength: 640, 207 + }, 208 + description: { 209 + type: 'string', 210 + description: 'Free-form profile description text.', 211 + maxGraphemes: 256, 212 + maxLength: 2560, 213 + }, 214 + avatar: { 215 + type: 'blob', 216 + description: 'Profile picture for conference attendee', 217 + accept: ['image/png', 'image/jpeg'], 218 + maxSize: 1000000, 219 + }, 220 + banner: { 221 + type: 'blob', 222 + description: 223 + 'Larger horizontal image to display behind profile view.', 224 + accept: ['image/png', 'image/jpeg'], 225 + maxSize: 1000000, 226 + }, 227 + labels: { 228 + type: 'union', 229 + description: 'Self-label values for the conference profile.', 230 + refs: ['lex:com.atproto.label.defs#selfLabels'], 231 + }, 232 + createdAt: { 233 + type: 'string', 234 + format: 'datetime', 235 + }, 236 + }, 237 + }, 238 + }, 239 + }, 240 + }, 241 + ComAtprotoRepoStrongRef: { 242 + lexicon: 1, 243 + id: 'com.atproto.repo.strongRef', 244 + description: 'A URI with a content-hash fingerprint.', 245 + defs: { 246 + main: { 247 + type: 'object', 248 + required: ['uri', 'cid'], 249 + properties: { 250 + uri: { 251 + type: 'string', 252 + format: 'at-uri', 253 + }, 254 + cid: { 255 + type: 'string', 256 + format: 'cid', 257 + }, 258 + }, 259 + }, 260 + }, 261 + }, 262 + } as const satisfies Record<string, LexiconDoc> 263 + export const schemas = Object.values(schemaDict) satisfies LexiconDoc[] 264 + export const lexicons: Lexicons = new Lexicons(schemas) 265 + 266 + export function validate<T extends { $type: string }>( 267 + v: unknown, 268 + id: string, 269 + hash: string, 270 + requiredType: true, 271 + ): ValidationResult<T> 272 + export function validate<T extends { $type?: string }>( 273 + v: unknown, 274 + id: string, 275 + hash: string, 276 + requiredType?: false, 277 + ): ValidationResult<T> 278 + export function validate( 279 + v: unknown, 280 + id: string, 281 + hash: string, 282 + requiredType?: boolean, 283 + ): ValidationResult { 284 + return (requiredType ? is$typed : maybe$typed)(v, id, hash) 285 + ? lexicons.validate(`${id}#${hash}`, v) 286 + : { 287 + success: false, 288 + error: new ValidationError( 289 + `Must be an object with "${hash === 'main' ? id : `${id}#${hash}`}" $type property`, 290 + ), 291 + } 292 + } 293 + 294 + export const ids = { 295 + ComAtprotoLabelDefs: 'com.atproto.label.defs', 296 + OrgAtmosphereconfProfile: 'org.atmosphereconf.profile', 297 + ComAtprotoRepoStrongRef: 'com.atproto.repo.strongRef', 298 + } as const
+146
src/lexicon/types/com/atproto/label/defs.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../../lexicons' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'com.atproto.label.defs' 16 + 17 + /** Metadata tag on an atproto resource (eg, repo or record). */ 18 + export interface Label { 19 + $type?: 'com.atproto.label.defs#label' 20 + /** The AT Protocol version of the label object. */ 21 + ver?: number 22 + /** DID of the actor who created this label. */ 23 + src: string 24 + /** AT URI of the record, repository (account), or other resource that this label applies to. */ 25 + uri: string 26 + /** Optionally, CID specifying the specific version of 'uri' resource this label applies to. */ 27 + cid?: string 28 + /** The short string name of the value or type of this label. */ 29 + val: string 30 + /** If true, this is a negation label, overwriting a previous label. */ 31 + neg?: boolean 32 + /** Timestamp when this label was created. */ 33 + cts: string 34 + /** Timestamp at which this label expires (no longer applies). */ 35 + exp?: string 36 + /** Signature of dag-cbor encoded label. */ 37 + sig?: Uint8Array 38 + } 39 + 40 + const hashLabel = 'label' 41 + 42 + export function isLabel<V>(v: V) { 43 + return is$typed(v, id, hashLabel) 44 + } 45 + 46 + export function validateLabel<V>(v: V) { 47 + return validate<Label & V>(v, id, hashLabel) 48 + } 49 + 50 + /** Metadata tags on an atproto record, published by the author within the record. */ 51 + export interface SelfLabels { 52 + $type?: 'com.atproto.label.defs#selfLabels' 53 + values: SelfLabel[] 54 + } 55 + 56 + const hashSelfLabels = 'selfLabels' 57 + 58 + export function isSelfLabels<V>(v: V) { 59 + return is$typed(v, id, hashSelfLabels) 60 + } 61 + 62 + export function validateSelfLabels<V>(v: V) { 63 + return validate<SelfLabels & V>(v, id, hashSelfLabels) 64 + } 65 + 66 + /** Metadata tag on an atproto record, published by the author within the record. Note that schemas should use #selfLabels, not #selfLabel. */ 67 + export interface SelfLabel { 68 + $type?: 'com.atproto.label.defs#selfLabel' 69 + /** The short string name of the value or type of this label. */ 70 + val: string 71 + } 72 + 73 + const hashSelfLabel = 'selfLabel' 74 + 75 + export function isSelfLabel<V>(v: V) { 76 + return is$typed(v, id, hashSelfLabel) 77 + } 78 + 79 + export function validateSelfLabel<V>(v: V) { 80 + return validate<SelfLabel & V>(v, id, hashSelfLabel) 81 + } 82 + 83 + /** Declares a label value and its expected interpretations and behaviors. */ 84 + export interface LabelValueDefinition { 85 + $type?: 'com.atproto.label.defs#labelValueDefinition' 86 + /** The value of the label being defined. Must only include lowercase ascii and the '-' character ([a-z-]+). */ 87 + identifier: string 88 + /** How should a client visually convey this label? 'inform' means neutral and informational; 'alert' means negative and warning; 'none' means show nothing. */ 89 + severity: 'inform' | 'alert' | 'none' | (string & {}) 90 + /** What should this label hide in the UI, if applied? 'content' hides all of the target; 'media' hides the images/video/audio; 'none' hides nothing. */ 91 + blurs: 'content' | 'media' | 'none' | (string & {}) 92 + /** The default setting for this label. */ 93 + defaultSetting: 'ignore' | 'warn' | 'hide' | (string & {}) 94 + /** Does the user need to have adult content enabled in order to configure this label? */ 95 + adultOnly?: boolean 96 + locales: LabelValueDefinitionStrings[] 97 + } 98 + 99 + const hashLabelValueDefinition = 'labelValueDefinition' 100 + 101 + export function isLabelValueDefinition<V>(v: V) { 102 + return is$typed(v, id, hashLabelValueDefinition) 103 + } 104 + 105 + export function validateLabelValueDefinition<V>(v: V) { 106 + return validate<LabelValueDefinition & V>(v, id, hashLabelValueDefinition) 107 + } 108 + 109 + /** Strings which describe the label in the UI, localized into a specific language. */ 110 + export interface LabelValueDefinitionStrings { 111 + $type?: 'com.atproto.label.defs#labelValueDefinitionStrings' 112 + /** The code of the language these strings are written in. */ 113 + lang: string 114 + /** A short human-readable name for the label. */ 115 + name: string 116 + /** A longer description of what the label means and why it might be applied. */ 117 + description: string 118 + } 119 + 120 + const hashLabelValueDefinitionStrings = 'labelValueDefinitionStrings' 121 + 122 + export function isLabelValueDefinitionStrings<V>(v: V) { 123 + return is$typed(v, id, hashLabelValueDefinitionStrings) 124 + } 125 + 126 + export function validateLabelValueDefinitionStrings<V>(v: V) { 127 + return validate<LabelValueDefinitionStrings & V>( 128 + v, 129 + id, 130 + hashLabelValueDefinitionStrings, 131 + ) 132 + } 133 + 134 + export type LabelValue = 135 + | '!hide' 136 + | '!no-promote' 137 + | '!warn' 138 + | '!no-unauthenticated' 139 + | 'dmca-violation' 140 + | 'doxxing' 141 + | 'porn' 142 + | 'sexual' 143 + | 'nudity' 144 + | 'nsfl' 145 + | 'gore' 146 + | (string & {})
+31
src/lexicon/types/com/atproto/repo/strongRef.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../../lexicons' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'com.atproto.repo.strongRef' 16 + 17 + export interface Main { 18 + $type?: 'com.atproto.repo.strongRef' 19 + uri: string 20 + cid: string 21 + } 22 + 23 + const hashMain = 'main' 24 + 25 + export function isMain<V>(v: V) { 26 + return is$typed(v, id, hashMain) 27 + } 28 + 29 + export function validateMain<V>(v: V) { 30 + return validate<Main & V>(v, id, hashMain) 31 + }
+42
src/lexicon/types/org/atmosphereconf/profile.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from '@atproto/lexicon' 5 + import { CID } from 'multiformats/cid' 6 + import { validate as _validate } from '../../../lexicons' 7 + import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util' 8 + import type * as ComAtprotoLabelDefs from '../../com/atproto/label/defs.js' 9 + 10 + const is$typed = _is$typed, 11 + validate = _validate 12 + const id = 'org.atmosphereconf.profile' 13 + 14 + export interface Main { 15 + $type: 'org.atmosphereconf.profile' 16 + displayName?: string 17 + /** Free-form profile description text. */ 18 + description?: string 19 + /** Profile picture for conference attendee */ 20 + avatar?: BlobRef 21 + /** Larger horizontal image to display behind profile view. */ 22 + banner?: BlobRef 23 + labels?: $Typed<ComAtprotoLabelDefs.SelfLabels> | { $type: string } 24 + createdAt?: string 25 + [k: string]: unknown 26 + } 27 + 28 + const hashMain = 'main' 29 + 30 + export function isMain<V>(v: V) { 31 + return is$typed(v, id, hashMain) 32 + } 33 + 34 + export function validateMain<V>(v: V) { 35 + return validate<Main & V>(v, id, hashMain, true) 36 + } 37 + 38 + export { 39 + type Main as Record, 40 + isMain as isRecord, 41 + validateMain as validateRecord, 42 + }
+82
src/lexicon/util.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + 5 + import { type ValidationResult } from '@atproto/lexicon' 6 + 7 + export type OmitKey<T, K extends keyof T> = { 8 + [K2 in keyof T as K2 extends K ? never : K2]: T[K2] 9 + } 10 + 11 + export type $Typed<V, T extends string = string> = V & { $type: T } 12 + export type Un$Typed<V extends { $type?: string }> = OmitKey<V, '$type'> 13 + 14 + export type $Type<Id extends string, Hash extends string> = Hash extends 'main' 15 + ? Id 16 + : `${Id}#${Hash}` 17 + 18 + function isObject<V>(v: V): v is V & object { 19 + return v != null && typeof v === 'object' 20 + } 21 + 22 + function is$type<Id extends string, Hash extends string>( 23 + $type: unknown, 24 + id: Id, 25 + hash: Hash, 26 + ): $type is $Type<Id, Hash> { 27 + return hash === 'main' 28 + ? $type === id 29 + : // $type === `${id}#${hash}` 30 + typeof $type === 'string' && 31 + $type.length === id.length + 1 + hash.length && 32 + $type.charCodeAt(id.length) === 35 /* '#' */ && 33 + $type.startsWith(id) && 34 + $type.endsWith(hash) 35 + } 36 + 37 + export type $TypedObject< 38 + V, 39 + Id extends string, 40 + Hash extends string, 41 + > = V extends { 42 + $type: $Type<Id, Hash> 43 + } 44 + ? V 45 + : V extends { $type?: string } 46 + ? V extends { $type?: infer T extends $Type<Id, Hash> } 47 + ? V & { $type: T } 48 + : never 49 + : V & { $type: $Type<Id, Hash> } 50 + 51 + export function is$typed<V, Id extends string, Hash extends string>( 52 + v: V, 53 + id: Id, 54 + hash: Hash, 55 + ): v is $TypedObject<V, Id, Hash> { 56 + return isObject(v) && '$type' in v && is$type(v.$type, id, hash) 57 + } 58 + 59 + export function maybe$typed<V, Id extends string, Hash extends string>( 60 + v: V, 61 + id: Id, 62 + hash: Hash, 63 + ): v is V & object & { $type?: $Type<Id, Hash> } { 64 + return ( 65 + isObject(v) && 66 + ('$type' in v ? v.$type === undefined || is$type(v.$type, id, hash) : true) 67 + ) 68 + } 69 + 70 + export type Validator<R = unknown> = (v: unknown) => ValidationResult<R> 71 + export type ValidatorParam<V extends Validator> = 72 + V extends Validator<infer R> ? R : never 73 + 74 + /** 75 + * Utility function that allows to convert a "validate*" utility function into a 76 + * type predicate. 77 + */ 78 + export function asPredicate<V extends Validator>(validate: V) { 79 + return function <T>(v: T): v is T & ValidatorParam<V> { 80 + return validate(v).success 81 + } 82 + }
+104
src/pages/api/profile/create.ts
··· 1 + import type { APIRoute } from 'astro' 2 + import { getOAuthClient } from '../../../lib/context' 3 + import { getSession } from '../../../lib/session' 4 + import { Agent, BlobRef } from '@atproto/api' 5 + import type { Main as ProfileRecord } from '../../../lexicon/types/org/atmosphereconf/profile' 6 + 7 + async function fileToBlob(agent: Agent, file: File): Promise<BlobRef> { 8 + const arrayBuffer = await file.arrayBuffer() 9 + const uint8Array = new Uint8Array(arrayBuffer) 10 + 11 + const response = await agent.com.atproto.repo.uploadBlob(uint8Array, { 12 + encoding: file.type, 13 + }) 14 + 15 + return response.data.blob 16 + } 17 + 18 + export const POST: APIRoute = async ({ request, cookies, redirect }) => { 19 + try { 20 + const session = getSession(cookies) 21 + const oauthClient = getOAuthClient(cookies) 22 + 23 + if (!session.did) { 24 + return new Response('Unauthorized', { status: 401 }) 25 + } 26 + 27 + const oauthSession = await oauthClient.restore(session.did) 28 + if (!oauthSession) { 29 + return new Response('Session expired', { status: 401 }) 30 + } 31 + 32 + const agent = new Agent(oauthSession) 33 + const formData = await request.formData() 34 + 35 + // Extract form data 36 + const displayName = formData.get('displayName') 37 + const description = formData.get('description') 38 + const avatarFile = formData.get('avatar') 39 + const bannerFile = formData.get('banner') 40 + 41 + if (!displayName || typeof displayName !== 'string') { 42 + return new Response('Display name is required', { status: 400 }) 43 + } 44 + 45 + // Validate file sizes 46 + if (avatarFile instanceof File && avatarFile.size > 0 && avatarFile.size > 1000000) { 47 + return new Response('Avatar file size must be less than 1MB', { status: 400 }) 48 + } 49 + 50 + if (bannerFile instanceof File && bannerFile.size > 0 && bannerFile.size > 1000000) { 51 + return new Response('Banner file size must be less than 1MB', { status: 400 }) 52 + } 53 + 54 + // Build the profile record 55 + const record: Omit<ProfileRecord, '$type'> = { 56 + displayName: displayName.slice(0, 64), 57 + description: typeof description === 'string' ? description.slice(0, 256) : undefined, 58 + createdAt: new Date().toISOString(), 59 + } 60 + 61 + // Upload avatar if provided 62 + if (avatarFile instanceof File && avatarFile.size > 0) { 63 + try { 64 + record.avatar = await fileToBlob(agent, avatarFile) 65 + } catch (err) { 66 + console.error('Failed to upload avatar:', err) 67 + return new Response('Failed to upload avatar', { status: 500 }) 68 + } 69 + } 70 + 71 + // Upload banner if provided 72 + if (bannerFile instanceof File && bannerFile.size > 0) { 73 + try { 74 + record.banner = await fileToBlob(agent, bannerFile) 75 + } catch (err) { 76 + console.error('Failed to upload banner:', err) 77 + return new Response('Failed to upload banner', { status: 500 }) 78 + } 79 + } 80 + 81 + // Create or update the profile record 82 + try { 83 + await agent.com.atproto.repo.putRecord({ 84 + repo: agent.assertDid, 85 + collection: 'org.atmosphereconf.profile', 86 + rkey: 'self', 87 + record: { 88 + $type: 'org.atmosphereconf.profile', 89 + ...record, 90 + }, 91 + }) 92 + 93 + return redirect('/') 94 + } catch (err) { 95 + console.error('Failed to create profile:', err) 96 + const error = err instanceof Error ? err.message : 'unexpected error' 97 + return new Response(`Failed to create profile: ${error}`, { status: 500 }) 98 + } 99 + } catch (err) { 100 + console.error('Profile creation failed:', err) 101 + const error = err instanceof Error ? err.message : 'unexpected error' 102 + return new Response(`Profile creation failed: ${error}`, { status: 500 }) 103 + } 104 + }
+45 -35
src/pages/index.astro
··· 1 1 --- 2 - import "../styles.css"; 3 - import { getSession } from "../lib/session"; 4 - import { getOAuthClient } from "../lib/context"; 5 - import { Agent } from "@atproto/api"; 2 + import '../styles.css' 3 + import { getSession } from '../lib/session' 4 + import { getOAuthClient } from '../lib/context' 5 + import { Agent } from '@atproto/api' 6 6 7 - const session = getSession(Astro.cookies); 8 - const oauthClient = getOAuthClient(Astro.cookies); 7 + const session = getSession(Astro.cookies) 8 + const oauthClient = getOAuthClient(Astro.cookies) 9 9 10 - let agent: Agent | null = null; 11 - let profile: any = null; 10 + let agent: Agent | null = null 11 + let profile: any = null 12 12 13 13 if (session.did) { 14 14 try { 15 - const oauthSession = await oauthClient.restore(session.did); 15 + const oauthSession = await oauthClient.restore(session.did) 16 16 if (oauthSession) { 17 - agent = new Agent(oauthSession); 17 + agent = new Agent(oauthSession) 18 18 19 19 try { 20 - const profileResponse = await agent.com.atproto.repo.getRecord({ 21 - repo: agent.assertDid, 22 - collection: "app.bsky.actor.profile", 23 - rkey: "self", 24 - }); 25 - profile = profileResponse.data; 20 + const profileResponse = await agent.app.bsky.actor.getProfile({ 21 + actor: agent.assertDid, 22 + }) 23 + profile = profileResponse.data 26 24 } catch (err) { 27 - console.warn("Failed to fetch profile:", err); 25 + console.warn('Failed to fetch profile:', err) 28 26 } 29 27 } 30 28 } catch (err) { 31 - console.warn("OAuth restore failed:", err); 32 - session.destroy(); 29 + console.warn('OAuth restore failed:', err) 30 + session.destroy() 33 31 } 34 32 } 35 33 36 - const displayName = profile?.value?.displayName || agent?.assertDid || "User"; 37 - const handle = agent?.assertDid || ""; 34 + const displayName = profile?.displayName || agent?.assertDid || 'User' 35 + const handle = profile?.handle || agent?.assertDid || '' 36 + const avatar = profile?.avatar 37 + const description = profile?.description 38 38 --- 39 39 40 40 <html lang="en" data-theme="dracula"> ··· 43 43 <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 44 44 <meta name="viewport" content="width=device-width" /> 45 45 <meta name="generator" content={Astro.generator} /> 46 - <title>ATProto Login</title> 46 + <title>ATmosphere Login</title> 47 47 </head> 48 48 <body> 49 49 <div class="min-h-screen flex items-center justify-center"> 50 - <div class="card w-96 bg-base-100 shadow-xl"> 50 + <div class="card w-96 bg-base-200 shadow-xl p-8"> 51 51 <div class="card-body"> 52 52 { 53 53 agent ? ( 54 54 <> 55 55 <h2 class="card-title justify-center mb-4">Welcome!</h2> 56 56 <div class="space-y-4"> 57 - <div class="text-center"> 57 + <div class="flex flex-col items-center text-center"> 58 + {avatar && ( 59 + <div class="avatar mb-4"> 60 + <div class="w-24 rounded-full"> 61 + <img src={avatar} alt={displayName} /> 62 + </div> 63 + </div> 64 + )} 58 65 <p class="text-lg font-semibold">{displayName}</p> 59 66 <p class="text-sm opacity-70">{handle}</p> 67 + {description && <p class="text-sm mt-2 opacity-80">{description}</p>} 60 68 </div> 61 - <form method="POST" action="/api/logout" class="w-full"> 62 - <button type="submit" class="btn btn-error w-full"> 63 - Logout 64 - </button> 65 - </form> 69 + <div class="space-y-2 w-full"> 70 + <a href={`/profile/${handle}`} class="btn btn-primary w-full"> 71 + View Profile 72 + </a> 73 + <form method="POST" action="/api/logout" class="w-full"> 74 + <button type="submit" class="btn btn-error w-full"> 75 + Logout 76 + </button> 77 + </form> 78 + </div> 66 79 </div> 67 80 </> 68 81 ) : ( 69 82 <> 70 - <h2 class="card-title justify-center mb-6">ATProto Login</h2> 83 + <h2 class="card-title justify-center mb-6">ATmosphere Login</h2> 71 84 <form method="POST" action="/api/login"> 72 85 <div class="join join-vertical w-full"> 73 86 <input 74 87 type="text" 75 88 placeholder="Enter your handle (e.g. alice.bsky.social)" 76 - class="input input-bordered join-item" 89 + class="input input-bordered join-item w-full" 77 90 name="handle" 78 91 required 79 92 /> 80 - <button 81 - type="submit" 82 - class="btn btn-primary btn-wide join-item" 83 - > 93 + <button type="submit" class="btn btn-primary join-item w-full"> 84 94 Login 85 95 </button> 86 96 </div>
+208
src/pages/profile/[handle].astro
··· 1 + --- 2 + import '../../styles.css' 3 + import { getSession } from '../../lib/session' 4 + import { getOAuthClient } from '../../lib/context' 5 + import { Agent } from '@atproto/api' 6 + 7 + const { handle } = Astro.params 8 + 9 + if (!handle) { 10 + return Astro.redirect('/') 11 + } 12 + 13 + const session = getSession(Astro.cookies) 14 + const oauthClient = getOAuthClient(Astro.cookies) 15 + 16 + let agent: Agent | null = null 17 + let profile: any = null 18 + let conferenceProfile: any = null 19 + let did: string | null = null 20 + let isOwnProfile = false 21 + 22 + // Get agent if authenticated 23 + if (session.did) { 24 + try { 25 + const oauthSession = await oauthClient.restore(session.did) 26 + if (oauthSession) { 27 + agent = new Agent(oauthSession) 28 + isOwnProfile = agent.assertDid === session.did 29 + } 30 + } catch (err) { 31 + console.warn('OAuth restore failed:', err) 32 + } 33 + } 34 + 35 + // Create a public agent to resolve the profile if we don't have an authenticated one 36 + const publicAgent = agent || new Agent({ service: 'https://public.api.bsky.app' }) 37 + 38 + // Resolve handle to DID and get profile 39 + try { 40 + const resolveResponse = await publicAgent.resolveHandle({ handle }) 41 + did = resolveResponse.data.did 42 + 43 + // Get Bluesky profile for basic info 44 + try { 45 + const profileResponse = await publicAgent.app.bsky.actor.getProfile({ 46 + actor: did, 47 + }) 48 + profile = profileResponse.data 49 + } catch (err) { 50 + console.warn('Failed to fetch Bluesky profile:', err) 51 + } 52 + 53 + // Get conference profile 54 + try { 55 + const response = await publicAgent.com.atproto.repo.getRecord({ 56 + repo: did, 57 + collection: 'org.atmosphereconf.profile', 58 + rkey: 'self' 59 + }) 60 + conferenceProfile = response.data.value 61 + } catch (err) { 62 + console.log('No conference profile found for this user') 63 + } 64 + } catch (err) { 65 + console.error('Failed to resolve handle:', err) 66 + return new Response('Profile not found', { status: 404 }) 67 + } 68 + 69 + // Helper function to convert blob refs to URLs 70 + function blobRefToUrl(blobRef: any, did: string): string { 71 + if (!blobRef || typeof blobRef !== 'object') return '' 72 + 73 + // Handle BlobRef object with CID 74 + if (blobRef.ref) { 75 + const cid = blobRef.ref.toString() 76 + return `https://cdn.bsky.app/img/avatar/plain/${did}/${cid}@jpeg` 77 + } 78 + 79 + return '' 80 + } 81 + 82 + const displayName = conferenceProfile?.displayName || profile?.displayName || handle 83 + const description = conferenceProfile?.description || profile?.description || '' 84 + 85 + // Handle both blob refs and direct URLs 86 + let avatar = '' 87 + if (conferenceProfile?.avatar) { 88 + avatar = blobRefToUrl(conferenceProfile.avatar, did) 89 + } else if (profile?.avatar) { 90 + avatar = profile.avatar 91 + } 92 + 93 + let banner = '' 94 + if (conferenceProfile?.banner) { 95 + banner = blobRefToUrl(conferenceProfile.banner, did) 96 + } else if (profile?.banner) { 97 + banner = profile.banner 98 + } 99 + 100 + const hasConferenceProfile = !!conferenceProfile 101 + --- 102 + 103 + <html lang="en" data-theme="dracula"> 104 + <head> 105 + <meta charset="utf-8" /> 106 + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 107 + <meta name="viewport" content="width=device-width" /> 108 + <meta name="generator" content={Astro.generator} /> 109 + <title>{displayName} - ATmosphere</title> 110 + </head> 111 + <body> 112 + <div class="min-h-screen bg-base-300"> 113 + {banner && ( 114 + <div class="w-full h-48 md:h-64 bg-base-200"> 115 + <img 116 + src={banner} 117 + alt="Profile banner" 118 + class="w-full h-full object-cover" 119 + /> 120 + </div> 121 + )} 122 + 123 + <div class="container mx-auto px-4 -mt-16 relative z-10 max-w-4xl"> 124 + <div class="card bg-base-200 shadow-xl"> 125 + <div class="card-body"> 126 + <div class="flex flex-col md:flex-row gap-6"> 127 + <div class="flex-shrink-0"> 128 + {avatar ? ( 129 + <div class="avatar"> 130 + <div class="w-32 rounded-full ring ring-primary ring-offset-base-100 ring-offset-2"> 131 + <img src={avatar} alt={displayName} /> 132 + </div> 133 + </div> 134 + ) : ( 135 + <div class="avatar placeholder"> 136 + <div class="bg-neutral text-neutral-content rounded-full w-32"> 137 + <span class="text-3xl">{displayName[0]?.toUpperCase()}</span> 138 + </div> 139 + </div> 140 + )} 141 + </div> 142 + 143 + <div class="flex-grow"> 144 + <div class="flex flex-col md:flex-row md:items-start md:justify-between gap-4"> 145 + <div> 146 + <h1 class="text-3xl font-bold">{displayName}</h1> 147 + <p class="text-sm opacity-70">@{handle}</p> 148 + {did && ( 149 + <p class="text-xs opacity-50 mt-1 font-mono break-all"> 150 + {did} 151 + </p> 152 + )} 153 + </div> 154 + 155 + {isOwnProfile && ( 156 + <a href="/profile/create" class="btn btn-primary btn-sm"> 157 + Edit Profile 158 + </a> 159 + )} 160 + </div> 161 + 162 + {description && ( 163 + <p class="mt-4 text-base whitespace-pre-wrap">{description}</p> 164 + )} 165 + 166 + <div class="mt-4"> 167 + {hasConferenceProfile ? ( 168 + <div class="badge badge-success gap-2"> 169 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="inline-block w-4 h-4 stroke-current"> 170 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path> 171 + </svg> 172 + Conference Attendee 173 + </div> 174 + ) : ( 175 + <div class="badge badge-ghost gap-2"> 176 + No Conference Profile 177 + </div> 178 + )} 179 + </div> 180 + </div> 181 + </div> 182 + 183 + {!hasConferenceProfile && isOwnProfile && ( 184 + <div class="alert alert-warning mt-6"> 185 + <svg xmlns="http://www.w3.org/2000/svg" class="stroke-current shrink-0 h-6 w-6" fill="none" viewBox="0 0 24 24"> 186 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" /> 187 + </svg> 188 + <span>You haven't set up your conference profile yet.</span> 189 + <div> 190 + <a href="/profile/create" class="btn btn-sm btn-primary">Create Now</a> 191 + </div> 192 + </div> 193 + )} 194 + </div> 195 + </div> 196 + 197 + <div class="mt-6 mb-12"> 198 + <a href="/" class="btn btn-ghost"> 199 + <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2" fill="none" viewBox="0 0 24 24" stroke="currentColor"> 200 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" /> 201 + </svg> 202 + Back to Home 203 + </a> 204 + </div> 205 + </div> 206 + </div> 207 + </body> 208 + </html>
+87
src/pages/profile/create.astro
··· 1 + --- 2 + import '../../styles.css' 3 + import ProfileForm from '../../components/ProfileForm.astro' 4 + import { getSession } from '../../lib/session' 5 + import { getOAuthClient } from '../../lib/context' 6 + import { Agent } from '@atproto/api' 7 + 8 + const session = getSession(Astro.cookies) 9 + const oauthClient = getOAuthClient(Astro.cookies) 10 + 11 + // Redirect to login if not authenticated 12 + if (!session.did) { 13 + return Astro.redirect('/') 14 + } 15 + 16 + let agent: Agent | null = null 17 + let existingProfile: any = null 18 + 19 + try { 20 + const oauthSession = await oauthClient.restore(session.did) 21 + if (oauthSession) { 22 + agent = new Agent(oauthSession) 23 + 24 + // Check if profile already exists 25 + try { 26 + // Try to get the profile record from the repo 27 + const did = agent.assertDid 28 + const response = await agent.com.atproto.repo.getRecord({ 29 + repo: did, 30 + collection: 'org.atmosphereconf.profile', 31 + rkey: 'self' 32 + }) 33 + existingProfile = response.data.value 34 + } catch (err) { 35 + // Profile doesn't exist yet, which is fine 36 + console.log('No existing profile found') 37 + } 38 + } 39 + } catch (err) { 40 + console.warn('OAuth restore failed:', err) 41 + session.destroy() 42 + return Astro.redirect('/') 43 + } 44 + 45 + const displayName = existingProfile?.displayName || '' 46 + const description = existingProfile?.description || '' 47 + --- 48 + 49 + <html lang="en" data-theme="dracula"> 50 + <head> 51 + <meta charset="utf-8" /> 52 + <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> 53 + <meta name="viewport" content="width=device-width" /> 54 + <meta name="generator" content={Astro.generator} /> 55 + <title>Create Profile - ATmosphere</title> 56 + </head> 57 + <body> 58 + <div class="min-h-screen flex items-center justify-center p-4"> 59 + <div class="card w-full max-w-2xl bg-base-200 shadow-xl"> 60 + <div class="card-body"> 61 + <h2 class="card-title justify-center text-2xl mb-6"> 62 + {existingProfile ? 'Update Your Profile' : 'Create Your Profile'} 63 + </h2> 64 + 65 + <div class="alert alert-info mb-4"> 66 + <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6"> 67 + <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path> 68 + </svg> 69 + <span>Set up your conference attendee profile</span> 70 + </div> 71 + 72 + <ProfileForm 73 + displayName={displayName} 74 + description={description} 75 + submitLabel={existingProfile ? 'Update Profile' : 'Create Profile'} 76 + /> 77 + 78 + <div class="divider">OR</div> 79 + 80 + <a href="/" class="btn btn-ghost w-full"> 81 + Back to Home 82 + </a> 83 + </div> 84 + </div> 85 + </div> 86 + </body> 87 + </html>
+2 -3
src/styles.css
··· 1 - @tailwind base; 2 - @tailwind components; 3 - @tailwind utilities; 1 + @import "tailwindcss"; 2 + 4 3 @plugin "daisyui" { 5 4 themes: 6 5 dracula --default,
-3
tailwind.config.mjs
··· 1 1 /** @type {import('tailwindcss').Config} */ 2 2 export default { 3 3 content: ["./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}"], 4 - daisyui: { 5 - themes: true, 6 - }, 7 4 };