grain.social is a photo sharing platform built on atproto.

wip

+1
.gitignore
··· 5 5 image_storage 6 6 .env 7 7 grain-root.crt 8 + *.log
+2 -8
README.md
··· 85 85 86 86 #### Install the root certificate on your machine 87 87 88 - First, get your Caddy container ID: 89 - 90 - ```bash 91 - docker ps 92 - ``` 93 - 94 - Then copy the cert out: 88 + Copy the cert out: 95 89 96 90 ```bash 97 - docker cp <caddy_container_id>:/data/pki/authorities/grain/root.crt ./grain-root.crt 91 + docker cp caddy:/data/pki/authorities/grain/root.crt ./grain-root.crt 98 92 ``` 99 93 100 94 Once you have grain-root.crt, install it:
+50
__generated__/index.ts
··· 39 39 export class Server { 40 40 xrpc: XrpcServer 41 41 app: AppNS 42 + sh: ShNS 42 43 social: SocialNS 43 44 com: ComNS 44 45 45 46 constructor(options?: XrpcOptions) { 46 47 this.xrpc = createXrpcServer(schemas, options) 47 48 this.app = new AppNS(this) 49 + this.sh = new ShNS(this) 48 50 this.social = new SocialNS(this) 49 51 this.com = new ComNS(this) 50 52 } ··· 118 120 } 119 121 } 120 122 123 + export class ShNS { 124 + _server: Server 125 + tangled: ShTangledNS 126 + 127 + constructor(server: Server) { 128 + this._server = server 129 + this.tangled = new ShTangledNS(server) 130 + } 131 + } 132 + 133 + export class ShTangledNS { 134 + _server: Server 135 + graph: ShTangledGraphNS 136 + actor: ShTangledActorNS 137 + 138 + constructor(server: Server) { 139 + this._server = server 140 + this.graph = new ShTangledGraphNS(server) 141 + this.actor = new ShTangledActorNS(server) 142 + } 143 + } 144 + 145 + export class ShTangledGraphNS { 146 + _server: Server 147 + 148 + constructor(server: Server) { 149 + this._server = server 150 + } 151 + } 152 + 153 + export class ShTangledActorNS { 154 + _server: Server 155 + 156 + constructor(server: Server) { 157 + this._server = server 158 + } 159 + } 160 + 121 161 export class SocialNS { 122 162 _server: Server 123 163 grain: SocialGrainNS ··· 131 171 export class SocialGrainNS { 132 172 _server: Server 133 173 gallery: SocialGrainGalleryNS 174 + graph: SocialGrainGraphNS 134 175 actor: SocialGrainActorNS 135 176 photo: SocialGrainPhotoNS 136 177 137 178 constructor(server: Server) { 138 179 this._server = server 139 180 this.gallery = new SocialGrainGalleryNS(server) 181 + this.graph = new SocialGrainGraphNS(server) 140 182 this.actor = new SocialGrainActorNS(server) 141 183 this.photo = new SocialGrainPhotoNS(server) 142 184 } 143 185 } 144 186 145 187 export class SocialGrainGalleryNS { 188 + _server: Server 189 + 190 + constructor(server: Server) { 191 + this._server = server 192 + } 193 + } 194 + 195 + export class SocialGrainGraphNS { 146 196 _server: Server 147 197 148 198 constructor(server: Server) {
+130 -215
__generated__/lexicons.ts
··· 2296 2296 }, 2297 2297 }, 2298 2298 }, 2299 + ShTangledGraphFollow: { 2300 + lexicon: 1, 2301 + id: 'sh.tangled.graph.follow', 2302 + defs: { 2303 + main: { 2304 + type: 'record', 2305 + key: 'tid', 2306 + record: { 2307 + type: 'object', 2308 + required: ['subject', 'createdAt'], 2309 + properties: { 2310 + subject: { 2311 + type: 'string', 2312 + format: 'did', 2313 + }, 2314 + createdAt: { 2315 + type: 'string', 2316 + format: 'datetime', 2317 + }, 2318 + }, 2319 + }, 2320 + }, 2321 + }, 2322 + }, 2323 + ShTangledActorProfile: { 2324 + lexicon: 1, 2325 + id: 'sh.tangled.actor.profile', 2326 + defs: { 2327 + main: { 2328 + type: 'record', 2329 + description: 'A declaration of a Tangled account profile.', 2330 + key: 'literal:self', 2331 + record: { 2332 + type: 'object', 2333 + required: ['bluesky'], 2334 + properties: { 2335 + description: { 2336 + type: 'string', 2337 + description: 'Free-form profile description text.', 2338 + maxGraphemes: 256, 2339 + maxLength: 2560, 2340 + }, 2341 + links: { 2342 + type: 'array', 2343 + minLength: 0, 2344 + maxLength: 5, 2345 + items: { 2346 + type: 'string', 2347 + description: 2348 + 'Any URI, intended for social profiles or websites, can be used to link DIDs/AT-URIs too.', 2349 + }, 2350 + }, 2351 + stats: { 2352 + type: 'array', 2353 + minLength: 0, 2354 + maxLength: 2, 2355 + items: { 2356 + type: 'string', 2357 + description: 'Vanity stats.', 2358 + enum: [ 2359 + 'merged-pull-request-count', 2360 + 'closed-pull-request-count', 2361 + 'open-pull-request-count', 2362 + 'open-issue-count', 2363 + 'closed-issue-count', 2364 + 'repository-count', 2365 + ], 2366 + }, 2367 + }, 2368 + bluesky: { 2369 + type: 'boolean', 2370 + description: 'Include link to this account on Bluesky.', 2371 + }, 2372 + location: { 2373 + type: 'string', 2374 + description: 'Free-form location text.', 2375 + maxGraphemes: 40, 2376 + maxLength: 400, 2377 + }, 2378 + pinnedRepositories: { 2379 + type: 'array', 2380 + description: 2381 + 'Any ATURI, it is up to appviews to validate these fields.', 2382 + minLength: 0, 2383 + maxLength: 6, 2384 + items: { 2385 + type: 'string', 2386 + format: 'at-uri', 2387 + }, 2388 + }, 2389 + }, 2390 + }, 2391 + }, 2392 + }, 2393 + }, 2299 2394 SocialGrainDefs: { 2300 2395 lexicon: 1, 2301 2396 id: 'social.grain.defs', ··· 2349 2444 reason: { 2350 2445 type: 'string', 2351 2446 description: 2352 - "Expected values are 'gallery-favorite', and 'unknown'.", 2353 - knownValues: ['gallery-favorite', 'unknown'], 2447 + 'The reason why this notification was delivered - e.g. your gallery was favd, or you received a new follower.', 2448 + knownValues: ['follow', 'gallery-favorite', 'unknown'], 2354 2449 }, 2355 2450 reasonSubject: { 2356 2451 type: 'string', ··· 2468 2563 }, 2469 2564 }, 2470 2565 }, 2566 + SocialGrainGraphFollow: { 2567 + lexicon: 1, 2568 + id: 'social.grain.graph.follow', 2569 + defs: { 2570 + main: { 2571 + key: 'tid', 2572 + type: 'record', 2573 + record: { 2574 + type: 'object', 2575 + required: ['subject', 'createdAt'], 2576 + properties: { 2577 + subject: { 2578 + type: 'string', 2579 + format: 'did', 2580 + }, 2581 + createdAt: { 2582 + type: 'string', 2583 + format: 'datetime', 2584 + }, 2585 + }, 2586 + }, 2587 + }, 2588 + }, 2589 + }, 2471 2590 SocialGrainFavorite: { 2472 2591 lexicon: 1, 2473 2592 id: 'social.grain.favorite', ··· 2632 2751 type: 'string', 2633 2752 format: 'datetime', 2634 2753 }, 2635 - apertureValue: { 2636 - type: 'integer', 2637 - }, 2638 - brightnessValue: { 2639 - type: 'integer', 2640 - }, 2641 - colorSpace: { 2642 - type: 'integer', 2643 - }, 2644 - contrast: { 2645 - type: 'string', 2646 - enum: ['Normal', 'Soft', 'Hard'], 2647 - }, 2648 - createDate: { 2649 - type: 'string', 2650 - format: 'datetime', 2651 - }, 2652 - customRendered: { 2653 - type: 'string', 2654 - }, 2655 2754 dateTimeOriginal: { 2656 2755 type: 'string', 2657 - format: 'datetime', 2658 2756 }, 2659 - digitalZoomRatio: { 2660 - type: 'integer', 2661 - }, 2662 - exifVersion: { 2757 + exposureTime: { 2663 2758 type: 'string', 2664 2759 }, 2665 - exposureCompensation: { 2666 - type: 'integer', 2667 - }, 2668 - exposureMode: { 2669 - type: 'string', 2670 - }, 2671 - exposureProgram: { 2672 - type: 'string', 2673 - }, 2674 - exposureTime: { 2675 - type: 'integer', 2676 - }, 2677 2760 fNumber: { 2678 - type: 'integer', 2679 - }, 2680 - fileSource: { 2681 2761 type: 'string', 2682 2762 }, 2683 2763 flash: { 2684 2764 type: 'string', 2685 2765 }, 2686 - focalLength: { 2687 - type: 'integer', 2688 - }, 2689 2766 focalLengthIn35mmFormat: { 2690 - type: 'integer', 2691 - }, 2692 - focalPlaneResolutionUnit: { 2693 2767 type: 'string', 2694 2768 }, 2695 - focalPlaneXResolution: { 2696 - type: 'integer', 2697 - }, 2698 - focalPlaneYResolution: { 2699 - type: 'integer', 2700 - }, 2701 2769 iSO: { 2702 2770 type: 'integer', 2703 2771 }, 2704 - lensInfo: { 2705 - type: 'array', 2706 - items: { 2707 - type: 'integer', 2708 - }, 2709 - maxLength: 4, 2710 - }, 2711 - lensModel: { 2772 + lensMake: { 2712 2773 type: 'string', 2713 2774 }, 2714 - lightSource: { 2775 + lensModel: { 2715 2776 type: 'string', 2716 2777 }, 2717 2778 make: { 2718 2779 type: 'string', 2719 2780 }, 2720 - maxApertureValue: { 2721 - type: 'integer', 2722 - }, 2723 - meteringMode: { 2724 - type: 'string', 2725 - }, 2726 2781 model: { 2727 2782 type: 'string', 2728 2783 }, 2729 - modifyDate: { 2730 - type: 'string', 2731 - format: 'datetime', 2732 - }, 2733 - recommendedExposureIndex: { 2734 - type: 'integer', 2735 - }, 2736 - resolutionUnit: { 2737 - type: 'string', 2738 - }, 2739 - saturation: { 2740 - type: 'string', 2741 - }, 2742 - sceneCaptureType: { 2743 - type: 'string', 2744 - }, 2745 - sceneType: { 2746 - type: 'string', 2747 - }, 2748 - sensitivityType: { 2749 - type: 'integer', 2750 - }, 2751 - sharpness: { 2752 - type: 'string', 2753 - }, 2754 - shutterSpeedValue: { 2755 - type: 'integer', 2756 - }, 2757 - software: { 2758 - type: 'string', 2759 - }, 2760 - whiteBalance: { 2761 - type: 'string', 2762 - }, 2763 - xResolution: { 2764 - type: 'integer', 2765 - }, 2766 - yResolution: { 2767 - type: 'integer', 2768 - }, 2769 2784 }, 2770 2785 }, 2771 2786 }, ··· 2776 2791 defs: { 2777 2792 main: { 2778 2793 type: 'record', 2779 - description: 'EXIF metadata for a photo', 2794 + description: 'Basic EXIF metadata for a photo', 2780 2795 key: 'tid', 2781 2796 record: { 2782 2797 type: 'object', ··· 2790 2805 type: 'string', 2791 2806 format: 'datetime', 2792 2807 }, 2793 - apertureValue: { 2794 - type: 'integer', 2795 - }, 2796 - brightnessValue: { 2797 - type: 'integer', 2798 - }, 2799 - colorSpace: { 2800 - type: 'integer', 2801 - }, 2802 - contrast: { 2803 - type: 'string', 2804 - enum: ['Normal', 'Soft', 'Hard'], 2805 - }, 2806 - createDate: { 2807 - type: 'string', 2808 - format: 'datetime', 2809 - }, 2810 - customRendered: { 2811 - type: 'string', 2812 - }, 2813 2808 dateTimeOriginal: { 2814 2809 type: 'string', 2815 2810 format: 'datetime', 2816 2811 }, 2817 - digitalZoomRatio: { 2818 - type: 'integer', 2819 - }, 2820 - exifVersion: { 2821 - type: 'string', 2822 - }, 2823 - exposureCompensation: { 2824 - type: 'integer', 2825 - }, 2826 - exposureMode: { 2827 - type: 'string', 2828 - }, 2829 - exposureProgram: { 2830 - type: 'string', 2831 - }, 2832 2812 exposureTime: { 2833 2813 type: 'integer', 2834 2814 }, 2835 2815 fNumber: { 2836 2816 type: 'integer', 2837 2817 }, 2838 - fileSource: { 2839 - type: 'string', 2840 - }, 2841 2818 flash: { 2842 2819 type: 'string', 2843 2820 }, 2844 - focalLength: { 2845 - type: 'integer', 2846 - }, 2847 2821 focalLengthIn35mmFormat: { 2848 2822 type: 'integer', 2849 2823 }, 2850 - focalPlaneResolutionUnit: { 2851 - type: 'string', 2852 - }, 2853 - focalPlaneXResolution: { 2854 - type: 'integer', 2855 - }, 2856 - focalPlaneYResolution: { 2857 - type: 'integer', 2858 - }, 2859 2824 iSO: { 2860 2825 type: 'integer', 2861 2826 }, 2862 - lensInfo: { 2863 - type: 'array', 2864 - items: { 2865 - type: 'integer', 2866 - }, 2867 - maxLength: 4, 2827 + lensMake: { 2828 + type: 'string', 2868 2829 }, 2869 2830 lensModel: { 2870 2831 type: 'string', 2871 2832 }, 2872 - lightSource: { 2873 - type: 'string', 2874 - }, 2875 2833 make: { 2876 2834 type: 'string', 2877 2835 }, 2878 - maxApertureValue: { 2879 - type: 'integer', 2880 - }, 2881 - meteringMode: { 2882 - type: 'string', 2883 - }, 2884 2836 model: { 2885 2837 type: 'string', 2886 - }, 2887 - modifyDate: { 2888 - type: 'string', 2889 - format: 'datetime', 2890 - }, 2891 - recommendedExposureIndex: { 2892 - type: 'integer', 2893 - }, 2894 - resolutionUnit: { 2895 - type: 'string', 2896 - }, 2897 - saturation: { 2898 - type: 'string', 2899 - }, 2900 - sceneCaptureType: { 2901 - type: 'string', 2902 - }, 2903 - sceneType: { 2904 - type: 'string', 2905 - }, 2906 - sensitivityType: { 2907 - type: 'integer', 2908 - }, 2909 - sharpness: { 2910 - type: 'string', 2911 - }, 2912 - shutterSpeedValue: { 2913 - type: 'integer', 2914 - }, 2915 - software: { 2916 - type: 'string', 2917 - }, 2918 - whiteBalance: { 2919 - type: 'string', 2920 - }, 2921 - xResolution: { 2922 - type: 'integer', 2923 - }, 2924 - yResolution: { 2925 - type: 'integer', 2926 2838 }, 2927 2839 }, 2928 2840 }, ··· 3211 3123 AppBskyActorDefs: 'app.bsky.actor.defs', 3212 3124 AppBskyActorProfile: 'app.bsky.actor.profile', 3213 3125 AppBskyLabelerDefs: 'app.bsky.labeler.defs', 3126 + ShTangledGraphFollow: 'sh.tangled.graph.follow', 3127 + ShTangledActorProfile: 'sh.tangled.actor.profile', 3214 3128 SocialGrainDefs: 'social.grain.defs', 3215 3129 SocialGrainNotificationDefs: 'social.grain.notification.defs', 3216 3130 SocialGrainGalleryItem: 'social.grain.gallery.item', 3217 3131 SocialGrainGalleryDefs: 'social.grain.gallery.defs', 3218 3132 SocialGrainGallery: 'social.grain.gallery', 3133 + SocialGrainGraphFollow: 'social.grain.graph.follow', 3219 3134 SocialGrainFavorite: 'social.grain.favorite', 3220 3135 SocialGrainActorDefs: 'social.grain.actor.defs', 3221 3136 SocialGrainActorProfile: 'social.grain.actor.profile',
+47
__generated__/types/sh/tangled/actor/profile.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'sh.tangled.actor.profile' 16 + 17 + export interface Record { 18 + $type: 'sh.tangled.actor.profile' 19 + /** Free-form profile description text. */ 20 + description?: string 21 + links?: string[] 22 + stats?: ( 23 + | 'merged-pull-request-count' 24 + | 'closed-pull-request-count' 25 + | 'open-pull-request-count' 26 + | 'open-issue-count' 27 + | 'closed-issue-count' 28 + | 'repository-count' 29 + )[] 30 + /** Include link to this account on Bluesky. */ 31 + bluesky: boolean 32 + /** Free-form location text. */ 33 + location?: string 34 + /** Any ATURI, it is up to appviews to validate these fields. */ 35 + pinnedRepositories?: string[] 36 + [k: string]: unknown 37 + } 38 + 39 + const hashRecord = 'main' 40 + 41 + export function isRecord<V>(v: V) { 42 + return is$typed(v, id, hashRecord) 43 + } 44 + 45 + export function validateRecord<V>(v: V) { 46 + return validate<Record & V>(v, id, hashRecord, true) 47 + }
+32
__generated__/types/sh/tangled/graph/follow.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'sh.tangled.graph.follow' 16 + 17 + export interface Record { 18 + $type: 'sh.tangled.graph.follow' 19 + subject: string 20 + createdAt: string 21 + [k: string]: unknown 22 + } 23 + 24 + const hashRecord = 'main' 25 + 26 + export function isRecord<V>(v: V) { 27 + return is$typed(v, id, hashRecord) 28 + } 29 + 30 + export function validateRecord<V>(v: V) { 31 + return validate<Record & V>(v, id, hashRecord, true) 32 + }
+32
__generated__/types/social/grain/graph/follow.ts
··· 1 + /** 2 + * GENERATED CODE - DO NOT MODIFY 3 + */ 4 + import { type ValidationResult, BlobRef } from "npm:@atproto/lexicon" 5 + import { CID } from "npm:multiformats/cid" 6 + import { validate as _validate } from '../../../../lexicons.ts' 7 + import { 8 + type $Typed, 9 + is$typed as _is$typed, 10 + type OmitKey, 11 + } from '../../../../util.ts' 12 + 13 + const is$typed = _is$typed, 14 + validate = _validate 15 + const id = 'social.grain.graph.follow' 16 + 17 + export interface Record { 18 + $type: 'social.grain.graph.follow' 19 + subject: string 20 + createdAt: string 21 + [k: string]: unknown 22 + } 23 + 24 + const hashRecord = 'main' 25 + 26 + export function isRecord<V>(v: V) { 27 + return is$typed(v, id, hashRecord) 28 + } 29 + 30 + export function validateRecord<V>(v: V) { 31 + return validate<Record & V>(v, id, hashRecord, true) 32 + }
+2 -2
__generated__/types/social/grain/notification/defs.ts
··· 20 20 uri: string 21 21 cid: string 22 22 author: SocialGrainActorDefs.ProfileView 23 - /** Expected values are 'gallery-favorite', and 'unknown'. */ 24 - reason: 'gallery-favorite' | 'unknown' | (string & {}) 23 + /** The reason why this notification was delivered - e.g. your gallery was favd, or you received a new follower. */ 24 + reason: 'follow' | 'gallery-favorite' | 'unknown' | (string & {}) 25 25 reasonSubject?: string 26 26 record: { [_ in string]: unknown } 27 27 isRead: boolean
+4 -36
__generated__/types/social/grain/photo/defs.ts
··· 45 45 cid?: string 46 46 photo: string 47 47 createdAt: string 48 - apertureValue?: number 49 - brightnessValue?: number 50 - colorSpace?: number 51 - contrast?: 'Normal' | 'Soft' | 'Hard' 52 - createDate?: string 53 - customRendered?: string 54 48 dateTimeOriginal?: string 55 - digitalZoomRatio?: number 56 - exifVersion?: string 57 - exposureCompensation?: number 58 - exposureMode?: string 59 - exposureProgram?: string 60 - exposureTime?: number 61 - fNumber?: number 62 - fileSource?: string 49 + exposureTime?: string 50 + fNumber?: string 63 51 flash?: string 64 - focalLength?: number 65 - focalLengthIn35mmFormat?: number 66 - focalPlaneResolutionUnit?: string 67 - focalPlaneXResolution?: number 68 - focalPlaneYResolution?: number 52 + focalLengthIn35mmFormat?: string 69 53 iSO?: number 70 - lensInfo?: number[] 54 + lensMake?: string 71 55 lensModel?: string 72 - lightSource?: string 73 56 make?: string 74 - maxApertureValue?: number 75 - meteringMode?: string 76 57 model?: string 77 - modifyDate?: string 78 - recommendedExposureIndex?: number 79 - resolutionUnit?: string 80 - saturation?: string 81 - sceneCaptureType?: string 82 - sceneType?: string 83 - sensitivityType?: number 84 - sharpness?: string 85 - shutterSpeedValue?: number 86 - software?: string 87 - whiteBalance?: string 88 - xResolution?: number 89 - yResolution?: number 90 58 } 91 59 92 60 const hashExifView = 'exifView'
+1 -33
__generated__/types/social/grain/photo/exif.ts
··· 18 18 $type: 'social.grain.photo.exif' 19 19 photo: string 20 20 createdAt: string 21 - apertureValue?: number 22 - brightnessValue?: number 23 - colorSpace?: number 24 - contrast?: 'Normal' | 'Soft' | 'Hard' 25 - createDate?: string 26 - customRendered?: string 27 21 dateTimeOriginal?: string 28 - digitalZoomRatio?: number 29 - exifVersion?: string 30 - exposureCompensation?: number 31 - exposureMode?: string 32 - exposureProgram?: string 33 22 exposureTime?: number 34 23 fNumber?: number 35 - fileSource?: string 36 24 flash?: string 37 - focalLength?: number 38 25 focalLengthIn35mmFormat?: number 39 - focalPlaneResolutionUnit?: string 40 - focalPlaneXResolution?: number 41 - focalPlaneYResolution?: number 42 26 iSO?: number 43 - lensInfo?: number[] 27 + lensMake?: string 44 28 lensModel?: string 45 - lightSource?: string 46 29 make?: string 47 - maxApertureValue?: number 48 - meteringMode?: string 49 30 model?: string 50 - modifyDate?: string 51 - recommendedExposureIndex?: number 52 - resolutionUnit?: string 53 - saturation?: string 54 - sceneCaptureType?: string 55 - sceneType?: string 56 - sensitivityType?: number 57 - sharpness?: string 58 - shutterSpeedValue?: number 59 - software?: string 60 - whiteBalance?: string 61 - xResolution?: number 62 - yResolution?: number 63 31 [k: string]: unknown 64 32 } 65 33
+29
build.ts
··· 1 + import * as esbuild from "esbuild"; 2 + 3 + import { denoPlugins } from "@luca/esbuild-deno-loader"; 4 + 5 + console.log("Bundling js..."); 6 + 7 + await esbuild.build({ 8 + plugins: [...denoPlugins()], 9 + entryPoints: ["./src/static/mod.ts"], 10 + outfile: "./static/app.esm.js", 11 + bundle: true, 12 + format: "esm", 13 + sourcemap: Deno.env.get("DEV") === "true" ? "linked" : false, 14 + minify: Deno.env.get("DEV") !== "true", 15 + }); 16 + 17 + const command = new Deno.Command("du", { 18 + args: ["-h", "./static/app.esm.js"], 19 + stdout: "piped", 20 + stderr: "piped", 21 + }); 22 + const { code, stdout, stderr } = await command.output(); 23 + if (code === 0) { 24 + console.log(new TextDecoder().decode(stdout)); 25 + } else { 26 + console.error(new TextDecoder().decode(stderr)); 27 + } 28 + 29 + esbuild.stop();
+15 -4
deno.json
··· 2 2 "imports": { 3 3 "$lexicon/": "./__generated__/", 4 4 "@atproto/syntax": "npm:@atproto/syntax@^0.4.0", 5 - "@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.29", 5 + "@bigmoves/bff": "jsr:@bigmoves/bff@0.3.0-beta.32", 6 + "@luca/esbuild-deno-loader": "jsr:@luca/esbuild-deno-loader@^0.11.1", 6 7 "@std/http": "jsr:@std/http@^1.0.17", 7 8 "@std/path": "jsr:@std/path@^1.0.9", 8 9 "@tailwindcss/cli": "npm:@tailwindcss/cli@^4.1.4", 9 10 "date-fns": "npm:date-fns@^4.1.0", 11 + "esbuild": "npm:esbuild@^0.25.5", 12 + "exifr": "npm:exifr@^7.1.3", 13 + "htmx.org": "npm:htmx.org@^1.9.12", 14 + "hyperscript.org": "npm:hyperscript.org@^0.9.14", 10 15 "popmotion": "npm:popmotion@^11.0.5", 11 16 "preact": "npm:preact@^10.26.5", 17 + "sortablejs": "npm:sortablejs@^1.15.6", 12 18 "tailwindcss": "npm:tailwindcss@^4.1.4", 13 19 "typed-htmx": "npm:typed-htmx@^0.3.1" 14 20 }, 21 + "patch": [ 22 + "../bff/packages/bff" 23 + ], 15 24 "tasks": { 16 25 "start": "deno run -A ./src/main.tsx", 17 - "dev": "deno run \"dev:*\"", 26 + "build": "rm -f ./static/app.esm.js.map && deno run -A build.ts", 27 + "dev": "DEV=true deno run \"dev:*\"", 28 + "dev:build": "deno -A --watch=src/static/ build.ts", 18 29 "dev:server": "deno run -A --env-file=.env --watch ./src/main.tsx", 19 30 "dev:tailwind": "deno run -A --node-modules-dir npm:@tailwindcss/cli -i ./src/input.css -o ./static/styles.css --watch", 20 - "sync": "deno run -A --env=.env jsr:@bigmoves/bff-cli@0.3.0-beta.29 sync --collections=social.grain.gallery,social.grain.actor.profile,social.grain.photo,social.grain.photo.exif,social.grain.favorite,social.grain.gallery.item --external-collections=app.bsky.actor.profile,app.bsky.graph.follow", 21 - "codegen": "deno run -A jsr:@bigmoves/bff-cli@0.3.0-beta.29 lexgen" 31 + "sync": "deno run -A --env=.env jsr:@bigmoves/bff-cli@0.3.0-beta.30 sync --collections=social.grain.gallery,social.grain.actor.profile,social.grain.photo,social.grain.favorite,social.grain.gallery.item,social.grain.graph.follow --external-collections=app.bsky.actor.profile,app.bsky.graph.follow,sh.tangled.graph.follow,sh.tangled.actor.profile", 32 + "codegen": "deno run -A jsr:@bigmoves/bff-cli@0.3.0-beta.30 lexgen" 22 33 }, 23 34 "compilerOptions": { 24 35 "jsx": "precompile",
+709 -105
deno.lock
··· 2 2 "version": "5", 3 3 "specifiers": { 4 4 "jsr:@bigmoves/atproto-oauth-client@0.2": "0.2.0", 5 - "jsr:@bigmoves/bff@0.3.0-beta.29": "0.3.0-beta.29", 5 + "jsr:@bigmoves/bff@0.3.0-beta.32": "0.3.0-beta.32", 6 + "jsr:@deno/gfm@0.10": "0.10.0", 7 + "jsr:@denosaurs/emoji@0.3": "0.3.1", 8 + "jsr:@luca/esbuild-deno-loader@~0.11.1": "0.11.1", 9 + "jsr:@std/assert@^1.0.12": "1.0.13", 6 10 "jsr:@std/assert@^1.0.13": "1.0.13", 7 - "jsr:@std/cli@^1.0.18": "1.0.18", 11 + "jsr:@std/async@^1.0.12": "1.0.12", 12 + "jsr:@std/bytes@^1.0.2": "1.0.6", 13 + "jsr:@std/cache@0.2": "0.2.0", 14 + "jsr:@std/cli@^1.0.16": "1.0.19", 15 + "jsr:@std/cli@^1.0.18": "1.0.19", 16 + "jsr:@std/data-structures@^1.0.6": "1.0.7", 8 17 "jsr:@std/encoding@^1.0.10": "1.0.10", 18 + "jsr:@std/encoding@^1.0.5": "1.0.10", 9 19 "jsr:@std/fmt@^1.0.8": "1.0.8", 20 + "jsr:@std/fs@^1.0.16": "1.0.17", 10 21 "jsr:@std/html@^1.0.4": "1.0.4", 11 22 "jsr:@std/http@^1.0.13": "1.0.17", 12 23 "jsr:@std/http@^1.0.17": "1.0.17", 13 24 "jsr:@std/internal@^1.0.6": "1.0.8", 14 25 "jsr:@std/media-types@^1.1.0": "1.1.0", 15 26 "jsr:@std/net@^1.0.4": "1.0.4", 27 + "jsr:@std/path@^1.0.6": "1.1.0", 16 28 "jsr:@std/path@^1.0.8": "1.1.0", 17 29 "jsr:@std/path@^1.0.9": "1.1.0", 18 30 "jsr:@std/path@^1.1.0": "1.1.0", 19 31 "jsr:@std/streams@^1.0.9": "1.0.9", 20 - "npm:@atproto-labs/handle-resolver-node@~0.1.14": "0.1.15", 32 + "jsr:@std/testing@^1.0.11": "1.0.11", 33 + "npm:@atproto-labs/handle-resolver-node@~0.1.14": "0.1.16", 21 34 "npm:@atproto-labs/simple-store@~0.1.2": "0.1.2", 22 - "npm:@atproto/api@~0.15.7": "0.15.7", 35 + "npm:@atproto/api@~0.15.7": "0.15.12", 23 36 "npm:@atproto/common@~0.4.10": "0.4.11", 24 37 "npm:@atproto/identity@~0.4.7": "0.4.8", 25 38 "npm:@atproto/jwk@0.1.4": "0.1.4", 26 - "npm:@atproto/lex-cli@*": "0.8.1", 39 + "npm:@atproto/lex-cli@*": "0.8.2", 27 40 "npm:@atproto/lexicon@*": "0.4.11", 28 41 "npm:@atproto/lexicon@~0.4.11": "0.4.11", 29 - "npm:@atproto/oauth-client@~0.3.13": "0.3.16", 30 - "npm:@atproto/oauth-types@~0.2.4": "0.2.7", 42 + "npm:@atproto/oauth-client@~0.3.13": "0.3.20", 43 + "npm:@atproto/oauth-types@~0.2.4": "0.2.8", 31 44 "npm:@atproto/syntax@0.4": "0.4.0", 32 - "npm:@atproto/xrpc-server@*": "0.7.18", 33 - "npm:@tailwindcss/cli@*": "4.1.7", 34 - "npm:@tailwindcss/cli@^4.1.4": "4.1.7", 45 + "npm:@atproto/xrpc-server@*": "0.7.19", 46 + "npm:@tailwindcss/cli@*": "4.1.8", 47 + "npm:@tailwindcss/cli@^4.0.12": "4.1.8", 48 + "npm:@tailwindcss/cli@^4.1.3": "4.1.8", 49 + "npm:@tailwindcss/cli@^4.1.4": "4.1.8", 35 50 "npm:@types/node@*": "22.15.15", 51 + "npm:buffer@^6.0.3": "6.0.3", 36 52 "npm:clsx@^2.1.1": "2.1.1", 37 53 "npm:date-fns@^4.1.0": "4.1.0", 54 + "npm:esbuild@~0.25.5": "0.25.5", 55 + "npm:exif-esm@^1.0.1": "1.0.1", 56 + "npm:exifr@^7.1.3": "7.1.3", 57 + "npm:github-slugger@2": "2.0.0", 58 + "npm:he@^1.2.0": "1.2.0", 59 + "npm:htmx.org@^1.9.12": "1.9.12", 60 + "npm:hyperscript.org@~0.9.14": "0.9.14", 38 61 "npm:jose@5.9.6": "5.9.6", 39 - "npm:multiformats@*": "13.3.4", 40 - "npm:multiformats@^13.3.2": "13.3.4", 62 + "npm:katex@0.16": "0.16.22", 63 + "npm:marked-alert@2": "2.1.2_marked@12.0.2", 64 + "npm:marked-footnote@^1.2.0": "1.2.4_marked@12.0.2", 65 + "npm:marked-gfm-heading-id@^3.1.0": "3.2.0_marked@12.0.2", 66 + "npm:marked@12": "12.0.2", 67 + "npm:multiformats@*": "13.3.6", 68 + "npm:multiformats@^13.3.2": "13.3.6", 41 69 "npm:popmotion@^11.0.5": "11.0.5", 42 - "npm:preact-render-to-string@^6.5.13": "6.5.13_preact@10.26.6", 43 - "npm:preact@^10.26.5": "10.26.6", 70 + "npm:preact-render-to-string@^6.5.13": "6.5.13_preact@10.26.8", 71 + "npm:preact@^10.26.5": "10.26.8", 72 + "npm:prismjs@^1.29.0": "1.30.0", 73 + "npm:sanitize-html@^2.13.0": "2.17.0", 74 + "npm:sharp@~0.34.1": "0.34.1", 75 + "npm:sortablejs@^1.15.6": "1.15.6", 44 76 "npm:tailwind-merge@^3.2.0": "3.3.0", 45 - "npm:tailwindcss@^4.1.4": "4.1.7", 77 + "npm:tailwindcss@^4.0.12": "4.1.8", 78 + "npm:tailwindcss@^4.1.3": "4.1.8", 79 + "npm:tailwindcss@^4.1.4": "4.1.8", 46 80 "npm:typed-htmx@~0.3.1": "0.3.1" 47 81 }, 48 82 "jsr": { ··· 65 99 "npm:tailwind-merge" 66 100 ] 67 101 }, 68 - "@bigmoves/bff@0.3.0-beta.29": { 69 - "integrity": "dd96a6601940ccf4be342bb89c87d5f0fae2f366341fbaa0722f8d17ebb3c047", 102 + "@bigmoves/bff@0.3.0-beta.32": { 103 + "integrity": "d33581157c6d52bd9ecccdbcb090559377de71d28137ce7fdf3882740390a389", 70 104 "dependencies": [ 71 105 "jsr:@bigmoves/atproto-oauth-client", 72 - "jsr:@std/assert", 106 + "jsr:@std/assert@^1.0.13", 73 107 "jsr:@std/fmt", 74 108 "jsr:@std/http@^1.0.13", 75 109 "jsr:@std/path@^1.0.8", ··· 86 120 "npm:tailwind-merge" 87 121 ] 88 122 }, 123 + "@deno/gfm@0.10.0": { 124 + "integrity": "51708205e3559a4aeb6afb29d07c5bfafe7941f91bb360351ef6621de9a39527", 125 + "dependencies": [ 126 + "jsr:@denosaurs/emoji", 127 + "npm:github-slugger", 128 + "npm:he", 129 + "npm:katex", 130 + "npm:marked", 131 + "npm:marked-alert", 132 + "npm:marked-footnote", 133 + "npm:marked-gfm-heading-id", 134 + "npm:prismjs", 135 + "npm:sanitize-html" 136 + ] 137 + }, 138 + "@denosaurs/emoji@0.3.1": { 139 + "integrity": "b0aed5f55dec99e83da7c9637fe0a36d1d6252b7c99deaaa3fc5dea3fcf3da8b" 140 + }, 141 + "@luca/esbuild-deno-loader@0.11.1": { 142 + "integrity": "dc020d16d75b591f679f6b9288b10f38bdb4f24345edb2f5732affa1d9885267", 143 + "dependencies": [ 144 + "jsr:@std/bytes", 145 + "jsr:@std/encoding@^1.0.5", 146 + "jsr:@std/path@^1.0.6" 147 + ] 148 + }, 89 149 "@std/assert@1.0.13": { 90 150 "integrity": "ae0d31e41919b12c656c742b22522c32fb26ed0cba32975cb0de2a273cb68b29", 91 151 "dependencies": [ 92 152 "jsr:@std/internal" 93 153 ] 94 154 }, 95 - "@std/cli@1.0.18": { 96 - "integrity": "33846eab6a7cac52156cc105a798451df06965693606e4668adfe0436a155fd7" 155 + "@std/async@1.0.12": { 156 + "integrity": "d1bfcec459e8012846fe4e38dfc4241ab23240ecda3d8d6dfcf6d81a632e803d" 157 + }, 158 + "@std/bytes@1.0.6": { 159 + "integrity": "f6ac6adbd8ccd99314045f5703e23af0a68d7f7e58364b47d2c7f408aeb5820a" 160 + }, 161 + "@std/cache@0.2.0": { 162 + "integrity": "63a2ccd5a9e7c03e430f7d34dfcfd0d0cfc90731a1eaf8208f4c66e418fc3035" 163 + }, 164 + "@std/cli@1.0.19": { 165 + "integrity": "b3601a54891f89f3f738023af11960c4e6f7a45dc76cde39a6861124cba79e88" 166 + }, 167 + "@std/data-structures@1.0.7": { 168 + "integrity": "16932d2c8d281f65eaaa2209af2473209881e33b1ced54cd1b015e7b4cdbb0d2" 97 169 }, 98 170 "@std/encoding@1.0.10": { 99 171 "integrity": "8783c6384a2d13abd5e9e87a7ae0520a30e9f56aeeaa3bdf910a3eaaf5c811a1" ··· 101 173 "@std/fmt@1.0.8": { 102 174 "integrity": "71e1fc498787e4434d213647a6e43e794af4fd393ef8f52062246e06f7e372b7" 103 175 }, 176 + "@std/fs@1.0.17": { 177 + "integrity": "1c00c632677c1158988ef7a004cb16137f870aafdb8163b9dce86ec652f3952b", 178 + "dependencies": [ 179 + "jsr:@std/path@^1.0.9" 180 + ] 181 + }, 104 182 "@std/html@1.0.4": { 105 183 "integrity": "eff3497c08164e6ada49b7f81a28b5108087033823153d065e3f89467dd3d50e" 106 184 }, 107 185 "@std/http@1.0.17": { 108 186 "integrity": "98aec8ab4080d95c21f731e3008f69c29c5012d12f1b4e553f85935db601569f", 109 187 "dependencies": [ 110 - "jsr:@std/cli", 111 - "jsr:@std/encoding", 188 + "jsr:@std/cli@^1.0.18", 189 + "jsr:@std/encoding@^1.0.10", 112 190 "jsr:@std/fmt", 113 191 "jsr:@std/html", 114 192 "jsr:@std/media-types", ··· 131 209 }, 132 210 "@std/streams@1.0.9": { 133 211 "integrity": "a9d26b1988cdd7aa7b1f4b51e1c36c1557f3f252880fa6cc5b9f37078b1a5035" 212 + }, 213 + "@std/testing@1.0.11": { 214 + "integrity": "12b3db12d34f0f385a26248933bde766c0f8c5ad8b6ab34d4d38f528ab852f48", 215 + "dependencies": [ 216 + "jsr:@std/assert@^1.0.12", 217 + "jsr:@std/async", 218 + "jsr:@std/data-structures", 219 + "jsr:@std/fs", 220 + "jsr:@std/internal", 221 + "jsr:@std/path@^1.0.8" 222 + ] 134 223 } 135 224 }, 136 225 "npm": { ··· 141 230 "@jridgewell/trace-mapping" 142 231 ] 143 232 }, 144 - "@atproto-labs/did-resolver@0.1.12": { 145 - "integrity": "sha512-criWN7o21C5TFsauB+bGTlkqqerOU6gT2TbxdQVgZUWqNcfazUmUjT4gJAY02i+O4d3QmZa27fv9CcaRKWkSug==", 233 + "@atproto-labs/did-resolver@0.1.13": { 234 + "integrity": "sha512-DG3YNaCKc6PAIv1Gsz3E1Kufw2t14OBxe4LdKK7KKLCNoex51hm+A5yMevShe3BSll+QosqWYIEgkPSc5xBoGQ==", 146 235 "dependencies": [ 147 236 "@atproto-labs/fetch", 148 237 "@atproto-labs/pipe", ··· 152 241 "zod" 153 242 ] 154 243 }, 155 - "@atproto-labs/fetch-node@0.1.8": { 156 - "integrity": "sha512-OOTIhZNPEDDm7kaYU8iYRgzM+D5n3mP2iiBSyKuLakKTaZBL5WwYlUsJVsqX26SnUXtGEroOJEVJ6f66OcG80w==", 244 + "@atproto-labs/fetch-node@0.1.9": { 245 + "integrity": "sha512-8sHDDXZEzQptLu8ddUU/8U+THS6dumgPynVX0/1PjUYd4S/FWyPcz6yMIiVChTfzKnZvYRRz47+qvOKhydrHQw==", 157 246 "dependencies": [ 158 247 "@atproto-labs/fetch", 159 248 "@atproto-labs/pipe", 160 249 "ipaddr.js@2.2.0", 161 - "psl", 162 250 "undici" 163 251 ] 164 252 }, 165 - "@atproto-labs/fetch@0.2.2": { 166 - "integrity": "sha512-QyafkedbFeVaN20DYUpnY2hcArYxjdThPXbYMqOSoZhcvkrUqaw4xDND4wZB5TBD9cq2yqe9V6mcw9P4XQKQuQ==", 253 + "@atproto-labs/fetch@0.2.3": { 254 + "integrity": "sha512-NZtbJOCbxKUFRFKMpamT38PUQMY0hX0p7TG5AEYOPhZKZEP7dHZ1K2s1aB8MdVH0qxmqX7nQleNrrvLf09Zfdw==", 167 255 "dependencies": [ 168 256 "@atproto-labs/pipe" 169 257 ] 170 258 }, 171 - "@atproto-labs/handle-resolver-node@0.1.15": { 172 - "integrity": "sha512-krl9KqfCCrGID35VAAHKBIiXOxe3gYxAtOJLYpZc5cOPFwnvPlAdhTYZLIc1dJRKDayi8gh6Q4XZRDv7i8dryg==", 259 + "@atproto-labs/handle-resolver-node@0.1.16": { 260 + "integrity": "sha512-i2F989zjyC7b/odrV3/tOpIT1IDIxR3F0khPG4REfOWcmJ89QcP8BiejJ6KFJk3hbTJHq6X9/pTG1vesCvyIKA==", 173 261 "dependencies": [ 174 262 "@atproto-labs/fetch-node", 175 263 "@atproto-labs/handle-resolver", ··· 185 273 "zod" 186 274 ] 187 275 }, 188 - "@atproto-labs/identity-resolver@0.1.16": { 189 - "integrity": "sha512-pFrtKT49cYBhCDd2U1t/CcUBiMmQzaNQxh8oSkDUlGs/K3P8rJFTAGAMm8UjokfGEKwF4hX9oo7O8Kn+GkyExw==", 276 + "@atproto-labs/identity-resolver@0.1.17": { 277 + "integrity": "sha512-EaH9Lm8M85IKRx+oWZ4tppYRVH8u+MYpEz1kjzYeM3ttZ2xcqKVmYHiOIgd5YPCVV2EIfXKnlM4soHQ+rZ1c6A==", 190 278 "dependencies": [ 191 279 "@atproto-labs/did-resolver", 192 280 "@atproto-labs/handle-resolver", 193 281 "@atproto/syntax" 194 282 ] 195 283 }, 196 - "@atproto-labs/pipe@0.1.0": { 197 - "integrity": "sha512-ghOqHFyJlQVFPESzlVHjKroP0tPzbmG5Jms0dNI9yLDEfL8xp4OFPWLX4f6T8mRq69wWs4nIDM3sSsFbFqLa1w==" 284 + "@atproto-labs/pipe@0.1.1": { 285 + "integrity": "sha512-hdNw2oUs2B6BN1lp+32pF7cp8EMKuIN5Qok2Vvv/aOpG/3tNSJ9YkvfI0k6Zd188LeDDYRUpYpxcoFIcGH/FNg==" 198 286 }, 199 287 "@atproto-labs/simple-store-memory@0.1.3": { 200 288 "integrity": "sha512-jkitT9+AtU+0b28DoN92iURLaCt/q/q4yX8q6V+9LSwYlUTqKoj/5NFKvF7x6EBuG+gpUdlcycbH7e60gjOhRQ==", ··· 209 297 "@atproto-labs/simple-store@0.2.0": { 210 298 "integrity": "sha512-0bRbAlI8Ayh03wRwncAMEAyUKtZ+AuTS1jgPrfym1WVOAOiottI/ZmgccqLl6w5MbxVcClNQF7WYGKvGwGoIhA==" 211 299 }, 212 - "@atproto/api@0.15.7": { 213 - "integrity": "sha512-YRETLcOwDCYfGs7Sl9ObqPwhOlVWrPkw4f1AYGIrXLQS58WHe/vz1lZbqOqMsC6gvCnyZnOuKlhsRHZ14rBLzg==", 300 + "@atproto/api@0.15.12": { 301 + "integrity": "sha512-51IHenZMA+Ekfe2OlZL/mTFqvZQU93jI4xsLvTFhGc4tSQYCHV9r/AJTANPZLFrhm9GfWZ0n90r/9IQl9eicjg==", 214 302 "dependencies": [ 215 303 "@atproto/common-web", 216 304 "@atproto/lexicon", ··· 270 358 "zod" 271 359 ] 272 360 }, 273 - "@atproto/jwk@0.1.5": { 274 - "integrity": "sha512-OzZFLhX41TOcMeanP3aZlL5bLeaUIZT15MI4aU5cwflNq/rwpGOpz3uwDjZc8ytgUjuTQ8LabSz5jMmwoTSWFg==", 361 + "@atproto/jwk@0.2.0": { 362 + "integrity": "sha512-foOxExbw04XCaoLaGdv9BQj0Ac7snZsk6IpQjOsjYatf+i62Pi9bUkZ0MAoA75HPk8ZmKoDnbA60uBMmiOPPHQ==", 275 363 "dependencies": [ 276 364 "multiformats@9.9.0", 277 365 "zod" 278 366 ] 279 367 }, 280 - "@atproto/lex-cli@0.8.1": { 281 - "integrity": "sha512-0Ns6kX46gum2jU8bpvWCSVqoYhjmJrOGR/NLfLHgPbJtBlyxMGQAxqpy1x6zOi6SkkRGWYhHvRfr5J8lTHbxjA==", 368 + "@atproto/lex-cli@0.8.2": { 369 + "integrity": "sha512-yNQFYBV3tBBLnVrRUtUBlx/WIF4ypMFsvOsCLjA7pHL1SyW9JbczSEAoiNtoDmPc4UXCjMtXggz0ovBG8lynNA==", 282 370 "dependencies": [ 283 371 "@atproto/lexicon", 284 372 "@atproto/syntax", 285 373 "chalk", 286 - "commander", 374 + "commander@9.5.0", 287 375 "prettier", 288 376 "ts-morph", 289 377 "yesno", ··· 301 389 "zod" 302 390 ] 303 391 }, 304 - "@atproto/oauth-client@0.3.16": { 305 - "integrity": "sha512-AEtGLOXRJzBcBa8LyUXwFf/M7cZc+CcOBjLsiqmVQriSwccfyTkALgiyM0UcRHJqlwtLPuf9RYtgKPc8rW5F/w==", 392 + "@atproto/oauth-client@0.3.20": { 393 + "integrity": "sha512-aclxN2vD5ldc9YiQtX6z4S5g5lU12sz297gzuTxBFUYiS3bh7dxU8j/cbD/BDvXIiVRzzzc5kOgE1CgT9XZ2mg==", 306 394 "dependencies": [ 307 395 "@atproto-labs/did-resolver", 308 396 "@atproto-labs/fetch", ··· 311 399 "@atproto-labs/simple-store@0.2.0", 312 400 "@atproto-labs/simple-store-memory", 313 401 "@atproto/did", 314 - "@atproto/jwk@0.1.5", 402 + "@atproto/jwk@0.2.0", 315 403 "@atproto/oauth-types", 316 404 "@atproto/xrpc", 317 405 "multiformats@9.9.0", 318 406 "zod" 319 407 ] 320 408 }, 321 - "@atproto/oauth-types@0.2.7": { 322 - "integrity": "sha512-2SlDveiSI0oowC+sfuNd/npV8jw/FhokSS26qyUyldTg1g9ZlhxXUfMP4IZOPeZcVn9EszzQRHs1H9ZJqVQIew==", 409 + "@atproto/oauth-types@0.2.8": { 410 + "integrity": "sha512-xcYI2JmhrWwscePDoaKeDawVCCZkcvBqrBFMpMk4gf/OujH0pNSKBD/aWsayc6WvujVbTqwrG2hwPLfRqzJbwg==", 323 411 "dependencies": [ 324 - "@atproto/jwk@0.1.5", 412 + "@atproto/jwk@0.2.0", 325 413 "zod" 326 414 ] 327 415 }, 328 416 "@atproto/syntax@0.4.0": { 329 417 "integrity": "sha512-b9y5ceHS8YKOfP3mdKmwAx5yVj9294UN7FG2XzP6V5aKUdFazEYRnR9m5n5ZQFKa3GNvz7de9guZCJ/sUTcOAA==" 330 418 }, 331 - "@atproto/xrpc-server@0.7.18": { 332 - "integrity": "sha512-kjlAsI+UNbbm6AK3Y5Hb4BJ7VQHNKiYYu2kX5vhZJZHO8qfO40GPYYb/2TknZV8IG6fDPBQhUpcDRolI86sgag==", 419 + "@atproto/xrpc-server@0.7.19": { 420 + "integrity": "sha512-YSCl/tU2NDykgDYslFSOYCr96esUgDwncFiADKL59/fyIFPLoT0qY8Uq/budpxUh0qPzjow4HHgVWESOaOpUmA==", 333 421 "dependencies": [ 334 422 "@atproto/common", 335 423 "@atproto/crypto", ··· 401 489 "tslib@2.8.1" 402 490 ] 403 491 }, 492 + "@esbuild/aix-ppc64@0.25.5": { 493 + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", 494 + "os": ["aix"], 495 + "cpu": ["ppc64"] 496 + }, 497 + "@esbuild/android-arm64@0.25.5": { 498 + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", 499 + "os": ["android"], 500 + "cpu": ["arm64"] 501 + }, 502 + "@esbuild/android-arm@0.25.5": { 503 + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", 504 + "os": ["android"], 505 + "cpu": ["arm"] 506 + }, 507 + "@esbuild/android-x64@0.25.5": { 508 + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", 509 + "os": ["android"], 510 + "cpu": ["x64"] 511 + }, 512 + "@esbuild/darwin-arm64@0.25.5": { 513 + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", 514 + "os": ["darwin"], 515 + "cpu": ["arm64"] 516 + }, 517 + "@esbuild/darwin-x64@0.25.5": { 518 + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", 519 + "os": ["darwin"], 520 + "cpu": ["x64"] 521 + }, 522 + "@esbuild/freebsd-arm64@0.25.5": { 523 + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", 524 + "os": ["freebsd"], 525 + "cpu": ["arm64"] 526 + }, 527 + "@esbuild/freebsd-x64@0.25.5": { 528 + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", 529 + "os": ["freebsd"], 530 + "cpu": ["x64"] 531 + }, 532 + "@esbuild/linux-arm64@0.25.5": { 533 + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", 534 + "os": ["linux"], 535 + "cpu": ["arm64"] 536 + }, 537 + "@esbuild/linux-arm@0.25.5": { 538 + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", 539 + "os": ["linux"], 540 + "cpu": ["arm"] 541 + }, 542 + "@esbuild/linux-ia32@0.25.5": { 543 + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", 544 + "os": ["linux"], 545 + "cpu": ["ia32"] 546 + }, 547 + "@esbuild/linux-loong64@0.25.5": { 548 + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", 549 + "os": ["linux"], 550 + "cpu": ["loong64"] 551 + }, 552 + "@esbuild/linux-mips64el@0.25.5": { 553 + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", 554 + "os": ["linux"], 555 + "cpu": ["mips64el"] 556 + }, 557 + "@esbuild/linux-ppc64@0.25.5": { 558 + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", 559 + "os": ["linux"], 560 + "cpu": ["ppc64"] 561 + }, 562 + "@esbuild/linux-riscv64@0.25.5": { 563 + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", 564 + "os": ["linux"], 565 + "cpu": ["riscv64"] 566 + }, 567 + "@esbuild/linux-s390x@0.25.5": { 568 + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", 569 + "os": ["linux"], 570 + "cpu": ["s390x"] 571 + }, 572 + "@esbuild/linux-x64@0.25.5": { 573 + "integrity": "sha512-uhj8N2obKTE6pSZ+aMUbqq+1nXxNjZIIjCjGLfsWvVpy7gKCOL6rsY1MhRh9zLtUtAI7vpgLMK6DxjO8Qm9lJw==", 574 + "os": ["linux"], 575 + "cpu": ["x64"] 576 + }, 577 + "@esbuild/netbsd-arm64@0.25.5": { 578 + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", 579 + "os": ["netbsd"], 580 + "cpu": ["arm64"] 581 + }, 582 + "@esbuild/netbsd-x64@0.25.5": { 583 + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", 584 + "os": ["netbsd"], 585 + "cpu": ["x64"] 586 + }, 587 + "@esbuild/openbsd-arm64@0.25.5": { 588 + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", 589 + "os": ["openbsd"], 590 + "cpu": ["arm64"] 591 + }, 592 + "@esbuild/openbsd-x64@0.25.5": { 593 + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", 594 + "os": ["openbsd"], 595 + "cpu": ["x64"] 596 + }, 597 + "@esbuild/sunos-x64@0.25.5": { 598 + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", 599 + "os": ["sunos"], 600 + "cpu": ["x64"] 601 + }, 602 + "@esbuild/win32-arm64@0.25.5": { 603 + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", 604 + "os": ["win32"], 605 + "cpu": ["arm64"] 606 + }, 607 + "@esbuild/win32-ia32@0.25.5": { 608 + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", 609 + "os": ["win32"], 610 + "cpu": ["ia32"] 611 + }, 612 + "@esbuild/win32-x64@0.25.5": { 613 + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", 614 + "os": ["win32"], 615 + "cpu": ["x64"] 616 + }, 617 + "@img/sharp-darwin-arm64@0.34.1": { 618 + "integrity": "sha512-pn44xgBtgpEbZsu+lWf2KNb6OAf70X68k+yk69Ic2Xz11zHR/w24/U49XT7AeRwJ0Px+mhALhU5LPci1Aymk7A==", 619 + "optionalDependencies": [ 620 + "@img/sharp-libvips-darwin-arm64" 621 + ], 622 + "os": ["darwin"], 623 + "cpu": ["arm64"] 624 + }, 625 + "@img/sharp-darwin-x64@0.34.1": { 626 + "integrity": "sha512-VfuYgG2r8BpYiOUN+BfYeFo69nP/MIwAtSJ7/Zpxc5QF3KS22z8Pvg3FkrSFJBPNQ7mmcUcYQFBmEQp7eu1F8Q==", 627 + "optionalDependencies": [ 628 + "@img/sharp-libvips-darwin-x64" 629 + ], 630 + "os": ["darwin"], 631 + "cpu": ["x64"] 632 + }, 633 + "@img/sharp-libvips-darwin-arm64@1.1.0": { 634 + "integrity": "sha512-HZ/JUmPwrJSoM4DIQPv/BfNh9yrOA8tlBbqbLz4JZ5uew2+o22Ik+tHQJcih7QJuSa0zo5coHTfD5J8inqj9DA==", 635 + "os": ["darwin"], 636 + "cpu": ["arm64"] 637 + }, 638 + "@img/sharp-libvips-darwin-x64@1.1.0": { 639 + "integrity": "sha512-Xzc2ToEmHN+hfvsl9wja0RlnXEgpKNmftriQp6XzY/RaSfwD9th+MSh0WQKzUreLKKINb3afirxW7A0fz2YWuQ==", 640 + "os": ["darwin"], 641 + "cpu": ["x64"] 642 + }, 643 + "@img/sharp-libvips-linux-arm64@1.1.0": { 644 + "integrity": "sha512-IVfGJa7gjChDET1dK9SekxFFdflarnUB8PwW8aGwEoF3oAsSDuNUTYS+SKDOyOJxQyDC1aPFMuRYLoDInyV9Ew==", 645 + "os": ["linux"], 646 + "cpu": ["arm64"] 647 + }, 648 + "@img/sharp-libvips-linux-arm@1.1.0": { 649 + "integrity": "sha512-s8BAd0lwUIvYCJyRdFqvsj+BJIpDBSxs6ivrOPm/R7piTs5UIwY5OjXrP2bqXC9/moGsyRa37eYWYCOGVXxVrA==", 650 + "os": ["linux"], 651 + "cpu": ["arm"] 652 + }, 653 + "@img/sharp-libvips-linux-ppc64@1.1.0": { 654 + "integrity": "sha512-tiXxFZFbhnkWE2LA8oQj7KYR+bWBkiV2nilRldT7bqoEZ4HiDOcePr9wVDAZPi/Id5fT1oY9iGnDq20cwUz8lQ==", 655 + "os": ["linux"], 656 + "cpu": ["ppc64"] 657 + }, 658 + "@img/sharp-libvips-linux-s390x@1.1.0": { 659 + "integrity": "sha512-xukSwvhguw7COyzvmjydRb3x/09+21HykyapcZchiCUkTThEQEOMtBj9UhkaBRLuBrgLFzQ2wbxdeCCJW/jgJA==", 660 + "os": ["linux"], 661 + "cpu": ["s390x"] 662 + }, 663 + "@img/sharp-libvips-linux-x64@1.1.0": { 664 + "integrity": "sha512-yRj2+reB8iMg9W5sULM3S74jVS7zqSzHG3Ol/twnAAkAhnGQnpjj6e4ayUz7V+FpKypwgs82xbRdYtchTTUB+Q==", 665 + "os": ["linux"], 666 + "cpu": ["x64"] 667 + }, 668 + "@img/sharp-libvips-linuxmusl-arm64@1.1.0": { 669 + "integrity": "sha512-jYZdG+whg0MDK+q2COKbYidaqW/WTz0cc1E+tMAusiDygrM4ypmSCjOJPmFTvHHJ8j/6cAGyeDWZOsK06tP33w==", 670 + "os": ["linux"], 671 + "cpu": ["arm64"] 672 + }, 673 + "@img/sharp-libvips-linuxmusl-x64@1.1.0": { 674 + "integrity": "sha512-wK7SBdwrAiycjXdkPnGCPLjYb9lD4l6Ze2gSdAGVZrEL05AOUJESWU2lhlC+Ffn5/G+VKuSm6zzbQSzFX/P65A==", 675 + "os": ["linux"], 676 + "cpu": ["x64"] 677 + }, 678 + "@img/sharp-linux-arm64@0.34.1": { 679 + "integrity": "sha512-kX2c+vbvaXC6vly1RDf/IWNXxrlxLNpBVWkdpRq5Ka7OOKj6nr66etKy2IENf6FtOgklkg9ZdGpEu9kwdlcwOQ==", 680 + "optionalDependencies": [ 681 + "@img/sharp-libvips-linux-arm64" 682 + ], 683 + "os": ["linux"], 684 + "cpu": ["arm64"] 685 + }, 686 + "@img/sharp-linux-arm@0.34.1": { 687 + "integrity": "sha512-anKiszvACti2sGy9CirTlNyk7BjjZPiML1jt2ZkTdcvpLU1YH6CXwRAZCA2UmRXnhiIftXQ7+Oh62Ji25W72jA==", 688 + "optionalDependencies": [ 689 + "@img/sharp-libvips-linux-arm" 690 + ], 691 + "os": ["linux"], 692 + "cpu": ["arm"] 693 + }, 694 + "@img/sharp-linux-s390x@0.34.1": { 695 + "integrity": "sha512-7s0KX2tI9mZI2buRipKIw2X1ufdTeaRgwmRabt5bi9chYfhur+/C1OXg3TKg/eag1W+6CCWLVmSauV1owmRPxA==", 696 + "optionalDependencies": [ 697 + "@img/sharp-libvips-linux-s390x" 698 + ], 699 + "os": ["linux"], 700 + "cpu": ["s390x"] 701 + }, 702 + "@img/sharp-linux-x64@0.34.1": { 703 + "integrity": "sha512-wExv7SH9nmoBW3Wr2gvQopX1k8q2g5V5Iag8Zk6AVENsjwd+3adjwxtp3Dcu2QhOXr8W9NusBU6XcQUohBZ5MA==", 704 + "optionalDependencies": [ 705 + "@img/sharp-libvips-linux-x64" 706 + ], 707 + "os": ["linux"], 708 + "cpu": ["x64"] 709 + }, 710 + "@img/sharp-linuxmusl-arm64@0.34.1": { 711 + "integrity": "sha512-DfvyxzHxw4WGdPiTF0SOHnm11Xv4aQexvqhRDAoD00MzHekAj9a/jADXeXYCDFH/DzYruwHbXU7uz+H+nWmSOQ==", 712 + "optionalDependencies": [ 713 + "@img/sharp-libvips-linuxmusl-arm64" 714 + ], 715 + "os": ["linux"], 716 + "cpu": ["arm64"] 717 + }, 718 + "@img/sharp-linuxmusl-x64@0.34.1": { 719 + "integrity": "sha512-pax/kTR407vNb9qaSIiWVnQplPcGU8LRIJpDT5o8PdAx5aAA7AS3X9PS8Isw1/WfqgQorPotjrZL3Pqh6C5EBg==", 720 + "optionalDependencies": [ 721 + "@img/sharp-libvips-linuxmusl-x64" 722 + ], 723 + "os": ["linux"], 724 + "cpu": ["x64"] 725 + }, 726 + "@img/sharp-wasm32@0.34.1": { 727 + "integrity": "sha512-YDybQnYrLQfEpzGOQe7OKcyLUCML4YOXl428gOOzBgN6Gw0rv8dpsJ7PqTHxBnXnwXr8S1mYFSLSa727tpz0xg==", 728 + "dependencies": [ 729 + "@emnapi/runtime" 730 + ], 731 + "cpu": ["wasm32"] 732 + }, 733 + "@img/sharp-win32-ia32@0.34.1": { 734 + "integrity": "sha512-WKf/NAZITnonBf3U1LfdjoMgNO5JYRSlhovhRhMxXVdvWYveM4kM3L8m35onYIdh75cOMCo1BexgVQcCDzyoWw==", 735 + "os": ["win32"], 736 + "cpu": ["ia32"] 737 + }, 738 + "@img/sharp-win32-x64@0.34.1": { 739 + "integrity": "sha512-hw1iIAHpNE8q3uMIRCgGOeDoz9KtFNarFLQclLxr/LK1VBkj8nby18RjFvr6aP7USRYAjTZW6yisnBWMX571Tw==", 740 + "os": ["win32"], 741 + "cpu": ["x64"] 742 + }, 404 743 "@ipld/dag-cbor@7.0.3": { 405 744 "integrity": "sha512-1VVh2huHsuohdXC1bGJNE8WR72slZ9XE2T3wbBBq31dm7ZBatmKLLxrB+XAqafxfRFjv08RZmj/W/ZqaM13AuA==", 406 745 "dependencies": [ ··· 428 767 "@jridgewell/set-array@1.2.1": { 429 768 "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==" 430 769 }, 770 + "@jridgewell/source-map@0.3.6": { 771 + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", 772 + "dependencies": [ 773 + "@jridgewell/gen-mapping", 774 + "@jridgewell/trace-mapping" 775 + ] 776 + }, 431 777 "@jridgewell/sourcemap-codec@1.5.0": { 432 778 "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" 433 779 }, ··· 446 792 "@tybys/wasm-util" 447 793 ] 448 794 }, 449 - "@noble/curves@1.9.1": { 450 - "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", 795 + "@noble/curves@1.9.2": { 796 + "integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==", 451 797 "dependencies": [ 452 798 "@noble/hashes" 453 799 ] ··· 545 891 ], 546 892 "scripts": true 547 893 }, 548 - "@tailwindcss/cli@4.1.7": { 549 - "integrity": "sha512-hJNjpov/UiJc9ZWH4j/eEQxqklADrD/71s+t8Y0wbyQVAwtLkSp+MeC/sHTb03X+28rfbe0fRXkiBsf73/IwPg==", 894 + "@tailwindcss/cli@4.1.8": { 895 + "integrity": "sha512-+6lkjXSr/68zWiabK3mVYVHmOq/SAHjJ13mR8spyB4LgUWZbWzU9kCSErlAUo+gK5aVfgqe8kY6Ltz9+nz5XYA==", 550 896 "dependencies": [ 551 897 "@parcel/watcher", 552 898 "@tailwindcss/node", ··· 558 904 ], 559 905 "bin": true 560 906 }, 561 - "@tailwindcss/node@4.1.7": { 562 - "integrity": "sha512-9rsOpdY9idRI2NH6CL4wORFY0+Q6fnx9XP9Ju+iq/0wJwGD5IByIgFmwVbyy4ymuyprj8Qh4ErxMKTUL4uNh3g==", 907 + "@tailwindcss/node@4.1.8": { 908 + "integrity": "sha512-OWwBsbC9BFAJelmnNcrKuf+bka2ZxCE2A4Ft53Tkg4uoiE67r/PMEYwCsourC26E+kmxfwE0hVzMdxqeW+xu7Q==", 563 909 "dependencies": [ 564 910 "@ampproject/remapping", 565 911 "enhanced-resolve", ··· 570 916 "tailwindcss" 571 917 ] 572 918 }, 573 - "@tailwindcss/oxide-android-arm64@4.1.7": { 574 - "integrity": "sha512-IWA410JZ8fF7kACus6BrUwY2Z1t1hm0+ZWNEzykKmMNM09wQooOcN/VXr0p/WJdtHZ90PvJf2AIBS/Ceqx1emg==", 919 + "@tailwindcss/oxide-android-arm64@4.1.8": { 920 + "integrity": "sha512-Fbz7qni62uKYceWYvUjRqhGfZKwhZDQhlrJKGtnZfuNtHFqa8wmr+Wn74CTWERiW2hn3mN5gTpOoxWKk0jRxjg==", 575 921 "os": ["android"], 576 922 "cpu": ["arm64"] 577 923 }, 578 - "@tailwindcss/oxide-darwin-arm64@4.1.7": { 579 - "integrity": "sha512-81jUw9To7fimGGkuJ2W5h3/oGonTOZKZ8C2ghm/TTxbwvfSiFSDPd6/A/KE2N7Jp4mv3Ps9OFqg2fEKgZFfsvg==", 924 + "@tailwindcss/oxide-darwin-arm64@4.1.8": { 925 + "integrity": "sha512-RdRvedGsT0vwVVDztvyXhKpsU2ark/BjgG0huo4+2BluxdXo8NDgzl77qh0T1nUxmM11eXwR8jA39ibvSTbi7A==", 580 926 "os": ["darwin"], 581 927 "cpu": ["arm64"] 582 928 }, 583 - "@tailwindcss/oxide-darwin-x64@4.1.7": { 584 - "integrity": "sha512-q77rWjEyGHV4PdDBtrzO0tgBBPlQWKY7wZK0cUok/HaGgbNKecegNxCGikuPJn5wFAlIywC3v+WMBt0PEBtwGw==", 929 + "@tailwindcss/oxide-darwin-x64@4.1.8": { 930 + "integrity": "sha512-t6PgxjEMLp5Ovf7uMb2OFmb3kqzVTPPakWpBIFzppk4JE4ix0yEtbtSjPbU8+PZETpaYMtXvss2Sdkx8Vs4XRw==", 585 931 "os": ["darwin"], 586 932 "cpu": ["x64"] 587 933 }, 588 - "@tailwindcss/oxide-freebsd-x64@4.1.7": { 589 - "integrity": "sha512-RfmdbbK6G6ptgF4qqbzoxmH+PKfP4KSVs7SRlTwcbRgBwezJkAO3Qta/7gDy10Q2DcUVkKxFLXUQO6J3CRvBGw==", 934 + "@tailwindcss/oxide-freebsd-x64@4.1.8": { 935 + "integrity": "sha512-g8C8eGEyhHTqwPStSwZNSrOlyx0bhK/V/+zX0Y+n7DoRUzyS8eMbVshVOLJTDDC+Qn9IJnilYbIKzpB9n4aBsg==", 590 936 "os": ["freebsd"], 591 937 "cpu": ["x64"] 592 938 }, 593 - "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.7": { 594 - "integrity": "sha512-OZqsGvpwOa13lVd1z6JVwQXadEobmesxQ4AxhrwRiPuE04quvZHWn/LnihMg7/XkN+dTioXp/VMu/p6A5eZP3g==", 939 + "@tailwindcss/oxide-linux-arm-gnueabihf@4.1.8": { 940 + "integrity": "sha512-Jmzr3FA4S2tHhaC6yCjac3rGf7hG9R6Gf2z9i9JFcuyy0u79HfQsh/thifbYTF2ic82KJovKKkIB6Z9TdNhCXQ==", 595 941 "os": ["linux"], 596 942 "cpu": ["arm"] 597 943 }, 598 - "@tailwindcss/oxide-linux-arm64-gnu@4.1.7": { 599 - "integrity": "sha512-voMvBTnJSfKecJxGkoeAyW/2XRToLZ227LxswLAwKY7YslG/Xkw9/tJNH+3IVh5bdYzYE7DfiaPbRkSHFxY1xA==", 944 + "@tailwindcss/oxide-linux-arm64-gnu@4.1.8": { 945 + "integrity": "sha512-qq7jXtO1+UEtCmCeBBIRDrPFIVI4ilEQ97qgBGdwXAARrUqSn/L9fUrkb1XP/mvVtoVeR2bt/0L77xx53bPZ/Q==", 600 946 "os": ["linux"], 601 947 "cpu": ["arm64"] 602 948 }, 603 - "@tailwindcss/oxide-linux-arm64-musl@4.1.7": { 604 - "integrity": "sha512-PjGuNNmJeKHnP58M7XyjJyla8LPo+RmwHQpBI+W/OxqrwojyuCQ+GUtygu7jUqTEexejZHr/z3nBc/gTiXBj4A==", 949 + "@tailwindcss/oxide-linux-arm64-musl@4.1.8": { 950 + "integrity": "sha512-O6b8QesPbJCRshsNApsOIpzKt3ztG35gfX9tEf4arD7mwNinsoCKxkj8TgEE0YRjmjtO3r9FlJnT/ENd9EVefQ==", 605 951 "os": ["linux"], 606 952 "cpu": ["arm64"] 607 953 }, 608 - "@tailwindcss/oxide-linux-x64-gnu@4.1.7": { 609 - "integrity": "sha512-HMs+Va+ZR3gC3mLZE00gXxtBo3JoSQxtu9lobbZd+DmfkIxR54NO7Z+UQNPsa0P/ITn1TevtFxXTpsRU7qEvWg==", 954 + "@tailwindcss/oxide-linux-x64-gnu@4.1.8": { 955 + "integrity": "sha512-32iEXX/pXwikshNOGnERAFwFSfiltmijMIAbUhnNyjFr3tmWmMJWQKU2vNcFX0DACSXJ3ZWcSkzNbaKTdngH6g==", 610 956 "os": ["linux"], 611 957 "cpu": ["x64"] 612 958 }, 613 - "@tailwindcss/oxide-linux-x64-musl@4.1.7": { 614 - "integrity": "sha512-MHZ6jyNlutdHH8rd+YTdr3QbXrHXqwIhHw9e7yXEBcQdluGwhpQY2Eku8UZK6ReLaWtQ4gijIv5QoM5eE+qlsA==", 959 + "@tailwindcss/oxide-linux-x64-musl@4.1.8": { 960 + "integrity": "sha512-s+VSSD+TfZeMEsCaFaHTaY5YNj3Dri8rST09gMvYQKwPphacRG7wbuQ5ZJMIJXN/puxPcg/nU+ucvWguPpvBDg==", 615 961 "os": ["linux"], 616 962 "cpu": ["x64"] 617 963 }, 618 - "@tailwindcss/oxide-wasm32-wasi@4.1.7": { 619 - "integrity": "sha512-ANaSKt74ZRzE2TvJmUcbFQ8zS201cIPxUDm5qez5rLEwWkie2SkGtA4P+GPTj+u8N6JbPrC8MtY8RmJA35Oo+A==", 964 + "@tailwindcss/oxide-wasm32-wasi@4.1.8": { 965 + "integrity": "sha512-CXBPVFkpDjM67sS1psWohZ6g/2/cd+cq56vPxK4JeawelxwK4YECgl9Y9TjkE2qfF+9/s1tHHJqrC4SS6cVvSg==", 620 966 "dependencies": [ 621 967 "@emnapi/core", 622 968 "@emnapi/runtime", ··· 627 973 ], 628 974 "cpu": ["wasm32"] 629 975 }, 630 - "@tailwindcss/oxide-win32-arm64-msvc@4.1.7": { 631 - "integrity": "sha512-HUiSiXQ9gLJBAPCMVRk2RT1ZrBjto7WvqsPBwUrNK2BcdSxMnk19h4pjZjI7zgPhDxlAbJSumTC4ljeA9y0tEw==", 976 + "@tailwindcss/oxide-win32-arm64-msvc@4.1.8": { 977 + "integrity": "sha512-7GmYk1n28teDHUjPlIx4Z6Z4hHEgvP5ZW2QS9ygnDAdI/myh3HTHjDqtSqgu1BpRoI4OiLx+fThAyA1JePoENA==", 632 978 "os": ["win32"], 633 979 "cpu": ["arm64"] 634 980 }, 635 - "@tailwindcss/oxide-win32-x64-msvc@4.1.7": { 636 - "integrity": "sha512-rYHGmvoHiLJ8hWucSfSOEmdCBIGZIq7SpkPRSqLsH2Ab2YUNgKeAPT1Fi2cx3+hnYOrAb0jp9cRyode3bBW4mQ==", 981 + "@tailwindcss/oxide-win32-x64-msvc@4.1.8": { 982 + "integrity": "sha512-fou+U20j+Jl0EHwK92spoWISON2OBnCazIc038Xj2TdweYV33ZRkS9nwqiUi2d/Wba5xg5UoHfvynnb/UB49cQ==", 637 983 "os": ["win32"], 638 984 "cpu": ["x64"] 639 985 }, 640 - "@tailwindcss/oxide@4.1.7": { 641 - "integrity": "sha512-5SF95Ctm9DFiUyjUPnDGkoKItPX/k+xifcQhcqX5RA85m50jw1pT/KzjdvlqxRja45Y52nR4MR9fD1JYd7f8NQ==", 986 + "@tailwindcss/oxide@4.1.8": { 987 + "integrity": "sha512-d7qvv9PsM5N3VNKhwVUhpK6r4h9wtLkJ6lz9ZY9aeZgrUWk1Z8VPyqyDT9MZlem7GTGseRQHkeB1j3tC7W1P+A==", 642 988 "dependencies": [ 643 989 "detect-libc@2.0.4", 644 990 "tar" ··· 679 1025 "undici-types" 680 1026 ] 681 1027 }, 1028 + "@xmldom/xmldom@0.9.8": { 1029 + "integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==" 1030 + }, 682 1031 "abort-controller@3.0.0": { 683 1032 "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", 684 1033 "dependencies": [ ··· 692 1041 "negotiator" 693 1042 ] 694 1043 }, 1044 + "acorn@8.14.1": { 1045 + "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==", 1046 + "bin": true 1047 + }, 695 1048 "ansi-styles@4.3.0": { 696 1049 "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", 697 1050 "dependencies": [ ··· 741 1094 "dependencies": [ 742 1095 "fill-range" 743 1096 ] 1097 + }, 1098 + "buffer-from@1.1.2": { 1099 + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" 744 1100 }, 745 1101 "buffer@6.0.3": { 746 1102 "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", ··· 817 1173 "color-name@1.1.4": { 818 1174 "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" 819 1175 }, 1176 + "color-string@1.9.1": { 1177 + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", 1178 + "dependencies": [ 1179 + "color-name", 1180 + "simple-swizzle" 1181 + ] 1182 + }, 1183 + "color@4.2.3": { 1184 + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", 1185 + "dependencies": [ 1186 + "color-convert", 1187 + "color-string" 1188 + ] 1189 + }, 1190 + "commander@2.20.3": { 1191 + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" 1192 + }, 1193 + "commander@8.3.0": { 1194 + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==" 1195 + }, 820 1196 "commander@9.5.0": { 821 1197 "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" 822 1198 }, ··· 844 1220 "ms@2.0.0" 845 1221 ] 846 1222 }, 1223 + "deepmerge@4.3.1": { 1224 + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==" 1225 + }, 847 1226 "depd@2.0.0": { 848 1227 "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" 849 1228 }, ··· 857 1236 "detect-libc@2.0.4": { 858 1237 "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==" 859 1238 }, 1239 + "dom-serializer@2.0.0": { 1240 + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", 1241 + "dependencies": [ 1242 + "domelementtype", 1243 + "domhandler", 1244 + "entities" 1245 + ] 1246 + }, 1247 + "domelementtype@2.3.0": { 1248 + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==" 1249 + }, 1250 + "domhandler@5.0.3": { 1251 + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", 1252 + "dependencies": [ 1253 + "domelementtype" 1254 + ] 1255 + }, 1256 + "domutils@3.2.2": { 1257 + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", 1258 + "dependencies": [ 1259 + "dom-serializer", 1260 + "domelementtype", 1261 + "domhandler" 1262 + ] 1263 + }, 860 1264 "dunder-proto@1.0.1": { 861 1265 "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 862 1266 "dependencies": [ ··· 881 1285 "tapable" 882 1286 ] 883 1287 }, 1288 + "entities@4.5.0": { 1289 + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==" 1290 + }, 884 1291 "es-define-property@1.0.1": { 885 1292 "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" 886 1293 }, ··· 893 1300 "es-errors" 894 1301 ] 895 1302 }, 1303 + "esbuild@0.25.5": { 1304 + "integrity": "sha512-P8OtKZRv/5J5hhz0cUAdu/cLuPIKXpQl1R9pZtvmHWQvrAUVd0UNIPT4IB4W3rNOqVO0rlqHmCIbSwxh/c9yUQ==", 1305 + "optionalDependencies": [ 1306 + "@esbuild/aix-ppc64", 1307 + "@esbuild/android-arm", 1308 + "@esbuild/android-arm64", 1309 + "@esbuild/android-x64", 1310 + "@esbuild/darwin-arm64", 1311 + "@esbuild/darwin-x64", 1312 + "@esbuild/freebsd-arm64", 1313 + "@esbuild/freebsd-x64", 1314 + "@esbuild/linux-arm", 1315 + "@esbuild/linux-arm64", 1316 + "@esbuild/linux-ia32", 1317 + "@esbuild/linux-loong64", 1318 + "@esbuild/linux-mips64el", 1319 + "@esbuild/linux-ppc64", 1320 + "@esbuild/linux-riscv64", 1321 + "@esbuild/linux-s390x", 1322 + "@esbuild/linux-x64", 1323 + "@esbuild/netbsd-arm64", 1324 + "@esbuild/netbsd-x64", 1325 + "@esbuild/openbsd-arm64", 1326 + "@esbuild/openbsd-x64", 1327 + "@esbuild/sunos-x64", 1328 + "@esbuild/win32-arm64", 1329 + "@esbuild/win32-ia32", 1330 + "@esbuild/win32-x64" 1331 + ], 1332 + "scripts": true, 1333 + "bin": true 1334 + }, 896 1335 "escape-html@1.0.3": { 897 1336 "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" 898 1337 }, 1338 + "escape-string-regexp@4.0.0": { 1339 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" 1340 + }, 899 1341 "etag@1.8.1": { 900 1342 "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" 901 1343 }, ··· 905 1347 "events@3.3.0": { 906 1348 "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" 907 1349 }, 1350 + "exif-esm@1.0.1": { 1351 + "integrity": "sha512-IV4exsKAoDG7B1SpHXdfl76+8KQlq0P3H2BdCIu/KJIT2lu5sQExU6WvRJuw/LSiRHEvqxxNh2rMR1es1qS8mg==" 1352 + }, 1353 + "exifr@7.1.3": { 1354 + "integrity": "sha512-g/aje2noHivrRSLbAUtBPWFbxKdKhgj/xr1vATDdUXPOFYJlQ62Ft0oy+72V6XLIpDJfHs6gXLbBLAolqOXYRw==" 1355 + }, 908 1356 "express@4.21.2": { 909 1357 "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", 910 1358 "dependencies": [ ··· 944 1392 "fast-redact@3.5.0": { 945 1393 "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==" 946 1394 }, 947 - "fdir@6.4.4_picomatch@4.0.2": { 948 - "integrity": "sha512-1NZP+GK4GfuAv3PqKvxQRDMjdSRZjnkq7KfhlNrCNNlZ0ygQFpebfrnfnq/W7fpUnAv9aGWmY1zKx7FYL3gwhg==", 1395 + "fdir@6.4.5_picomatch@4.0.2": { 1396 + "integrity": "sha512-4BG7puHpVsIYxZUbiUE3RqGloLaSSwzYie5jvasC4LWuBWzZawynvYouhjbQKw2JuIGYdm0DzIxl8iVidKlUEw==", 949 1397 "dependencies": [ 950 1398 "picomatch@4.0.2" 951 1399 ], ··· 1008 1456 "es-object-atoms" 1009 1457 ] 1010 1458 }, 1459 + "github-slugger@2.0.0": { 1460 + "integrity": "sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==" 1461 + }, 1011 1462 "gopd@1.2.0": { 1012 1463 "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" 1013 1464 }, ··· 1029 1480 "function-bind" 1030 1481 ] 1031 1482 }, 1483 + "he@1.2.0": { 1484 + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", 1485 + "bin": true 1486 + }, 1032 1487 "hey-listen@1.0.8": { 1033 1488 "integrity": "sha512-COpmrF2NOg4TBWUJ5UVyaCU2A88wEMkUPK4hNqyCkqHbxT92BbvfjoSozkAIIm6XhicGlJHhFdullInrdhwU8Q==" 1034 1489 }, 1490 + "htmlparser2@8.0.2": { 1491 + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", 1492 + "dependencies": [ 1493 + "domelementtype", 1494 + "domhandler", 1495 + "domutils", 1496 + "entities" 1497 + ] 1498 + }, 1499 + "htmx.org@1.9.12": { 1500 + "integrity": "sha512-VZAohXyF7xPGS52IM8d1T1283y+X4D+Owf3qY1NZ9RuBypyu9l8cGsxUMAG5fEAb/DhT7rDoJ9Hpu5/HxFD3cw==" 1501 + }, 1035 1502 "http-errors@2.0.0": { 1036 1503 "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", 1037 1504 "dependencies": [ ··· 1042 1509 "toidentifier" 1043 1510 ] 1044 1511 }, 1512 + "hyperscript.org@0.9.14": { 1513 + "integrity": "sha512-ugmojsQQUMmXcnwaXYiYf8L3GbeANy/m59EmE/0Z6C5eQ52fOuSrvFkuEIejG9BdpbYB4iTtoYGqV99eYqDVMA==", 1514 + "dependencies": [ 1515 + "markdown-it-deflist", 1516 + "terser" 1517 + ], 1518 + "bin": true 1519 + }, 1045 1520 "iconv-lite@0.4.24": { 1046 1521 "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 1047 1522 "dependencies": [ ··· 1060 1535 "ipaddr.js@2.2.0": { 1061 1536 "integrity": "sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA==" 1062 1537 }, 1538 + "is-arrayish@0.3.2": { 1539 + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" 1540 + }, 1063 1541 "is-extglob@2.1.1": { 1064 1542 "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" 1065 1543 }, ··· 1072 1550 "is-number@7.0.0": { 1073 1551 "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" 1074 1552 }, 1553 + "is-plain-object@5.0.0": { 1554 + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" 1555 + }, 1075 1556 "iso-datestring-validator@2.2.2": { 1076 1557 "integrity": "sha512-yLEMkBbLZTlVQqOnQ4FiMujR6T4DEcCb1xizmvXS+OxuhwcbtynoosRzdMA69zZCShCNAbi+gJ71FxZBBXx1SA==" 1077 1558 }, ··· 1081 1562 }, 1082 1563 "jose@5.9.6": { 1083 1564 "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==" 1565 + }, 1566 + "katex@0.16.22": { 1567 + "integrity": "sha512-XCHRdUw4lf3SKBaJe4EvgqIuWwkPSo9XoeO8GjQW94Bp7TWv9hNhzZjZ+OH9yf1UmLygb7DIT5GSFQiyt16zYg==", 1568 + "dependencies": [ 1569 + "commander@8.3.0" 1570 + ], 1571 + "bin": true 1084 1572 }, 1085 1573 "lightningcss-darwin-arm64@1.30.1": { 1086 1574 "integrity": "sha512-c8JK7hyE65X1MHMN+Viq9n11RRC7hgin3HhYKhrMyaXflk5GVplZ60IxyoVtzILeKr+xAJwg6zK6sjTBJ0FKYQ==", ··· 1159 1647 "@jridgewell/sourcemap-codec" 1160 1648 ] 1161 1649 }, 1650 + "markdown-it-deflist@2.1.0": { 1651 + "integrity": "sha512-3OuqoRUlSxJiuQYu0cWTLHNhhq2xtoSFqsZK8plANg91+RJQU1ziQ6lA2LzmFAEes18uPBsHZpcX6We5l76Nzg==" 1652 + }, 1653 + "marked-alert@2.1.2_marked@12.0.2": { 1654 + "integrity": "sha512-EFNRZ08d8L/iEIPLTlQMDjvwIsj03gxWCczYTht6DCiHJIZhMk4NK5gtPY9UqAYb09eV5VGT+jD4lp396E0I+w==", 1655 + "dependencies": [ 1656 + "marked" 1657 + ] 1658 + }, 1659 + "marked-footnote@1.2.4_marked@12.0.2": { 1660 + "integrity": "sha512-DB2Kl+wFh6YwZd70qABMY6WUkG1UuyqoNTFoDfGyG79Pz24neYtLBkB+45a7o72V7gkfvbC3CGzIYFobxfMT1Q==", 1661 + "dependencies": [ 1662 + "marked" 1663 + ] 1664 + }, 1665 + "marked-gfm-heading-id@3.2.0_marked@12.0.2": { 1666 + "integrity": "sha512-Xfxpr5lXLDLY10XqzSCA9l2dDaiabQUgtYM9hw8yunyVsB/xYBRpiic6BOiY/EAJw1ik1eWr1ET1HKOAPZBhXg==", 1667 + "dependencies": [ 1668 + "github-slugger", 1669 + "marked" 1670 + ] 1671 + }, 1672 + "marked@12.0.2": { 1673 + "integrity": "sha512-qXUm7e/YKFoqFPYPa3Ukg9xlI5cyAtGmyEIzMfW//m6kXwCy2Ps9DYf5ioijFKQ8qyuscrHoY04iJGctu2Kg0Q==", 1674 + "bin": true 1675 + }, 1162 1676 "math-intrinsics@1.1.0": { 1163 1677 "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" 1164 1678 }, ··· 1219 1733 "ms@2.1.3": { 1220 1734 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" 1221 1735 }, 1222 - "multiformats@13.3.4": { 1223 - "integrity": "sha512-JXpM5p9TpJ/BHsUtmLaWuRN0ft0gJPGa6BhkX2KXjFHvkFQOQkDManoar3gx0JsTLNrOojBE2Mj4hFxohGnXZA==" 1736 + "multiformats@13.3.6": { 1737 + "integrity": "sha512-yakbt9cPYj8d3vi/8o/XWm61MrOILo7fsTL0qxNx6zS0Nso6K5JqqS2WV7vK/KSuDBvrW3KfCwAdAgarAgOmww==" 1224 1738 }, 1225 1739 "multiformats@9.9.0": { 1226 1740 "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==" 1227 1741 }, 1742 + "nanoid@3.3.11": { 1743 + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", 1744 + "bin": true 1745 + }, 1228 1746 "negotiator@0.6.3": { 1229 1747 "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" 1230 1748 }, ··· 1249 1767 "dependencies": [ 1250 1768 "ee-first" 1251 1769 ] 1770 + }, 1771 + "parse-srcset@1.0.2": { 1772 + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==" 1252 1773 }, 1253 1774 "parseurl@1.3.3": { 1254 1775 "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" ··· 1304 1825 "tslib@2.4.0" 1305 1826 ] 1306 1827 }, 1307 - "preact-render-to-string@6.5.13_preact@10.26.6": { 1828 + "postcss@8.5.3": { 1829 + "integrity": "sha512-dle9A3yYxlBSrt8Fu+IpjGT8SY8hN0mlaA6GY8t0P5PjIOZemULz/E2Bnm/2dcUOena75OTNkHI76uZBNUUq3A==", 1830 + "dependencies": [ 1831 + "nanoid", 1832 + "picocolors", 1833 + "source-map-js" 1834 + ] 1835 + }, 1836 + "preact-render-to-string@6.5.13_preact@10.26.8": { 1308 1837 "integrity": "sha512-iGPd+hKPMFKsfpR2vL4kJ6ZPcFIoWZEcBf0Dpm3zOpdVvj77aY8RlLiQji5OMrngEyaxGogeakTb54uS2FvA6w==", 1309 1838 "dependencies": [ 1310 1839 "preact" 1311 1840 ] 1312 1841 }, 1313 - "preact@10.26.6": { 1314 - "integrity": "sha512-5SRRBinwpwkaD+OqlBDeITlRgvd8I8QlxHJw9AxSdMNV6O+LodN9nUyYGpSF7sadHjs6RzeFShMexC6DbtWr9g==" 1842 + "preact@10.26.8": { 1843 + "integrity": "sha512-1nMfdFjucm5hKvq0IClqZwK4FJkGXhRrQstOQ3P4vp8HxKrJEMFcY6RdBRVTdfQS/UlnX6gfbPuTvaqx/bDoeQ==" 1315 1844 }, 1316 1845 "prettier@3.5.3": { 1317 1846 "integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==", 1318 1847 "bin": true 1319 1848 }, 1849 + "prismjs@1.30.0": { 1850 + "integrity": "sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==" 1851 + }, 1320 1852 "process-warning@3.0.0": { 1321 1853 "integrity": "sha512-mqn0kFRl0EoqhnL0GQ0veqFHyIN1yig9RHh/InzORTUiZHFRAur+aMtRkELNwGs9aNwKS6tg/An4NYBPGwvtzQ==" 1322 1854 }, ··· 1330 1862 "ipaddr.js@1.9.1" 1331 1863 ] 1332 1864 }, 1333 - "psl@1.15.0": { 1334 - "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==", 1335 - "dependencies": [ 1336 - "punycode" 1337 - ] 1338 - }, 1339 - "punycode@2.3.1": { 1340 - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" 1341 - }, 1342 1865 "qs@6.13.0": { 1343 1866 "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", 1344 1867 "dependencies": [ ··· 1385 1908 "safer-buffer@2.1.2": { 1386 1909 "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" 1387 1910 }, 1911 + "sanitize-html@2.17.0": { 1912 + "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", 1913 + "dependencies": [ 1914 + "deepmerge", 1915 + "escape-string-regexp", 1916 + "htmlparser2", 1917 + "is-plain-object", 1918 + "parse-srcset", 1919 + "postcss" 1920 + ] 1921 + }, 1922 + "semver@7.7.2": { 1923 + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", 1924 + "bin": true 1925 + }, 1388 1926 "send@0.19.0": { 1389 1927 "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", 1390 1928 "dependencies": [ ··· 1415 1953 "setprototypeof@1.2.0": { 1416 1954 "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" 1417 1955 }, 1956 + "sharp@0.34.1": { 1957 + "integrity": "sha512-1j0w61+eVxu7DawFJtnfYcvSv6qPFvfTaqzTQ2BLknVhHTwGS8sc63ZBF4rzkWMBVKybo4S5OBtDdZahh2A1xg==", 1958 + "dependencies": [ 1959 + "color", 1960 + "detect-libc@2.0.4", 1961 + "semver" 1962 + ], 1963 + "optionalDependencies": [ 1964 + "@img/sharp-darwin-arm64", 1965 + "@img/sharp-darwin-x64", 1966 + "@img/sharp-libvips-darwin-arm64", 1967 + "@img/sharp-libvips-darwin-x64", 1968 + "@img/sharp-libvips-linux-arm", 1969 + "@img/sharp-libvips-linux-arm64", 1970 + "@img/sharp-libvips-linux-ppc64", 1971 + "@img/sharp-libvips-linux-s390x", 1972 + "@img/sharp-libvips-linux-x64", 1973 + "@img/sharp-libvips-linuxmusl-arm64", 1974 + "@img/sharp-libvips-linuxmusl-x64", 1975 + "@img/sharp-linux-arm", 1976 + "@img/sharp-linux-arm64", 1977 + "@img/sharp-linux-s390x", 1978 + "@img/sharp-linux-x64", 1979 + "@img/sharp-linuxmusl-arm64", 1980 + "@img/sharp-linuxmusl-x64", 1981 + "@img/sharp-wasm32", 1982 + "@img/sharp-win32-ia32", 1983 + "@img/sharp-win32-x64" 1984 + ], 1985 + "scripts": true 1986 + }, 1418 1987 "side-channel-list@1.0.0": { 1419 1988 "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 1420 1989 "dependencies": [ ··· 1451 2020 "side-channel-weakmap" 1452 2021 ] 1453 2022 }, 2023 + "simple-swizzle@0.2.2": { 2024 + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", 2025 + "dependencies": [ 2026 + "is-arrayish" 2027 + ] 2028 + }, 1454 2029 "sonic-boom@3.8.1": { 1455 2030 "integrity": "sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==", 1456 2031 "dependencies": [ 1457 2032 "atomic-sleep" 1458 2033 ] 1459 2034 }, 2035 + "sortablejs@1.15.6": { 2036 + "integrity": "sha512-aNfiuwMEpfBM/CN6LY0ibyhxPfPbyFeBTYJKCvzkJ2GkUpazIt3H+QIPAMHwqQ7tMKaHz1Qj+rJJCqljnf4p3A==" 2037 + }, 1460 2038 "source-map-js@1.2.1": { 1461 2039 "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==" 1462 2040 }, 2041 + "source-map-support@0.5.21": { 2042 + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", 2043 + "dependencies": [ 2044 + "buffer-from", 2045 + "source-map" 2046 + ] 2047 + }, 2048 + "source-map@0.6.1": { 2049 + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==" 2050 + }, 1463 2051 "split2@4.2.0": { 1464 2052 "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==" 1465 2053 }, ··· 1488 2076 "tailwind-merge@3.3.0": { 1489 2077 "integrity": "sha512-fyW/pEfcQSiigd5SNn0nApUOxx0zB/dm6UDU/rEwc2c3sX2smWUNbapHv+QRqLGVp9GWX3THIa7MUGPo+YkDzQ==" 1490 2078 }, 1491 - "tailwindcss@4.1.7": { 1492 - "integrity": "sha512-kr1o/ErIdNhTz8uzAYL7TpaUuzKIE6QPQ4qmSdxnoX/lo+5wmUHQA6h3L5yIqEImSRnAAURDirLu/BgiXGPAhg==" 2079 + "tailwindcss@4.1.8": { 2080 + "integrity": "sha512-kjeW8gjdxasbmFKpVGrGd5T4i40mV5J2Rasw48QARfYeQ8YS9x02ON9SFWax3Qf616rt4Cp3nVNIj6Hd1mP3og==" 1493 2081 }, 1494 2082 "tapable@2.2.2": { 1495 2083 "integrity": "sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==" ··· 1505 2093 "yallist" 1506 2094 ] 1507 2095 }, 2096 + "terser@5.41.0": { 2097 + "integrity": "sha512-H406eLPXpZbAX14+B8psIuvIr8+3c+2hkuYzpMkoE0ij+NdsVATbA78vb8neA/eqrj7rywa2pIkdmWRsXW6wmw==", 2098 + "dependencies": [ 2099 + "@jridgewell/source-map", 2100 + "acorn", 2101 + "commander@2.20.3", 2102 + "source-map-support" 2103 + ], 2104 + "bin": true 2105 + }, 1508 2106 "thread-stream@2.7.0": { 1509 2107 "integrity": "sha512-qQiRWsU/wvNolI6tbbCKd9iKaTnCXsTwVxhhKM6nctPdujTyztjlbUkUTUymidWcMnZ5pWR0ej4a0tjsW021vw==", 1510 2108 "dependencies": [ 1511 2109 "real-require" 1512 2110 ] 1513 2111 }, 1514 - "tinyglobby@0.2.13_picomatch@4.0.2": { 1515 - "integrity": "sha512-mEwzpUgrLySlveBwEVDMKk5B57bhLPYovRfPAXD5gA/98Opn0rCDj3GtLwFvCvH5RK9uPCExUROW5NjDwvqkxw==", 2112 + "tinyglobby@0.2.14_picomatch@4.0.2": { 2113 + "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", 1516 2114 "dependencies": [ 1517 2115 "fdir", 1518 2116 "picomatch@4.0.2" ··· 1590 2188 "yesno@0.4.0": { 1591 2189 "integrity": "sha512-tdBxmHvbXPBKYIg81bMCB7bVeDmHkRzk5rVJyYYXurwKkHq/MCd8rz4HSJUP7hW0H2NlXiq8IFiWvYKEHhlotA==" 1592 2190 }, 1593 - "zod@3.25.7": { 1594 - "integrity": "sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg==" 2191 + "zod@3.25.51": { 2192 + "integrity": "sha512-TQSnBldh+XSGL+opiSIq0575wvDPqu09AqWe1F7JhUMKY+M91/aGlK4MhpVNO7MgYfHcVCB1ffwAUTJzllKJqg==" 1595 2193 } 1596 2194 }, 1597 2195 "workspace": { 1598 2196 "dependencies": [ 1599 - "jsr:@bigmoves/bff@0.3.0-beta.29", 2197 + "jsr:@bigmoves/bff@0.3.0-beta.32", 2198 + "jsr:@luca/esbuild-deno-loader@~0.11.1", 1600 2199 "jsr:@std/http@^1.0.17", 1601 2200 "jsr:@std/path@^1.0.9", 1602 2201 "npm:@atproto/syntax@0.4", 1603 2202 "npm:@tailwindcss/cli@^4.1.4", 1604 2203 "npm:date-fns@^4.1.0", 2204 + "npm:esbuild@~0.25.5", 2205 + "npm:exifr@^7.1.3", 2206 + "npm:htmx.org@^1.9.12", 2207 + "npm:hyperscript.org@~0.9.14", 1605 2208 "npm:popmotion@^11.0.5", 1606 2209 "npm:preact@^10.26.5", 2210 + "npm:sortablejs@^1.15.6", 1607 2211 "npm:tailwindcss@^4.1.4", 1608 2212 "npm:typed-htmx@~0.3.1" 1609 2213 ]
+1
fly.toml
··· 15 15 BFF_PUBLIC_URL = 'https://grain.social' 16 16 GOATCOUNTER_URL = 'https://grain.goatcounter.com/count' 17 17 USE_CDN = 'true' 18 + PDS_HOST_URL = 'https://ansel.grainsocial.network' 18 19 19 20 [[mounts]] 20 21 source = "litefs"
+71
lexicons/sh/tangled/actor/profile.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.actor.profile", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "description": "A declaration of a Tangled account profile.", 8 + "key": "literal:self", 9 + "record": { 10 + "type": "object", 11 + "required": [ 12 + "bluesky" 13 + ], 14 + "properties": { 15 + "description": { 16 + "type": "string", 17 + "description": "Free-form profile description text.", 18 + "maxGraphemes": 256, 19 + "maxLength": 2560 20 + }, 21 + "links": { 22 + "type": "array", 23 + "minLength": 0, 24 + "maxLength": 5, 25 + "items": { 26 + "type": "string", 27 + "description": "Any URI, intended for social profiles or websites, can be used to link DIDs/AT-URIs too." 28 + } 29 + }, 30 + "stats": { 31 + "type": "array", 32 + "minLength": 0, 33 + "maxLength": 2, 34 + "items": { 35 + "type": "string", 36 + "description": "Vanity stats.", 37 + "enum": [ 38 + "merged-pull-request-count", 39 + "closed-pull-request-count", 40 + "open-pull-request-count", 41 + "open-issue-count", 42 + "closed-issue-count", 43 + "repository-count" 44 + ] 45 + } 46 + }, 47 + "bluesky": { 48 + "type": "boolean", 49 + "description": "Include link to this account on Bluesky." 50 + }, 51 + "location": { 52 + "type": "string", 53 + "description": "Free-form location text.", 54 + "maxGraphemes": 40, 55 + "maxLength": 400 56 + }, 57 + "pinnedRepositories": { 58 + "type": "array", 59 + "description": "Any ATURI, it is up to appviews to validate these fields.", 60 + "minLength": 0, 61 + "maxLength": 6, 62 + "items": { 63 + "type": "string", 64 + "format": "at-uri" 65 + } 66 + } 67 + } 68 + } 69 + } 70 + } 71 + }
+27
lexicons/sh/tangled/graph/follow.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "sh.tangled.graph.follow", 4 + "defs": { 5 + "main": { 6 + "type": "record", 7 + "key": "tid", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "subject", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "subject": { 16 + "type": "string", 17 + "format": "did" 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime" 22 + } 23 + } 24 + } 25 + } 26 + } 27 + }
+27
lexicons/social/grain/graph/follow.json
··· 1 + { 2 + "lexicon": 1, 3 + "id": "social.grain.graph.follow", 4 + "defs": { 5 + "main": { 6 + "key": "tid", 7 + "type": "record", 8 + "record": { 9 + "type": "object", 10 + "required": [ 11 + "subject", 12 + "createdAt" 13 + ], 14 + "properties": { 15 + "subject": { 16 + "type": "string", 17 + "format": "did" 18 + }, 19 + "createdAt": { 20 + "type": "string", 21 + "format": "datetime" 22 + } 23 + } 24 + } 25 + } 26 + } 27 + }
+2 -1
lexicons/social/grain/notification/defs.json
··· 22 22 }, 23 23 "reason": { 24 24 "type": "string", 25 - "description": "Expected values are 'gallery-favorite', and 'unknown'.", 25 + "description": "The reason why this notification was delivered - e.g. your gallery was favd, or you received a new follower.", 26 26 "knownValues": [ 27 + "follow", 27 28 "gallery-favorite", 28 29 "unknown" 29 30 ]
+6 -42
lexicons/social/grain/photo/defs.json
··· 41 41 "cid": { "type": "string", "format": "cid" }, 42 42 "photo": { "type": "string", "format": "at-uri" }, 43 43 "createdAt": { "type": "string", "format": "datetime" }, 44 - "apertureValue": { "type": "integer" }, 45 - "brightnessValue": { "type": "integer" }, 46 - "colorSpace": { "type": "integer" }, 47 - "contrast": { "type": "string", "enum": ["Normal", "Soft", "Hard"] }, 48 - "createDate": { "type": "string", "format": "datetime" }, 49 - "customRendered": { "type": "string" }, 50 - "dateTimeOriginal": { "type": "string", "format": "datetime" }, 51 - "digitalZoomRatio": { "type": "integer" }, 52 - "exifVersion": { "type": "string" }, 53 - "exposureCompensation": { "type": "integer" }, 54 - "exposureMode": { "type": "string" }, 55 - "exposureProgram": { "type": "string" }, 56 - "exposureTime": { "type": "integer" }, 57 - "fNumber": { "type": "integer" }, 58 - "fileSource": { "type": "string" }, 44 + "dateTimeOriginal": { "type": "string" }, 45 + "exposureTime": { "type": "string" }, 46 + "fNumber": { "type": "string" }, 59 47 "flash": { "type": "string" }, 60 - "focalLength": { "type": "integer" }, 61 - "focalLengthIn35mmFormat": { "type": "integer" }, 62 - "focalPlaneResolutionUnit": { "type": "string" }, 63 - "focalPlaneXResolution": { "type": "integer" }, 64 - "focalPlaneYResolution": { "type": "integer" }, 48 + "focalLengthIn35mmFormat": { "type": "string" }, 65 49 "iSO": { "type": "integer" }, 66 - "lensInfo": { 67 - "type": "array", 68 - "items": { "type": "integer" }, 69 - "maxLength": 4 70 - }, 50 + "lensMake": { "type": "string" }, 71 51 "lensModel": { "type": "string" }, 72 - "lightSource": { "type": "string" }, 73 52 "make": { "type": "string" }, 74 - "maxApertureValue": { "type": "integer" }, 75 - "meteringMode": { "type": "string" }, 76 - "model": { "type": "string" }, 77 - "modifyDate": { "type": "string", "format": "datetime" }, 78 - "recommendedExposureIndex": { "type": "integer" }, 79 - "resolutionUnit": { "type": "string" }, 80 - "saturation": { "type": "string" }, 81 - "sceneCaptureType": { "type": "string" }, 82 - "sceneType": { "type": "string" }, 83 - "sensitivityType": { "type": "integer" }, 84 - "sharpness": { "type": "string" }, 85 - "shutterSpeedValue": { "type": "integer" }, 86 - "software": { "type": "string" }, 87 - "whiteBalance": { "type": "string" }, 88 - "xResolution": { "type": "integer" }, 89 - "yResolution": { "type": "integer" } 53 + "model": { "type": "string" } 90 54 } 91 55 } 92 56 }
+3 -39
lexicons/social/grain/photo/exif.json
··· 4 4 "defs": { 5 5 "main": { 6 6 "type": "record", 7 - "description": "EXIF metadata for a photo", 7 + "description": "Basic EXIF metadata for a photo", 8 8 "key": "tid", 9 9 "record": { 10 10 "type": "object", ··· 15 15 "properties": { 16 16 "photo": { "type": "string", "format": "at-uri" }, 17 17 "createdAt": { "type": "string", "format": "datetime" }, 18 - "apertureValue": { "type": "integer" }, 19 - "brightnessValue": { "type": "integer" }, 20 - "colorSpace": { "type": "integer" }, 21 - "contrast": { "type": "string", "enum": ["Normal", "Soft", "Hard"] }, 22 - "createDate": { "type": "string", "format": "datetime" }, 23 - "customRendered": { "type": "string" }, 24 18 "dateTimeOriginal": { "type": "string", "format": "datetime" }, 25 - "digitalZoomRatio": { "type": "integer" }, 26 - "exifVersion": { "type": "string" }, 27 - "exposureCompensation": { "type": "integer" }, 28 - "exposureMode": { "type": "string" }, 29 - "exposureProgram": { "type": "string" }, 30 19 "exposureTime": { "type": "integer" }, 31 20 "fNumber": { "type": "integer" }, 32 - "fileSource": { "type": "string" }, 33 21 "flash": { "type": "string" }, 34 - "focalLength": { "type": "integer" }, 35 22 "focalLengthIn35mmFormat": { "type": "integer" }, 36 - "focalPlaneResolutionUnit": { "type": "string" }, 37 - "focalPlaneXResolution": { "type": "integer" }, 38 - "focalPlaneYResolution": { "type": "integer" }, 39 23 "iSO": { "type": "integer" }, 40 - "lensInfo": { 41 - "type": "array", 42 - "items": { "type": "integer" }, 43 - "maxLength": 4 44 - }, 24 + "lensMake": { "type": "string" }, 45 25 "lensModel": { "type": "string" }, 46 - "lightSource": { "type": "string" }, 47 26 "make": { "type": "string" }, 48 - "maxApertureValue": { "type": "integer" }, 49 - "meteringMode": { "type": "string" }, 50 - "model": { "type": "string" }, 51 - "modifyDate": { "type": "string", "format": "datetime" }, 52 - "recommendedExposureIndex": { "type": "integer" }, 53 - "resolutionUnit": { "type": "string" }, 54 - "saturation": { "type": "string" }, 55 - "sceneCaptureType": { "type": "string" }, 56 - "sceneType": { "type": "string" }, 57 - "sensitivityType": { "type": "integer" }, 58 - "sharpness": { "type": "string" }, 59 - "shutterSpeedValue": { "type": "integer" }, 60 - "software": { "type": "string" }, 61 - "whiteBalance": { "type": "string" }, 62 - "xResolution": { "type": "integer" }, 63 - "yResolution": { "type": "integer" } 27 + "model": { "type": "string" } 64 28 } 65 29 } 66 30 }
+12 -1
local-infra/pds.env
··· 11 11 PDS_DID_PLC_URL=http://plc:8080 12 12 PDS_HOSTNAME=pds.dev.grain.social 13 13 PDS_EMAIL_SMTP_URL=smtp://maildev:1025 14 - PDS_EMAIL_FROM_ADDRESS=admin@grain.social 14 + PDS_EMAIL_FROM_ADDRESS=support@grain.social 15 15 PDS_SERVICE_NAME=Grain Social 16 16 PDS_INVITE_REQUIRED=0 17 + PDS_OAUTH_PROVIDER_NAME=Grain Social 18 + PDS_HOME_URL=http://localhost:8080 19 + PDS_LIGHT_COLOR=#fff 20 + PDS_DARK_COLOR=#09090b 21 + PDS_PRIMARY_COLOR=#00a6f4 22 + PDS_PRIMARY_COLOR_CONTRAST=#fff 23 + PDS_PRIMARY_COLOR_HUE=#fff 24 + PDS_TERMS_OF_SERVICE_URL = 'http://localhost:8080/support/terms' 25 + PDS_PRIVACY_POLICY_URL = 'http://localhost:8080/support/privacy' 26 + PDS_SUPPORT_URL= 'http://localhost:8080/support' 27 + PDS_CONTACT_EMAIL_ADDRESS = 'support@grain.social'
+2
services/nginx/Dockerfile
··· 1 + FROM nginx 2 + COPY nginx.conf /etc/nginx/nginx.conf
+23
services/nginx/fly.toml
··· 1 + # fly.toml app configuration file generated for grain-nginx on 2025-06-01T22:28:51-07:00 2 + # 3 + # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 + # 5 + 6 + app = 'grain-nginx' 7 + primary_region = 'sea' 8 + 9 + [build] 10 + dockerfile = './Dockerfile' 11 + 12 + [http_service] 13 + internal_port = 80 14 + force_https = true 15 + auto_stop_machines = 'stop' 16 + auto_start_machines = true 17 + min_machines_running = 0 18 + processes = ['app'] 19 + 20 + [[vm]] 21 + memory = '256mb' 22 + cpu_kind = 'shared' 23 + cpus = 1
+46
services/nginx/nginx.conf
··· 1 + worker_processes 1; 2 + 3 + events { 4 + worker_connections 1024; 5 + } 6 + 7 + http { 8 + resolver [fdaa::3]; 9 + client_max_body_size 50M; 10 + 11 + map $http_host $pds { 12 + default http://grain-pds.internal:3000; 13 + } 14 + 15 + map $http_host $appview { 16 + default http://atphoto.internal:8080; 17 + } 18 + 19 + map $http_upgrade $connection_upgrade { 20 + default upgrade; 21 + '' close; 22 + } 23 + 24 + server { 25 + listen 80; 26 + server_name *.grain.social; 27 + 28 + location /xrpc { 29 + proxy_pass $pds; 30 + proxy_set_header Host $host; 31 + 32 + proxy_set_header Upgrade $http_upgrade; 33 + proxy_set_header Connection $connection_upgrade; 34 + } 35 + 36 + location = /.well-known/atproto-did { 37 + proxy_pass $pds/.well-known/atproto-did; 38 + proxy_set_header Host $host; 39 + } 40 + 41 + location / { 42 + proxy_pass $appview; 43 + proxy_set_header Host $host; 44 + } 45 + } 46 + }
+25
services/pds/.env.example
··· 1 + # You do not necessarily need to fill this file out, this is just for reference 2 + # If you do want to use the official pdsadmin tool, then you should fill this out 3 + 4 + # public 5 + PDS_HOSTNAME=pds.example.com 6 + PDS_DATA_DIRECTORY=/pds 7 + PDS_BLOBSTORE_DISK_LOCATION=/pds/blocks 8 + PDS_BLOB_UPLOAD_LIMIT=52428800 9 + PDS_DID_PLC_URL=https://plc.directory 10 + PDS_BSKY_APP_VIEW_URL=https://api.bsky.app 11 + PDS_BSKY_APP_VIEW_DID=did:web:api.bsky.app 12 + PDS_REPORT_SERVICE_URL=https://mod.bsky.app 13 + PDS_REPORT_SERVICE_DID=did:plc:ar7c4by46qjdydhdevvrndac 14 + PDS_CRAWLERS=https://bsky.network 15 + LOG_ENABLED=true 16 + PDS_SERVICE_HANDLE_DOMAINS=.example.com 17 + 18 + # private 19 + PDS_JWT_SECRET=<secret> 20 + PDS_ADMIN_PASSWORD=<secret> 21 + PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=<secret> 22 + 23 + # private: email 24 + PDS_EMAIL_SMTP_URL=smtps://resend:<secret api key here>@smtp.resend.com:465/ 25 + PDS_EMAIL_FROM_ADDRESS=support@your.domain
+65
services/pds/README.md
··· 1 + # Setting up a PDS 2 + 3 + 1. Customizing _fly.toml_ 4 + - You should replace values `app`, `primary_region`, `env.PDS_HOSTNAME` to 5 + values that will make sense for your installation. 6 + - `app` controls the name of the project on fly.io 7 + - `primary_region` controls where the app will be deployed globally, `iad` 8 + is in Northern Virginia (USA) 9 + - `[env]`, `PDS_HOSTNAME` should make the URL from where you plan to reach 10 + the application, so for example, if you're planning to add a DNS entry to 11 + reach your PDS from `my-pds.my-site.com`, then, use that as the value 12 + here 13 + 2. Generate the necessary secret values for your PDS 14 + > 🚧 All of these values are super secret, do not share them! 15 + > 16 + > Make sure you have them written down somewhere because fly.io will never 17 + > let you see them again 18 + 1. _PDS_JWT_SECRET_: `openssl rand --hex 16` 19 + 2. _PDS_ADMIN_PASSWORD_: `openssl rand --hex 16` 20 + 3. _PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX_: 21 + `openssl ecparam --name secp256k1 --genkey --noout --outform DER | tail --bytes=+8 | head --bytes=32 | xxd --plain --cols 32` 22 + 3. Create the project in fly.io 23 + 1. Run `fly launch --no-deploy`, this will create the project on fly without 24 + deploying it. You need to make some changes ahead of an initial deployment 25 + 2. Create the volume that you specified earlier, make sure to choose the 26 + primary_region as the region for your volume `fly volume create pdsdata` 27 + 3. Apply the secrets you generated earlier 28 + `fly secrets set PDS_JWT_SECRET=secret PDS_ADMIN_PASSWORD=secret PDS_PLC_ROTATION_KEY_K256_PRIVATE_KEY_HEX=secret` 29 + 4. Deploy the app using `fly deploy` 30 + > 🚧 This should create only one machine, make sure using `fly m ls` 31 + > 32 + > If you have more than one machine scale down using `fly scale count 1` 33 + 5. Test your PDS: You can do this quickly by visitng 34 + `https://<your-app-name>.fly.dev/xrpc/com.atproto.sync.listRepos`, at this 35 + point you should see a response like this: 36 + ```json 37 + { "repos": [] } 38 + ``` 39 + 4. Setup your DNS 40 + 41 + - You need to create an entry for your PDS's hostname in the DNS console you use 42 + for your domain name: `pds.example.com` 43 + 44 + > 🚧 You need to create an entry that allows you to map handles to the pds 45 + > 46 + > The handle `username.pds.example.com` needs be able to resolve, so your PDS 47 + > should also be available at `username.pds.example.com`. If you don't do this, 48 + > other atproto services can't resolve the handle and you get `Invalid Handle` 49 + > everywhere you go 50 + 51 + - Now you should be able to reach your PDS at 52 + `https://pds.example.com/xrpc/com.atproto.sync.listRepos` 53 + 54 + 5. Bonus, Setting up emails: Blue Sky will ask you to verify your email, but, 55 + without having a mail service setup, you'll never be able to get the 56 + confirmation code! Follow the official PDS guide on setting up email 57 + services, it covers the topic fully: 58 + [link](https://github.com/bluesky-social/pds?tab=readme-ov-file#setting-up-smtp) 59 + > 🚧 Remember: You can add secrets to your fly service using 60 + > 61 + > `fly secrets set KEY1=VALUE1 KEY2=VALE2 ...` 62 + 63 + # Credits 64 + 65 + [keaysma](https://github.com/keaysma/pds-fly.io-template)
+234
services/pds/account.sh
··· 1 + #!/bin/bash 2 + set -o errexit 3 + set -o nounset 4 + set -o pipefail 5 + 6 + PDS_ENV_FILE=${PDS_ENV_FILE:-".env"} 7 + source "${PDS_ENV_FILE}" 8 + 9 + # curl a URL and fail if the request fails. 10 + function curl_cmd_get { 11 + curl --fail --silent --show-error "$@" 12 + } 13 + 14 + # curl a URL and fail if the request fails. 15 + function curl_cmd_post { 16 + curl --fail --silent --show-error --request POST --header "Content-Type: application/json" "$@" 17 + } 18 + 19 + # curl a URL but do not fail if the request fails. 20 + function curl_cmd_post_nofail { 21 + curl --silent --show-error --request POST --header "Content-Type: application/json" "$@" 22 + } 23 + 24 + # The subcommand to run. 25 + SUBCOMMAND="${1:-}" 26 + 27 + # 28 + # account list 29 + # 30 + if [[ "${SUBCOMMAND}" == "list" ]]; then 31 + DIDS="$(curl_cmd_get \ 32 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.sync.listRepos?limit=100" | jq --raw-output '.repos[].did' 33 + )" 34 + OUTPUT='[{"handle":"Handle","email":"Email","did":"DID"}' 35 + for did in ${DIDS}; do 36 + ITEM="$(curl_cmd_get \ 37 + --user "admin:${PDS_ADMIN_PASSWORD}" \ 38 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.getAccountInfo?did=${did}" 39 + )" 40 + OUTPUT="${OUTPUT},${ITEM}" 41 + done 42 + OUTPUT="${OUTPUT}]" 43 + echo "${OUTPUT}" | jq --raw-output '.[] | [.handle, .email, .did] | @tsv' | column -t 44 + 45 + # 46 + # account create 47 + # 48 + elif [[ "${SUBCOMMAND}" == "create" ]]; then 49 + EMAIL="${2:-}" 50 + HANDLE="${3:-}" 51 + 52 + if [[ "${EMAIL}" == "" ]]; then 53 + read -p "Enter an email address (e.g. alice@${PDS_HOSTNAME}): " EMAIL 54 + fi 55 + if [[ "${HANDLE}" == "" ]]; then 56 + read -p "Enter a handle (e.g. alice.${PDS_HOSTNAME}): " HANDLE 57 + fi 58 + 59 + if [[ "${EMAIL}" == "" || "${HANDLE}" == "" ]]; then 60 + echo "ERROR: missing EMAIL and/or HANDLE parameters." >/dev/stderr 61 + echo "Usage: $0 ${SUBCOMMAND} <EMAIL> <HANDLE>" >/dev/stderr 62 + exit 1 63 + fi 64 + 65 + PASSWORD="$(openssl rand -base64 30 | tr -d "=+/" | cut -c1-24)" 66 + INVITE_CODE="$(curl_cmd_post \ 67 + --user "admin:${PDS_ADMIN_PASSWORD}" \ 68 + --data '{"useCount": 1}' \ 69 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.server.createInviteCode" | jq --raw-output '.code' 70 + )" 71 + RESULT="$(curl_cmd_post_nofail \ 72 + --data "{\"email\":\"${EMAIL}\", \"handle\":\"${HANDLE}\", \"password\":\"${PASSWORD}\", \"inviteCode\":\"${INVITE_CODE}\"}" \ 73 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.server.createAccount" 74 + )" 75 + 76 + DID="$(echo $RESULT | jq --raw-output '.did')" 77 + if [[ "${DID}" != did:* ]]; then 78 + ERR="$(echo ${RESULT} | jq --raw-output '.message')" 79 + echo "ERROR: ${ERR}" >/dev/stderr 80 + echo "Usage: $0 ${SUBCOMMAND} <EMAIL> <HANDLE>" >/dev/stderr 81 + exit 1 82 + fi 83 + 84 + echo 85 + echo "Account created successfully!" 86 + echo "-----------------------------" 87 + echo "Handle : ${HANDLE}" 88 + echo "DID : ${DID}" 89 + echo "Password : ${PASSWORD}" 90 + echo "-----------------------------" 91 + echo "Save this password, it will not be displayed again." 92 + echo 93 + 94 + # 95 + # account delete 96 + # 97 + elif [[ "${SUBCOMMAND}" == "delete" ]]; then 98 + DID="${2:-}" 99 + 100 + if [[ "${DID}" == "" ]]; then 101 + echo "ERROR: missing DID parameter." >/dev/stderr 102 + echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr 103 + exit 1 104 + fi 105 + 106 + if [[ "${DID}" != did:* ]]; then 107 + echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr 108 + echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr 109 + exit 1 110 + fi 111 + 112 + echo "This action is permanent." 113 + read -r -p "Are you sure you'd like to delete ${DID}? [y/N] " response 114 + if [[ ! "${response}" =~ ^([yY][eE][sS]|[yY])$ ]]; then 115 + exit 0 116 + fi 117 + 118 + curl_cmd_post \ 119 + --user "admin:${PDS_ADMIN_PASSWORD}" \ 120 + --data "{\"did\": \"${DID}\"}" \ 121 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.deleteAccount" >/dev/null 122 + 123 + echo "${DID} deleted" 124 + 125 + # 126 + # account takedown 127 + # 128 + elif [[ "${SUBCOMMAND}" == "takedown" ]]; then 129 + DID="${2:-}" 130 + TAKEDOWN_REF="$(date +%s)" 131 + 132 + if [[ "${DID}" == "" ]]; then 133 + echo "ERROR: missing DID parameter." >/dev/stderr 134 + echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr 135 + exit 1 136 + fi 137 + 138 + if [[ "${DID}" != did:* ]]; then 139 + echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr 140 + echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr 141 + exit 1 142 + fi 143 + 144 + PAYLOAD="$(cat <<EOF 145 + { 146 + "subject": { 147 + "\$type": "com.atproto.admin.defs#repoRef", 148 + "did": "${DID}" 149 + }, 150 + "takedown": { 151 + "applied": true, 152 + "ref": "${TAKEDOWN_REF}" 153 + } 154 + } 155 + EOF 156 + )" 157 + 158 + curl_cmd_post \ 159 + --user "admin:${PDS_ADMIN_PASSWORD}" \ 160 + --data "${PAYLOAD}" \ 161 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.updateSubjectStatus" >/dev/null 162 + 163 + echo "${DID} taken down" 164 + 165 + # 166 + # account untakedown 167 + # 168 + elif [[ "${SUBCOMMAND}" == "untakedown" ]]; then 169 + DID="${2:-}" 170 + 171 + if [[ "${DID}" == "" ]]; then 172 + echo "ERROR: missing DID parameter." >/dev/stderr 173 + echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr 174 + exit 1 175 + fi 176 + 177 + if [[ "${DID}" != did:* ]]; then 178 + echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr 179 + echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr 180 + exit 1 181 + fi 182 + 183 + PAYLOAD=$(cat <<EOF 184 + { 185 + "subject": { 186 + "\$type": "com.atproto.admin.defs#repoRef", 187 + "did": "${DID}" 188 + }, 189 + "takedown": { 190 + "applied": false 191 + } 192 + } 193 + EOF 194 + ) 195 + 196 + curl_cmd_post \ 197 + --user "admin:${PDS_ADMIN_PASSWORD}" \ 198 + --data "${PAYLOAD}" \ 199 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.updateSubjectStatus" >/dev/null 200 + 201 + echo "${DID} untaken down" 202 + # 203 + # account reset-password 204 + # 205 + elif [[ "${SUBCOMMAND}" == "reset-password" ]]; then 206 + DID="${2:-}" 207 + PASSWORD="$(openssl rand -base64 30 | tr -d "=+/" | cut -c1-24)" 208 + 209 + if [[ "${DID}" == "" ]]; then 210 + echo "ERROR: missing DID parameter." >/dev/stderr 211 + echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr 212 + exit 1 213 + fi 214 + 215 + if [[ "${DID}" != did:* ]]; then 216 + echo "ERROR: DID parameter must start with \"did:\"." >/dev/stderr 217 + echo "Usage: $0 ${SUBCOMMAND} <DID>" >/dev/stderr 218 + exit 1 219 + fi 220 + 221 + curl_cmd_post \ 222 + --user "admin:${PDS_ADMIN_PASSWORD}" \ 223 + --data "{ \"did\": \"${DID}\", \"password\": \"${PASSWORD}\" }" \ 224 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.admin.updateAccountPassword" >/dev/null 225 + 226 + echo 227 + echo "Password reset for ${DID}" 228 + echo "New password: ${PASSWORD}" 229 + echo 230 + 231 + else 232 + echo "Unknown subcommand: ${SUBCOMMAND}" >/dev/stderr 233 + exit 1 234 + fi
+56
services/pds/fly.toml
··· 1 + # fly.toml app configuration file generated for grain-pds on 2025-06-01T21:15:20-07:00 2 + # 3 + # See https://fly.io/docs/reference/configuration/ for information about how to use this file. 4 + # 5 + 6 + app = 'grain-pds' 7 + primary_region = 'sea' 8 + 9 + [build] 10 + image = 'ghcr.io/bluesky-social/pds:0.4' 11 + 12 + [env] 13 + LOG_ENABLED = 'true' 14 + PDS_BLOBSTORE_DISK_LOCATION = '/pds/blocks' 15 + PDS_BLOB_UPLOAD_LIMIT = '52428800' 16 + PDS_BSKY_APP_VIEW_DID = 'did:web:api.bsky.app' 17 + PDS_BSKY_APP_VIEW_URL = 'https://api.bsky.app' 18 + PDS_CRAWLERS = 'https://bsky.network' 19 + PDS_DATA_DIRECTORY = '/pds' 20 + PDS_DID_PLC_URL = 'https://plc.directory' 21 + PDS_HOSTNAME = 'ansel.grainsocial.network' 22 + PDS_REPORT_SERVICE_DID = 'did:plc:ar7c4by46qjdydhdevvrndac' 23 + PDS_REPORT_SERVICE_URL = 'https://mod.bsky.app' 24 + PDS_SERVICE_HANDLE_DOMAINS = '.grain.social' 25 + PDS_SERVICE_NAME = 'Grain Social' 26 + PDS_HOME_URL = 'https://grain.social' 27 + PDS_LIGHT_COLOR = '#fff' 28 + PDS_DARK_COLOR = '#09090b' 29 + PDS_PRIMARY_COLOR = '#00a6f4' 30 + PDS_PRIMARY_COLOR_CONTRAST = '#fff' 31 + PDS_PRIMARY_COLOR_HUE = '#fff' 32 + PDS_TERMS_OF_SERVICE_URL = 'https://grain.social/support/terms' 33 + PDS_PRIVACY_POLICY_URL = 'https://grain.social/support/privacy' 34 + PDS_SUPPORT_URL= 'https://grain.social/support' 35 + PDS_CONTACT_EMAIL_ADDRESS = 'support@grain.social' 36 + 37 + [[mounts]] 38 + source = 'pdsdata' 39 + destination = '/pds' 40 + 41 + [[services]] 42 + protocol = 'tcp' 43 + internal_port = 3000 44 + auto_stop_machines = 'off' 45 + auto_start_machines = true 46 + min_machines_running = 1 47 + processes = ['app'] 48 + 49 + [[services.ports]] 50 + port = 443 51 + handlers = ['tls', 'http'] 52 + 53 + [[vm]] 54 + memory = '512mb' 55 + cpu_kind = 'shared' 56 + cpus = 1
+35
services/pds/pdsadmin-command.sh
··· 1 + # pdsadmin is a tool for managing the Personal Data Store (PDS) server. 2 + # But at the end of the day, it's just a bash script that makes curl requests 3 + # Even worse, it does all sorts of annoying checks that don't apply to OSX 4 + # So I have reversed engineered the requests I cared about and put them here 5 + 6 + # You can copy and paste these into your terminal, 7 + # Remove the underscores before the curl command 8 + # Replace the variables with your own values 9 + 10 + PDS_ENV_FILE=${PDS_ENV_FILE:-".env"} 11 + source "${PDS_ENV_FILE}" 12 + 13 + export DID="" 14 + 15 + # make an invite code 16 + curl \ 17 + --fail \ 18 + --silent \ 19 + --show-error \ 20 + --request POST \ 21 + --header "Content-Type: application/json" \ 22 + --user "admin:${PDS_ADMIN_PASSWORD}" \ 23 + --data '{"useCount": 20}' \ 24 + "https://${PDS_HOSTNAME}/xrpc/com.atproto.server.createInviteCode" 25 + 26 + # delete an account 27 + # curl \ 28 + # --fail \ 29 + # --silent \ 30 + # --show-error \ 31 + # --request POST \ 32 + # --header "Content-Type: application/json" \ 33 + # --user "admin:${PDS_ADMIN_PASSWORD}" \ 34 + # --data "{\"did\": \"${DID}\"}" \ 35 + # "https://${PDS_HOST}/xrpc/com.atproto.admin.deleteAccount"
+35
services/pds/request-crawl.sh
··· 1 + #!/bin/bash 2 + set -o errexit 3 + set -o nounset 4 + set -o pipefail 5 + 6 + PDS_ENV_FILE=${PDS_ENV_FILE:-".env"} 7 + source "${PDS_ENV_FILE}" 8 + 9 + RELAY_HOSTS="${1:-}" 10 + if [[ "${RELAY_HOSTS}" == "" ]]; then 11 + RELAY_HOSTS="${PDS_CRAWLERS}" 12 + fi 13 + 14 + if [[ "${RELAY_HOSTS}" == "" ]]; then 15 + echo "ERROR: missing RELAY HOST parameter." >/dev/stderr 16 + echo "Usage: $0 <RELAY HOST>[,<RELAY HOST>,...]" >/dev/stderr 17 + exit 1 18 + fi 19 + 20 + for host in ${RELAY_HOSTS//,/ }; do 21 + echo "Requesting crawl from ${host}" 22 + if [[ $host != https:* && $host != http:* ]]; then 23 + host="https://${host}" 24 + fi 25 + curl \ 26 + --fail \ 27 + --silent \ 28 + --show-error \ 29 + --request POST \ 30 + --header "Content-Type: application/json" \ 31 + --data "{\"hostname\": \"${PDS_HOSTNAME}\"}" \ 32 + "${host}/xrpc/com.atproto.sync.requestCrawl" >/dev/null 33 + done 34 + 35 + echo "done"
+5 -11
src/app.tsx
··· 6 6 7 7 export function Root(props: Readonly<RootProps<State>>) { 8 8 const profile = props.ctx.state.profile; 9 - const scripts = props.ctx.state.scripts; 10 9 const hasNotifications = 11 10 props.ctx.state.notifications?.find((n) => n.isRead === false) !== 12 11 undefined; ··· 26 25 /> 27 26 ) 28 27 : null} 29 - <script src="https://unpkg.com/htmx.org@1.9.10" /> 30 - <script src="https://unpkg.com/hyperscript.org@0.9.14" /> 31 - <script src="https://unpkg.com/sortablejs@1.15.6" /> 32 - <script src="https://cdn.jsdelivr.net/npm/exifr/dist/lite.umd.js" /> 33 28 <style dangerouslySetInnerHTML={{ __html: CSS }} /> 34 29 <link 35 30 rel="stylesheet" ··· 50 45 href="https://unpkg.com/@fortawesome/fontawesome-free@6.7.2/css/all.min.css" 51 46 preload 52 47 /> 53 - {scripts?.map((file) => ( 54 - <script 55 - key={file} 56 - src={`/static/${file}?${staticFilesHash?.get(file)}`} 57 - /> 58 - ))} 48 + <script 49 + type="module" 50 + key="app.esm.js" 51 + src={`/static/app.esm.js?${staticFilesHash?.get("app.esm.js")}`} 52 + /> 59 53 </head> 60 54 <body class="h-full dark:bg-zinc-950 dark:text-white"> 61 55 <Layout id="layout">
+1 -1
src/components/AvatarInput.tsx
··· 22 22 id="file" 23 23 name="file" 24 24 accept="image/*" 25 - _="on change call Grain.handleAvatarImageSelect(me)" 25 + _="on change call Grain.profileDialog.handleAvatarImageSelect(me)" 26 26 /> 27 27 </label> 28 28 );
+27
src/components/Breadcrumb.tsx
··· 1 + type BreadcrumbItem = { 2 + label: string; 3 + href?: string; 4 + }; 5 + 6 + export function Breadcrumb({ items }: Readonly<{ items: BreadcrumbItem[] }>) { 7 + return ( 8 + <nav className="mb-4 text-sm text-zinc-500 dark:text-zinc-300"> 9 + {items.map((item, idx) => ( 10 + <> 11 + {item.href 12 + ? ( 13 + <a href={item.href} className="text-sky-500 hover:underline"> 14 + {item.label} 15 + </a> 16 + ) 17 + : ( 18 + <span className="text-zinc-700 dark:text-zinc-100"> 19 + {item.label} 20 + </span> 21 + )} 22 + {idx < items.length - 1 && <span className="mx-2">&gt;</span>} 23 + </> 24 + ))} 25 + </nav> 26 + ); 27 + }
+22
src/components/CameraBadges.tsx
··· 1 + import { cn } from "@bigmoves/bff/components"; 2 + 3 + export function CameraBadges( 4 + { cameras, class: classProp }: Readonly< 5 + { cameras: string[]; class?: string } 6 + >, 7 + ) { 8 + if (cameras.length === 0) return null; 9 + return ( 10 + <div class={cn("flex gap-1", classProp)} id="camera-badges"> 11 + {cameras.sort().map((camera) => ( 12 + <span class="text-xs font-semibold bg-zinc-100 dark:bg-zinc-800 w-fit px-1"> 13 + 📷 {camera} 14 + </span> 15 + ))} 16 + </div> 17 + ); 18 + } 19 + 20 + // <span class="text-xs font-semibold bg-zinc-100 dark:bg-zinc-800 w-fit px-1"> 21 + // 📷 {cameras.join(", ").replace(/, ([^,]*)$/, " & $1")} 22 + // </span>
+47
src/components/CreateAccountDialog.tsx
··· 1 + import { OAUTH_ROUTES } from "@bigmoves/bff"; 2 + import { Button, Dialog } from "@bigmoves/bff/components"; 3 + import { PDS_HOST_URL } from "../env.ts"; 4 + 5 + export function CreateAccountDialog({}: Readonly<{}>) { 6 + return ( 7 + <Dialog id="photo-alt-dialog" class="z-100"> 8 + <Dialog.Content class="dark:bg-zinc-950 relative"> 9 + <Dialog.X class="fill-zinc-950 dark:fill-zinc-50" /> 10 + <Dialog.Title>Choose your handle</Dialog.Title> 11 + <div className="flex flex-col space-y-4 my-10"> 12 + <form hx-post={OAUTH_ROUTES.signup} hx-swap="none" class="w-full"> 13 + <input 14 + type="hidden" 15 + name="pdsHostUrl" 16 + value="https://bsky.social" 17 + /> 18 + <Button 19 + type="submit" 20 + variant="primary" 21 + class="w-full" 22 + > 23 + user.bsky.social 24 + </Button> 25 + </form> 26 + <form hx-post={OAUTH_ROUTES.signup} hx-swap="none" class="w-full"> 27 + <input 28 + type="hidden" 29 + name="pdsHostUrl" 30 + value={PDS_HOST_URL} 31 + /> 32 + <Button 33 + type="submit" 34 + variant="primary" 35 + class="w-full" 36 + > 37 + user.grain.social 38 + </Button> 39 + </form> 40 + <p> 41 + Note: <b>.grain.social</b> handles are currently invite only. 42 + </p> 43 + </div> 44 + </Dialog.Content> 45 + </Dialog> 46 + ); 47 + }
+33
src/components/ExifOverlayDialog.tsx
··· 1 + import { PhotoView } from "$lexicon/types/social/grain/photo/defs.ts"; 2 + import { Dialog } from "@bigmoves/bff/components"; 3 + import { getOrderedExifData } from "../lib/photo.ts"; 4 + 5 + export function ExifOverlayDialog({ 6 + photo, 7 + }: Readonly<{ 8 + photo: PhotoView; 9 + }>) { 10 + return ( 11 + <Dialog class="z-101"> 12 + <Dialog.Content 13 + class="bg-transparent text-zinc-50 relative" 14 + _={Dialog._closeOnClick} 15 + > 16 + <Dialog.Title>Camera Settings</Dialog.Title> 17 + <Dialog.X /> 18 + {photo.exif && ( 19 + <div className="mt-4 text-sm"> 20 + <dl className="grid grid-cols-2 gap-x-4 gap-y-2"> 21 + {getOrderedExifData(photo).map(({ displayKey, value }) => ( 22 + <> 23 + <dt className="font-medium text-right">{displayKey}:</dt> 24 + <dd className="text-left">{String(value)}</dd> 25 + </> 26 + ))} 27 + </dl> 28 + </div> 29 + )} 30 + </Dialog.Content> 31 + </Dialog> 32 + ); 33 + }
+41
src/components/FollowsList.tsx
··· 1 + import { ProfileView } from "$lexicon/types/social/grain/actor/defs.ts"; 2 + import { profileLink } from "../utils.ts"; 3 + import { ActorAvatar } from "./ActorAvatar.tsx"; 4 + 5 + export function FollowsList( 6 + { profiles }: Readonly<{ profiles: ProfileView[] }>, 7 + ) { 8 + return ( 9 + <ul class="space-y-4 relative divide-zinc-200 dark:divide-zinc-800 divide-y"> 10 + {profiles.length === 0 11 + ? ( 12 + <li> 13 + Not following anyone yet. 14 + </li> 15 + ) 16 + : ( 17 + profiles.map((profile) => ( 18 + <li key={profile.did} class="pb-4"> 19 + <a 20 + href={profileLink(profile.handle)} 21 + class="flex items-center" 22 + > 23 + <div class="flex flex-col space-y-2"> 24 + <div class="flex items-center"> 25 + <ActorAvatar profile={profile} size={32} class="mr-2" /> 26 + <div class="flex flex-col"> 27 + <p>{profile.displayName}</p> 28 + <p class="text-zinc-600 dark:text-zinc-500"> 29 + @{profile.handle || profile.displayName} 30 + </p> 31 + </div> 32 + </div> 33 + <p>{profile.description}</p> 34 + </div> 35 + </a> 36 + </li> 37 + )) 38 + )} 39 + </ul> 40 + ); 41 + }
+23
src/components/GalleryInfo.tsx
··· 1 + import { Record as Gallery } from "$lexicon/types/social/grain/gallery.ts"; 2 + import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts"; 3 + import { getGalleryCameras } from "../lib/gallery.ts"; 4 + import { ActorInfo } from "./ActorInfo.tsx"; 5 + import { CameraBadges } from "./CameraBadges.tsx"; 6 + 7 + export function GalleryInfo({ gallery }: Readonly<{ gallery: GalleryView }>) { 8 + const description = (gallery.record as Gallery).description; 9 + const cameras = getGalleryCameras(gallery); 10 + return ( 11 + <div 12 + class="flex flex-col space-y-2 mb-4 max-w-[500px]" 13 + id="gallery-info" 14 + > 15 + <h1 class="font-bold text-2xl"> 16 + {(gallery.record as Gallery).title} 17 + </h1> 18 + <ActorInfo profile={gallery.creator} /> 19 + {description ? <p>{description}</p> : null} 20 + <CameraBadges class="my-1" cameras={cameras} /> 21 + </div> 22 + ); 23 + }
+103
src/components/GalleryLayout.tsx
··· 1 + import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts"; 2 + import { PhotoView } from "$lexicon/types/social/grain/photo/defs.ts"; 3 + import { AtUri } from "@atproto/syntax"; 4 + import { Button } from "@bigmoves/bff/components"; 5 + import { ComponentChildren } from "preact"; 6 + import { photoDialogLink } from "../utils.ts"; 7 + import { JustifiedSvg } from "./JustifiedSvg.tsx"; 8 + import { MasonrySvg } from "./MasonrySvg.tsx"; 9 + 10 + interface GalleryLayoutProps { 11 + children: ComponentChildren; 12 + layoutButtons?: ComponentChildren; 13 + } 14 + 15 + function GalleryLayout({ children, layoutButtons }: GalleryLayoutProps) { 16 + return ( 17 + <> 18 + {layoutButtons 19 + ? ( 20 + <div class="mb-2 flex justify-end"> 21 + {layoutButtons} 22 + </div> 23 + ) 24 + : null} 25 + {children} 26 + </> 27 + ); 28 + } 29 + 30 + function GalleryContainer({ children }: { children: ComponentChildren }) { 31 + return ( 32 + <div 33 + id="gallery-container" 34 + class="h-0 overflow-hidden relative mx-auto w-full" 35 + > 36 + {children} 37 + </div> 38 + ); 39 + } 40 + 41 + function GalleryLayoutModeButton({ 42 + mode, 43 + }: Readonly<{ 44 + mode: "justified" | "masonry"; 45 + }>) { 46 + return ( 47 + <Button 48 + id={`${mode}-button`} 49 + title={`${mode.charAt(0).toUpperCase() + mode.slice(1)} layout`} 50 + variant="primary" 51 + data-selected={mode === "justified" ? "true" : "false"} 52 + class="flex justify-center w-full sm:w-fit bg-zinc-100 dark:bg-zinc-800 border-zinc-100 dark:border-zinc-800 data-[selected=false]:bg-transparent data-[selected=false]:border-transparent text-zinc-950 dark:text-zinc-50" 53 + _={`on click call Grain.galleryLayout.setLayoutMode('${mode}') 54 + set @data-selected to 'true' 55 + set #${ 56 + mode === "justified" ? "masonry" : "justified" 57 + }-button's @data-selected to 'false'`} 58 + > 59 + {mode === "masonry" ? <MasonrySvg /> : <JustifiedSvg />} 60 + </Button> 61 + ); 62 + } 63 + 64 + function GalleryLayoutItem({ 65 + photo, 66 + gallery, 67 + }: Readonly<{ 68 + photo: PhotoView; 69 + gallery: GalleryView; 70 + }>) { 71 + return ( 72 + <button 73 + id={`photo-${new AtUri(photo.uri).rkey}`} 74 + type="button" 75 + hx-get={photoDialogLink(gallery, photo)} 76 + hx-trigger="click" 77 + hx-target="#layout" 78 + hx-swap="afterbegin" 79 + class="gallery-item absolute cursor-pointer" 80 + data-width={photo.aspectRatio?.width} 81 + data-height={photo.aspectRatio?.height} 82 + > 83 + <img 84 + src={photo.fullsize} 85 + alt={photo.alt} 86 + class="w-full h-full object-cover" 87 + /> 88 + {photo.alt 89 + ? ( 90 + <div class="absolute bg-zinc-950 dark:bg-zinc-900 bottom-1 right-1 sm:bottom-1 sm:right-1 text-xs text-white font-semibold py-[1px] px-[3px]"> 91 + ALT 92 + </div> 93 + ) 94 + : null} 95 + </button> 96 + ); 97 + } 98 + 99 + GalleryLayout.Container = GalleryContainer; 100 + GalleryLayout.ModeButton = GalleryLayoutModeButton; 101 + GalleryLayout.Item = GalleryLayoutItem; 102 + 103 + export { GalleryLayout };
+19 -126
src/components/GalleryPage.tsx
··· 1 1 import { Record as Favorite } from "$lexicon/types/social/grain/favorite.ts"; 2 - import { Record as Gallery } from "$lexicon/types/social/grain/gallery.ts"; 3 2 import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts"; 4 3 import { isPhotoView } from "$lexicon/types/social/grain/photo/defs.ts"; 5 4 import { AtUri } from "@atproto/syntax"; 6 5 import { WithBffMeta } from "@bigmoves/bff"; 7 6 import { Button } from "@bigmoves/bff/components"; 8 - import { ActorInfo } from "./ActorInfo.tsx"; 9 7 import { FavoriteButton } from "./FavoriteButton.tsx"; 10 - import { PhotoButton } from "./PhotoButton.tsx"; 8 + import { GalleryInfo } from "./GalleryInfo.tsx"; 9 + import { GalleryLayout } from "./GalleryLayout.tsx"; 11 10 import { ShareGalleryButton } from "./ShareGalleryButton.tsx"; 12 11 13 12 export function GalleryPage({ ··· 21 20 }>) { 22 21 const isCreator = currentUserDid === gallery.creator.did; 23 22 const isLoggedIn = !!currentUserDid; 24 - const description = (gallery.record as Gallery).description; 23 + const galleryItems = gallery.items?.filter(isPhotoView) ?? []; 25 24 return ( 26 - <div class="px-4"> 25 + <div class="px-4" id="gallery-page"> 27 26 <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mt-4 mb-2"> 28 - <div class="flex flex-col space-y-2 mb-4 max-w-[500px]"> 29 - <h1 class="font-bold text-2xl"> 30 - {(gallery.record as Gallery).title} 31 - </h1> 32 - <ActorInfo profile={gallery.creator} /> 33 - {description ? <p>{description}</p> : null} 34 - </div> 27 + <GalleryInfo gallery={gallery} /> 35 28 {isLoggedIn && isCreator 36 29 ? ( 37 30 <div class="flex self-start gap-2 w-full sm:w-fit flex-col sm:flex-row sm:flex-wrap sm:justify-end"> ··· 84 77 ) 85 78 : null} 86 79 </div> 87 - <div class="flex justify-end mb-2"> 88 - <Button 89 - id="justified-button" 90 - title="Justified layout" 91 - variant="primary" 92 - class="flex justify-center w-full sm:w-fit bg-zinc-100 dark:bg-zinc-800 border-zinc-100 dark:border-zinc-800 data-[selected=false]:bg-transparent data-[selected=false]:border-transparent text-zinc-950 dark:text-zinc-50" 93 - _="on click call Grain.toggleLayout('justified') 94 - set @data-selected to 'true' 95 - set #masonry-button's @data-selected to 'false'" 96 - > 97 - <svg 98 - width="24" 99 - height="24" 100 - viewBox="0 0 24 24" 101 - xmlns="http://www.w3.org/2000/svg" 102 - > 103 - <rect x="2" y="2" width="8" height="6" fill="currentColor" rx="1" /> 104 - <rect 105 - x="12" 106 - y="2" 107 - width="10" 108 - height="6" 109 - fill="currentColor" 110 - rx="1" 111 - /> 112 - <rect 113 - x="2" 114 - y="10" 115 - width="6" 116 - height="6" 117 - fill="currentColor" 118 - rx="1" 119 - /> 120 - <rect 121 - x="10" 122 - y="10" 123 - width="12" 124 - height="6" 125 - fill="currentColor" 126 - rx="1" 127 - /> 128 - <rect 129 - x="2" 130 - y="18" 131 - width="20" 132 - height="4" 133 - fill="currentColor" 134 - rx="1" 135 - /> 136 - </svg> 137 - </Button> 138 - <Button 139 - id="masonry-button" 140 - title="Masonry layout" 141 - variant="primary" 142 - data-selected="false" 143 - class="flex justify-center w-full sm:w-fit bg-zinc-100 dark:bg-zinc-800 border-zinc-100 dark:border-zinc-800 data-[selected=false]:bg-transparent data-[selected=false]:border-transparent text-zinc-950 dark:text-zinc-50" 144 - _="on click call Grain.toggleLayout('masonry') 145 - set @data-selected to 'true' 146 - set #justified-button's @data-selected to 'false'" 147 - > 148 - <svg 149 - width="24" 150 - height="24" 151 - viewBox="0 0 24 24" 152 - xmlns="http://www.w3.org/2000/svg" 153 - > 154 - <rect x="2" y="2" width="8" height="8" fill="currentColor" rx="1" /> 155 - <rect 156 - x="12" 157 - y="2" 158 - width="8" 159 - height="4" 160 - fill="currentColor" 161 - rx="1" 162 - /> 163 - <rect 164 - x="12" 165 - y="8" 166 - width="8" 167 - height="6" 168 - fill="currentColor" 169 - rx="1" 170 - /> 171 - <rect 172 - x="2" 173 - y="12" 174 - width="8" 175 - height="8" 176 - fill="currentColor" 177 - rx="1" 178 - /> 179 - <rect 180 - x="12" 181 - y="16" 182 - width="8" 183 - height="4" 184 - fill="currentColor" 185 - rx="1" 186 - /> 187 - </svg> 188 - </Button> 189 - </div> 190 - <div 191 - id="masonry-container" 192 - class="h-0 overflow-hidden relative mx-auto w-full" 193 - _="on load or htmx:afterSettle call Grain.computeLayout()" 80 + <GalleryLayout 81 + layoutButtons={ 82 + <> 83 + <GalleryLayout.ModeButton mode="justified" /> 84 + <GalleryLayout.ModeButton mode="masonry" /> 85 + </> 86 + } 194 87 > 195 - {gallery.items?.filter(isPhotoView)?.length 196 - ? gallery?.items 197 - ?.filter(isPhotoView) 198 - ?.map((photo) => ( 199 - <PhotoButton 88 + <GalleryLayout.Container> 89 + {galleryItems?.length 90 + ? galleryItems.map((photo) => ( 91 + <GalleryLayout.Item 200 92 key={photo.cid} 201 93 photo={photo} 202 94 gallery={gallery} 203 95 /> 204 96 )) 205 - : null} 206 - </div> 97 + : null} 98 + </GalleryLayout.Container> 99 + </GalleryLayout> 207 100 </div> 208 101 ); 209 102 }
+1 -1
src/components/GallerySortDialog.tsx
··· 7 7 { gallery }: Readonly<{ gallery: GalleryView }>, 8 8 ) { 9 9 return ( 10 - <Dialog class="z-100"> 10 + <Dialog class="z-100" id="gallery-sort-dialog"> 11 11 <Dialog.Content class="dark:bg-zinc-950 relative"> 12 12 <Dialog.X class="fill-zinc-950 dark:fill-zinc-50" /> 13 13 <Dialog.Title>Sort gallery</Dialog.Title>
+44
src/components/JustifiedSvg.tsx
··· 1 + export function JustifiedSvg() { 2 + return ( 3 + <svg 4 + width="24" 5 + height="24" 6 + viewBox="0 0 24 24" 7 + xmlns="http://www.w3.org/2000/svg" 8 + > 9 + <rect x="2" y="2" width="8" height="6" fill="currentColor" rx="1" /> 10 + <rect 11 + x="12" 12 + y="2" 13 + width="10" 14 + height="6" 15 + fill="currentColor" 16 + rx="1" 17 + /> 18 + <rect 19 + x="2" 20 + y="10" 21 + width="6" 22 + height="6" 23 + fill="currentColor" 24 + rx="1" 25 + /> 26 + <rect 27 + x="10" 28 + y="10" 29 + width="12" 30 + height="6" 31 + fill="currentColor" 32 + rx="1" 33 + /> 34 + <rect 35 + x="2" 36 + y="18" 37 + width="20" 38 + height="4" 39 + fill="currentColor" 40 + rx="1" 41 + /> 42 + </svg> 43 + ); 44 + }
+9 -5
src/components/Layout.tsx
··· 111 111 ) 112 112 : ( 113 113 <div class="flex items-center space-x-4"> 114 - <form hx-post="/signup" hx-swap="none" class="inline"> 115 - <Button variant="secondary" type="submit"> 116 - Create account 117 - </Button> 118 - </form> 114 + <Button 115 + variant="secondary" 116 + hx-get={`/dialogs/create-account`} 117 + hx-trigger="click" 118 + hx-target="body" 119 + hx-swap="afterbegin" 120 + > 121 + Create account 122 + </Button> 119 123 <Button variant="secondary" asChild> 120 124 <a href="/login"> 121 125 Sign in
+46 -9
src/components/LoginPage.tsx
··· 8 8 class="flex justify-center items-center w-full h-[calc(100vh-56px)] relative" 9 9 style="background-image: url('https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:bcgltzqazw5tb6k2g3ttenbj/bafkreiewhwu3ro5dv7omedphb62db4koa7qtvyzfhiiypg3ru4tvuxkrjy@jpeg'); background-size: cover; background-position: center;" 10 10 > 11 - <Login hx-target="#login" error={error} errorClass="text-white" /> 12 - <div class="absolute bottom-2 right-2 text-white text-sm"> 13 - Photo by{" "} 14 - <a 15 - href={profileLink("chadtmiller.com")} 16 - class="hover:underline font-semibold" 17 - > 18 - @chadtmiller.com 19 - </a> 11 + <Login 12 + hx-target="#login" 13 + error={error} 14 + errorClass="text-white" 15 + inputPlaceholder="Enter your handle or pds host" 16 + submitText="Login" 17 + infoText="e.g., user.bsky.social, user.grain.social, example.com, https://pds.example.com" 18 + infoClass="text-white text-sm! bg-zinc-950/70 p-4 font-mono" 19 + /> 20 + <div class="absolute bottom-2 left-2 right-2 flex flex-col sm:flex-row justify-between items-start sm:items-end text-white text-sm gap-1 sm:gap-0"> 21 + <div class="flex flex-col sm:flex-row"> 22 + <span> 23 + © 2025 Grain Social. All rights reserved. 24 + </span> 25 + <span class="flex flex-row items-center flex-wrap"> 26 + <a 27 + href="/support/terms" 28 + class="underline hover:no-underline ml-0 sm:ml-2 mt-1 sm:mt-0" 29 + > 30 + Terms 31 + </a> 32 + <span class="mx-1">|</span> 33 + <a 34 + href="/support/privacy" 35 + class="underline hover:no-underline ml-0 sm:ml-1 mt-1 sm:mt-0" 36 + > 37 + Privacy 38 + </a> 39 + <span class="mx-1">|</span> 40 + <a 41 + href="/support/copyright" 42 + class="underline hover:no-underline ml-0 sm:ml-1 mt-1 sm:mt-0" 43 + > 44 + Copyright 45 + </a> 46 + </span> 47 + </div> 48 + <div> 49 + Photo by{" "} 50 + <a 51 + href={profileLink("chadtmiller.com")} 52 + class="underline hover:no-underline" 53 + > 54 + @chadtmiller.com 55 + </a> 56 + </div> 20 57 </div> 21 58 </div> 22 59 );
+44
src/components/MasonrySvg.tsx
··· 1 + export function MasonrySvg() { 2 + return ( 3 + <svg 4 + width="24" 5 + height="24" 6 + viewBox="0 0 24 24" 7 + xmlns="http://www.w3.org/2000/svg" 8 + > 9 + <rect x="2" y="2" width="8" height="8" fill="currentColor" rx="1" /> 10 + <rect 11 + x="12" 12 + y="2" 13 + width="8" 14 + height="4" 15 + fill="currentColor" 16 + rx="1" 17 + /> 18 + <rect 19 + x="12" 20 + y="8" 21 + width="8" 22 + height="6" 23 + fill="currentColor" 24 + rx="1" 25 + /> 26 + <rect 27 + x="2" 28 + y="12" 29 + width="8" 30 + height="8" 31 + fill="currentColor" 32 + rx="1" 33 + /> 34 + <rect 35 + x="12" 36 + y="16" 37 + width="8" 38 + height="4" 39 + fill="currentColor" 40 + rx="1" 41 + /> 42 + </svg> 43 + ); 44 + }
+15 -3
src/components/NotificationsPage.tsx
··· 1 1 import { Record as Favorite } from "$lexicon/types/social/grain/favorite.ts"; 2 2 import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts"; 3 + import { Record as Follow } from "$lexicon/types/social/grain/graph/follow.ts"; 3 4 import { NotificationView } from "$lexicon/types/social/grain/notification/defs.ts"; 4 5 import { Un$Typed } from "$lexicon/util.ts"; 5 6 import { formatRelativeTime, profileLink } from "../utils.ts"; ··· 44 45 </span> 45 46 </a> 46 47 <span class="break-words"> 47 - favorited your gallery · {formatRelativeTime( 48 - new Date((notification.record as Favorite).createdAt), 48 + {notification.reason === "gallery-favorite" && ( 49 + <> 50 + favorited your gallery · {formatRelativeTime( 51 + new Date((notification.record as Favorite).createdAt), 52 + )} 53 + </> 54 + )} 55 + {notification.reason === "follow" && ( 56 + <> 57 + followed you · {formatRelativeTime( 58 + new Date((notification.record as Follow).createdAt), 59 + )} 60 + </> 49 61 )} 50 62 </span> 51 63 </div> 52 - {galleriesMap.get( 64 + {notification.reason === "gallery-favorite" && galleriesMap.get( 53 65 (notification.record as Favorite).subject, 54 66 ) 55 67 ? (
-39
src/components/PhotoButton.tsx
··· 1 - import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts"; 2 - import { PhotoView } from "$lexicon/types/social/grain/photo/defs.ts"; 3 - import { AtUri } from "@atproto/syntax"; 4 - import { photoDialogLink } from "../utils.ts"; 5 - 6 - export function PhotoButton({ 7 - photo, 8 - gallery, 9 - }: Readonly<{ 10 - photo: PhotoView; 11 - gallery: GalleryView; 12 - }>) { 13 - return ( 14 - <button 15 - id={`photo-${new AtUri(photo.uri).rkey}`} 16 - type="button" 17 - hx-get={photoDialogLink(gallery, photo)} 18 - hx-trigger="click" 19 - hx-target="#layout" 20 - hx-swap="afterbegin" 21 - class="masonry-tile absolute cursor-pointer" 22 - data-width={photo.aspectRatio?.width} 23 - data-height={photo.aspectRatio?.height} 24 - > 25 - <img 26 - src={photo.fullsize} 27 - alt={photo.alt} 28 - class="w-full h-full object-cover" 29 - /> 30 - {photo.alt 31 - ? ( 32 - <div class="absolute bg-zinc-950 dark:bg-zinc-900 bottom-1 right-1 sm:bottom-1 sm:right-1 text-xs text-white font-semibold py-[1px] px-[3px]"> 33 - ALT 34 - </div> 35 - ) 36 - : null} 37 - </button> 38 - ); 39 - }
+45 -2
src/components/PhotoDialog.tsx
··· 1 1 import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts"; 2 2 import { PhotoView } from "$lexicon/types/social/grain/photo/defs.ts"; 3 + import { AtUri } from "@atproto/syntax"; 3 4 import { Dialog } from "https://jsr.io/@bigmoves/bff/0.3.0-beta.21/components/Dialog.tsx"; 5 + import { cn } from "../../../bff/packages/bff/components/utils.ts"; 4 6 import { photoDialogLink } from "../utils.ts"; 5 7 6 8 export function PhotoDialog({ ··· 38 40 ) 39 41 : null} 40 42 <div 41 - class="flex flex-col w-5xl h-[calc(100vh-100px)] sm:h-screen z-20" 43 + class="flex flex-col w-5xl h-[calc(100vh-100px)] sm:h-screen z-20 relative sm:static" 42 44 _={Dialog._closeOnClick} 43 45 > 44 46 <div class="flex flex-col p-4 z-20 flex-1 relative"> ··· 48 50 class="absolute inset-0 w-full h-full object-contain" 49 51 /> 50 52 </div> 53 + {image.exif 54 + ? ( 55 + <div class="hidden sm:block absolute bottom-2 right-2"> 56 + <ExifButton photo={image} /> 57 + </div> 58 + ) 59 + : null} 51 60 {image.alt 52 61 ? ( 53 - <div class="px-4 sm:px-0 py-4 bg-black text-white text-left"> 62 + <div class="px-4 sm:px-0 py-4 bg-black text-white text-left flex"> 54 63 {image.alt} 64 + {image.exif 65 + ? ( 66 + <div class="block sm:hidden self-end justify-end -m-2"> 67 + <ExifButton photo={image} /> 68 + </div> 69 + ) 70 + : null} 55 71 </div> 56 72 ) 57 73 : null} 74 + {!image.alt && image.exif 75 + ? ( 76 + <ExifButton 77 + photo={image} 78 + class="block sm:hidden absolute bottom-2 right-2 z-100" 79 + /> 80 + ) 81 + : null} 58 82 </div> 59 83 </Dialog> 60 84 ); 61 85 } 86 + 87 + function ExifButton( 88 + { photo, class: classProp }: Readonly<{ photo: PhotoView; class?: string }>, 89 + ) { 90 + return ( 91 + <button 92 + type="button" 93 + class={cn("text-zinc-50 p-2 cursor-pointer", classProp)} 94 + hx-get={`/dialogs/photo/${new AtUri(photo.uri).rkey}/exif-overlay`} 95 + hx-trigger="click" 96 + hx-target="#layout" 97 + hx-swap="afterbegin" 98 + _="on click halt" 99 + > 100 + <i class="fa fa-camera" /> 101 + <span class="sr-only">Show EXIF</span> 102 + </button> 103 + ); 104 + }
+9 -39
src/components/PhotoExifDialog.tsx
··· 1 1 import { PhotoView } from "$lexicon/types/social/grain/photo/defs.ts"; 2 2 import { Dialog } from "@bigmoves/bff/components"; 3 + import { getOrderedExifData } from "../lib/photo.ts"; 3 4 4 5 export function PhotoExifDialog({ 5 6 photo, 6 7 }: Readonly<{ 7 8 photo: PhotoView; 8 9 }>) { 10 + console.log(getOrderedExifData(photo)); 9 11 return ( 10 12 <Dialog id="photo-alt-dialog" class="z-100"> 11 13 <Dialog.Content class="dark:bg-zinc-950 relative"> ··· 19 21 /> 20 22 </div> 21 23 {photo.exif && ( 22 - <div className="mt-4 text-sm text-zinc-700 dark:text-zinc-300"> 23 - { 24 - /* <a 25 - href={`https://pdsls.dev/${photo.exif.uri}`} 26 - className="my-4 hover:underline font-semibold block text-sky-500" 27 - > 28 - Inspect Record 29 - </a> */ 30 - } 24 + <div className="mt-4 text-sm"> 31 25 <dl className="grid grid-cols-2 gap-x-4 gap-y-2"> 32 - {Object.entries(photo.exif) 33 - .filter( 34 - ([key]) => 35 - ![ 36 - "$type", 37 - "photo", 38 - "createdAt", 39 - "uri", 40 - "cid", 41 - "did", 42 - "indexedAt", 43 - ].includes(key), 44 - ) 45 - .map(([key, value]) => { 46 - let displayKey; 47 - if (key.toLowerCase() === "iso") { 48 - displayKey = "ISO"; 49 - } else { 50 - displayKey = key 51 - .replace(/([a-z])([A-Z])/g, "$1 $2") 52 - .replace(/_/g, " ") 53 - .replace(/\b\w/g, (c) => c.toUpperCase()); 54 - } 55 - return ( 56 - <> 57 - <dt className="font-medium text-right">{displayKey}:</dt> 58 - <dd className="text-left">{String(value)}</dd> 59 - </> 60 - ); 61 - })} 26 + {getOrderedExifData(photo).map(({ displayKey, value }) => ( 27 + <> 28 + <dt className="font-medium text-right">{displayKey}:</dt> 29 + <dd className="text-left">{String(value)}</dd> 30 + </> 31 + ))} 62 32 </dl> 63 33 </div> 64 34 )}
+1 -1
src/components/ProfileDialog.tsx
··· 19 19 halt the event 20 20 put 'Updating...' into #submit-button.innerText 21 21 add @disabled to #submit-button 22 - call Grain.updateProfile(me) 22 + call Grain.profileDialog.updateProfile(me) 23 23 on htmx:afterOnLoad 24 24 put 'Update' into #submit-button.innerText 25 25 remove @disabled from #submit-button
+148 -39
src/components/ProfilePage.tsx
··· 5 5 import { Un$Typed } from "$lexicon/util.ts"; 6 6 import { AtUri } from "@atproto/syntax"; 7 7 import { Button, cn } from "@bigmoves/bff/components"; 8 - import { TimelineItem } from "../lib/timeline.ts"; 9 - import { bskyProfileLink, galleryLink, profileLink } from "../utils.ts"; 8 + import { getGalleryCameras } from "../lib/gallery.ts"; 9 + import type { SocialNetwork } from "../lib/timeline.ts"; 10 + import { 11 + bskyProfileLink, 12 + followersLink, 13 + followingLink, 14 + galleryLink, 15 + profileLink, 16 + } from "../utils.ts"; 17 + import { ActorAvatar } from "./ActorAvatar.tsx"; 10 18 import { AvatarButton } from "./AvatarButton.tsx"; 19 + import { CameraBadges } from "./CameraBadges.tsx"; 11 20 import { FollowButton } from "./FollowButton.tsx"; 12 - import { TimelineItem as Item } from "./TimelineItem.tsx"; 21 + 22 + export type ProfileTabs = "favs" | "galleries"; 13 23 14 24 export function ProfilePage({ 15 25 followUri, 26 + followersCount, 27 + followingCount, 28 + userProfiles, 16 29 loggedInUserDid, 17 - timelineItems, 18 30 profile, 19 31 selectedTab, 20 32 galleries, 33 + galleryFavs, 21 34 }: Readonly<{ 22 35 followUri?: string; 36 + followersCount?: number; 37 + followingCount?: number; 38 + userProfiles: SocialNetwork[]; 39 + actorProfiles: SocialNetwork[]; 23 40 loggedInUserDid?: string; 24 - timelineItems: TimelineItem[]; 25 41 profile: Un$Typed<ProfileView>; 26 - selectedTab?: string; 42 + selectedTab?: ProfileTabs; 27 43 galleries?: GalleryView[]; 44 + galleryFavs?: GalleryView[]; 28 45 }>) { 29 46 const isCreator = loggedInUserDid === profile.did; 30 47 const displayName = profile.displayName || profile.handle; 48 + const cameras = Array.from( 49 + new Set(galleries?.flatMap(getGalleryCameras) ?? []), 50 + ); 31 51 return ( 32 52 <div class="px-4 mb-4" id="profile-page"> 33 53 <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between my-4"> ··· 35 55 <AvatarButton profile={profile} /> 36 56 <p class="text-2xl font-bold">{displayName}</p> 37 57 <p class="text-zinc-600 dark:text-zinc-500">@{profile.handle}</p> 58 + <p class="space-x-1"> 59 + <a href={followersLink(profile.handle)}> 60 + <span class="font-semibold" id="followers-count"> 61 + {followersCount ?? 0} 62 + </span>{" "} 63 + <span class="text-zinc-600 dark:text-zinc-500">followers</span> 64 + </a>{" "} 65 + <a href={followingLink(profile.handle)}> 66 + <span class="font-semibold" id="following-count"> 67 + {followingCount ?? 0} 68 + </span>{" "} 69 + <span class="text-zinc-600 dark:text-zinc-500">following</span> 70 + </a>{" "} 71 + <span class="font-semibold">{galleries?.length ?? 0}</span> 72 + <span class="text-zinc-600 dark:text-zinc-500">galleries</span> 73 + </p> 74 + <CameraBadges cameras={cameras} class="mt-2" /> 38 75 {profile.description 39 76 ? <p class="mt-2 sm:max-w-[500px]">{profile.description}</p> 40 77 : null} 41 78 <p> 42 - <a 43 - href={bskyProfileLink(profile.handle)} 44 - class="text-xs hover:underline" 45 - > 46 - <i class="fa-brands fa-bluesky text-sky-500" /> @{profile.handle} 47 - </a> 79 + {userProfiles.includes("bluesky") && ( 80 + <a 81 + href={bskyProfileLink(profile.handle)} 82 + class="text-xs hover:underline" 83 + > 84 + <i class="fa-brands fa-bluesky text-sky-500" />{" "} 85 + @{profile.handle} 86 + </a> 87 + )} 48 88 </p> 49 89 </div> 50 90 {!isCreator && loggedInUserDid 51 91 ? ( 52 92 <div class="flex self-start gap-2 w-full sm:w-fit flex-col sm:flex-row"> 53 - <FollowButton followeeDid={profile.did} followUri={followUri} /> 93 + <FollowButton 94 + followeeDid={profile.did} 95 + followUri={followUri} 96 + /> 54 97 </div> 55 98 ) 56 99 : null} ··· 91 134 ) 92 135 : null} 93 136 </div> 94 - <div class="my-4 space-x-2 w-full flex sm:w-fit" role="tablist"> 137 + <div 138 + class="my-4 w-full flex sm:w-fit space-x-2 overflow-x-auto" 139 + role="tablist" 140 + style={{ WebkitOverflowScrolling: "touch" }} 141 + > 95 142 <button 96 143 type="button" 144 + name="tab" 145 + value="galleries" 97 146 hx-get={profileLink(profile.handle)} 98 - hx-target="body" 147 + hx-target="#profile-page" 99 148 hx-swap="outerHTML" 100 149 class={cn( 101 - "flex-1 py-2 px-4 cursor-pointer font-semibold", 102 - !selectedTab && "bg-zinc-100 dark:bg-zinc-800 font-semibold", 150 + "flex-1 min-w-[120px] py-2 px-4 cursor-pointer font-semibold", 151 + selectedTab === "galleries" && "bg-zinc-100 dark:bg-zinc-800", 103 152 )} 104 153 role="tab" 105 - aria-selected="true" 154 + aria-selected={selectedTab === "galleries"} 106 155 aria-controls="tab-content" 107 156 > 108 - Activity 157 + Galleries 109 158 </button> 110 - <button 159 + {isCreator && ( 160 + <button 161 + type="button" 162 + name="tab" 163 + value="favs" 164 + hx-get={profileLink(profile.handle)} 165 + hx-target="#profile-page" 166 + hx-swap="outerHTML" 167 + class={cn( 168 + "flex-1 min-w-[120px] py-2 px-4 cursor-pointer font-semibold", 169 + selectedTab === "favs" && "bg-zinc-100 dark:bg-zinc-800", 170 + )} 171 + role="tab" 172 + aria-selected={selectedTab === "favs"} 173 + aria-controls="tab-content" 174 + > 175 + Favs 176 + </button> 177 + )} 178 + { 179 + /* <button 111 180 type="button" 112 - hx-get={profileLink(profile.handle) + "?tab=galleries"} 113 - hx-target="#profile-page" 181 + hx-get={profileLink(profile.handle)} 182 + hx-target="body" 114 183 hx-swap="outerHTML" 115 184 class={cn( 116 - "flex-1 py-2 px-4 cursor-pointer font-semibold", 117 - selectedTab === "galleries" && "bg-zinc-100 dark:bg-zinc-800", 185 + "flex-1 min-w-[120px] py-2 px-4 cursor-pointer font-semibold", 186 + !selectedTab && "bg-zinc-100 dark:bg-zinc-800 font-semibold", 118 187 )} 119 188 role="tab" 120 - aria-selected="false" 189 + aria-selected={!selectedTab} 121 190 aria-controls="tab-content" 191 + hx-push-url="true" 122 192 > 123 - Galleries 124 - </button> 193 + Activity 194 + </button> */ 195 + } 125 196 </div> 126 197 <div id="tab-content" role="tabpanel"> 127 - {!selectedTab 198 + {selectedTab === "galleries" 128 199 ? ( 129 - <ul class="space-y-4 relative divide-zinc-200 dark:divide-zinc-800 divide-y w-fit"> 130 - {timelineItems.length 200 + <div class="grid grid-cols-1 sm:grid-cols-3 gap-2 mb-4"> 201 + {galleries?.length 131 202 ? ( 132 - timelineItems.map((item) => ( 133 - <Item item={item} key={item.itemUri} /> 203 + galleries.map((gallery) => ( 204 + <a 205 + href={galleryLink( 206 + gallery.creator.handle, 207 + new AtUri(gallery.uri).rkey, 208 + )} 209 + class="cursor-pointer relative aspect-square" 210 + > 211 + {gallery.items?.length 212 + ? ( 213 + <img 214 + src={gallery.items?.filter(isPhotoView)?.[0] 215 + ?.fullsize} 216 + alt={gallery.items?.filter(isPhotoView)?.[0]?.alt} 217 + class="w-full h-full object-cover" 218 + /> 219 + ) 220 + : ( 221 + <div class="w-full h-full bg-zinc-200 dark:bg-zinc-900" /> 222 + )} 223 + <div class="absolute bottom-0 left-0 bg-black/80 text-white p-2"> 224 + {(gallery.record as Gallery).title} 225 + </div> 226 + </a> 134 227 )) 135 228 ) 136 - : <li>No activity yet.</li>} 137 - </ul> 229 + : <p>No galleries yet.</p>} 230 + </div> 138 231 ) 139 232 : null} 140 - {selectedTab === "galleries" 233 + {selectedTab === "favs" 141 234 ? ( 142 235 <div class="grid grid-cols-1 sm:grid-cols-3 gap-2 mb-4"> 143 - {galleries?.length 236 + {galleryFavs?.length 144 237 ? ( 145 - galleries.map((gallery) => ( 238 + galleryFavs.map((gallery) => ( 146 239 <a 147 240 href={galleryLink( 148 241 gallery.creator.handle, ··· 162 255 : ( 163 256 <div class="w-full h-full bg-zinc-200 dark:bg-zinc-900" /> 164 257 )} 165 - <div class="absolute bottom-0 left-0 bg-black/80 text-white p-2"> 258 + <div class="absolute bottom-0 left-0 bg-black/80 text-white p-2 flex items-center gap-2"> 259 + <ActorAvatar profile={gallery.creator} size={20} />{" "} 166 260 {(gallery.record as Gallery).title} 167 261 </div> 168 262 </a> 169 263 )) 170 264 ) 171 - : <p>No galleries yet.</p>} 265 + : <p>No favs yet.</p>} 172 266 </div> 173 267 ) 174 268 : null} 269 + { 270 + /* {!selectedTab 271 + ? ( 272 + <ul class="space-y-4 relative divide-zinc-200 dark:divide-zinc-800 divide-y w-fit"> 273 + {timelineItems.length 274 + ? ( 275 + timelineItems.map((item) => ( 276 + <Item item={item} key={item.itemUri} /> 277 + )) 278 + ) 279 + : <li>No activity yet.</li>} 280 + </ul> 281 + ) 282 + : null} */ 283 + } 175 284 </div> 176 285 </div> 177 286 );
+61 -8
src/components/Timeline.tsx
··· 4 4 import { TimelineItem as Item } from "./TimelineItem.tsx"; 5 5 6 6 export function Timeline( 7 - { isLoggedIn, selectedTab, items }: Readonly< 8 - { isLoggedIn: boolean; selectedTab: string; items: TimelineItem[] } 7 + { isLoggedIn, selectedTab, items, actorProfiles, selectedGraph }: Readonly< 8 + { 9 + isLoggedIn: boolean; 10 + selectedTab: string; 11 + items: TimelineItem[]; 12 + actorProfiles: string[]; 13 + selectedGraph: string; 14 + } 9 15 >, 10 16 ) { 11 17 return ( ··· 17 23 <div class="flex sm:w-fit"> 18 24 <button 19 25 type="button" 20 - hx-get="/" 21 - hx-target="body" 26 + hx-get={`/?graph=${selectedGraph}`} 27 + hx-target="#timeline-page" 22 28 hx-swap="outerHTML" 23 29 class={cn( 24 - "flex-1 py-2 px-4 cursor-pointer font-semibold", 30 + "flex-1 py-2 sm:min-w-[120px] px-4 cursor-pointer font-semibold", 25 31 !selectedTab && 26 32 "bg-zinc-100 dark:bg-zinc-800 font-semibold", 27 33 )} ··· 33 39 </button> 34 40 <button 35 41 type="button" 36 - hx-get="/?tab=following" 42 + hx-get={`/?tab=following&graph=${selectedGraph}`} 37 43 hx-target="#timeline-page" 38 44 hx-swap="outerHTML" 39 45 class={cn( 40 - "flex-1 py-2 px-4 cursor-pointer font-semibold", 46 + "flex-1 py-2 sm:min-w-[120px] px-4 cursor-pointer font-semibold", 41 47 selectedTab === "following" && 42 48 "bg-zinc-100 dark:bg-zinc-800 font-semibold", 43 49 )} ··· 51 57 </div> 52 58 </div> 53 59 <div id="tab-content" role="tabpanel"> 60 + {actorProfiles.length > 1 && selectedTab === "following" 61 + ? ( 62 + <form 63 + hx-get="/" 64 + hx-target="#timeline-page" 65 + hx-swap="outerHTML" 66 + hx-trigger="change from:#graph-filter" 67 + class="mb-4 flex flex-col border-b border-zinc-200 dark:border-zinc-800 pb-4" 68 + > 69 + <label 70 + htmlFor="graph-filter" 71 + class="mb-1 font-medium sr-only" 72 + > 73 + Filter by AT Protocol Social Network 74 + </label> 75 + 76 + <input type="hidden" name="tab" value={selectedTab || ""} /> 77 + 78 + <select 79 + id="graph-filter" 80 + name="graph" 81 + class="border rounded px-2 py-1 dark:bg-zinc-900 dark:border-zinc-700 max-w-md" 82 + > 83 + {actorProfiles.map((graph) => ( 84 + <option 85 + value={graph} 86 + key={graph} 87 + selected={graph === selectedGraph} 88 + > 89 + {formatGraphName(graph)} 90 + </option> 91 + ))} 92 + </select> 93 + </form> 94 + ) 95 + : null} 54 96 <ul class="space-y-4 relative divide-zinc-200 dark:divide-zinc-800 divide-y w-fit"> 55 - {items.map((item) => <Item item={item} key={item.itemUri} />)} 97 + {items.length > 0 98 + ? items.map((item) => <Item item={item} key={item.itemUri} />) 99 + : ( 100 + <li class="text-center"> 101 + No galleries by people you follow on{" "} 102 + {formatGraphName(selectedGraph)} yet. 103 + </li> 104 + )} 56 105 </ul> 57 106 </div> 58 107 </> ··· 70 119 </div> 71 120 ); 72 121 } 122 + 123 + export function formatGraphName(graph: string): string { 124 + return graph.charAt(0).toUpperCase() + graph.slice(1); 125 + }
+38 -29
src/components/UploadPage.tsx
··· 1 1 import { PhotoView } from "$lexicon/types/social/grain/photo/defs.ts"; 2 2 import { Button } from "@bigmoves/bff/components"; 3 3 import { profileLink } from "../utils.ts"; 4 + import { Breadcrumb } from "./Breadcrumb.tsx"; 4 5 import { PhotoPreview } from "./PhotoPreview.tsx"; 5 6 6 7 export function UploadPage({ ··· 10 11 }: Readonly<{ handle: string; photos: PhotoView[]; returnTo?: string }>) { 11 12 return ( 12 13 <div class="flex flex-col px-4 pt-4 mb-4 space-y-4"> 13 - <div class="flex"> 14 - <div class="flex-1"> 15 - {returnTo 16 - ? ( 17 - <a href={returnTo} class="hover:underline"> 18 - <i class="fa-solid fa-arrow-left mr-2" /> 19 - Back to gallery 20 - </a> 21 - ) 22 - : ( 23 - <a href={profileLink(handle)} class="hover:underline"> 24 - <i class="fa-solid fa-arrow-left mr-2" /> 25 - Back to profile 26 - </a> 27 - )} 28 - </div> 29 - </div> 30 - <Button variant="primary" class="mb-4 w-full sm:w-fit" asChild> 31 - <label> 32 - <i class="fa fa-plus"></i> Add photos 33 - <input 34 - class="hidden" 35 - type="file" 36 - multiple 37 - accept="image/*" 38 - _="on change call Grain.uploadPhotos(me)" 39 - /> 40 - </label> 41 - </Button> 14 + <Breadcrumb 15 + items={[ 16 + returnTo 17 + ? { label: "Gallery", href: returnTo } 18 + : { label: "Profile", href: profileLink(handle) }, 19 + { label: "Upload" }, 20 + ]} 21 + /> 42 22 <div> 43 23 Upload 10 photos at a time. Click{" "} 44 24 <button ··· 52 32 </button>{" "} 53 33 to create a gallery or add to existing galleries once you're done! 54 34 </div> 35 + <form 36 + hx-encoding="multipart/form-data" 37 + _="on change from #file-input call Grain.uploadPage.uploadPhotos(me)" 38 + > 39 + <Button variant="primary" class="mb-4 w-full sm:w-fit" asChild> 40 + <label> 41 + <i class="fa fa-plus"></i> Add photos 42 + <input 43 + id="file-input" 44 + class="hidden" 45 + type="file" 46 + name="files" 47 + multiple 48 + accept="image/*" 49 + /> 50 + </label> 51 + </Button> 52 + 53 + <label class="block gap-2"> 54 + <input 55 + id="parse-exif" 56 + type="checkbox" 57 + name="parseExif" 58 + class="mr-2 accent-sky-600" 59 + checked 60 + /> 61 + Include image metadata (EXIF) 62 + </label> 63 + </form> 55 64 <div 56 65 id="image-preview" 57 66 class="w-full h-full grid grid-cols-2 sm:grid-cols-5 gap-2"
+305
src/legal.tsx
··· 1 + import { ComponentChildren } from "preact"; 2 + import { Breadcrumb } from "./components/Breadcrumb.tsx"; 3 + 4 + type SectionProps = { 5 + title: string; 6 + children: ComponentChildren; 7 + }; 8 + 9 + const Section = ({ title, children }: SectionProps) => ( 10 + <section className="mb-8"> 11 + <h2 className="text-xl font-bold mb-2 text-zinc-800 dark:text-zinc-100"> 12 + {title} 13 + </h2> 14 + <div className="space-y-2 text-zinc-700 dark:text-zinc-300 text-sm"> 15 + {children} 16 + </div> 17 + </section> 18 + ); 19 + 20 + export function Terms() { 21 + return ( 22 + <div className="px-4 py-4"> 23 + <Breadcrumb 24 + items={[{ label: "support", href: "/support" }, { label: "terms" }]} 25 + /> 26 + <h1 className="text-3xl font-bold mb-6 text-zinc-900 dark:text-white"> 27 + Terms and Conditions 28 + </h1> 29 + <div className="mb-6 text-sm text-zinc-900 dark:text-white"> 30 + Last Updated: June 3, 2025 31 + </div> 32 + <Section title="Overview"> 33 + <p> 34 + Grain is a photo sharing app built on the{" "} 35 + <a 36 + href="https://atproto.com/" 37 + className="text-sky-500 hover:underline" 38 + target="_blank" 39 + rel="noopener noreferrer" 40 + /> 41 + AT Protocol . All data, including photos, galleries, favorites, and 42 + metadata, is public and stored on the AT Protocol network. Users can 43 + upload photos, create and favorite galleries, and view non-location 44 + EXIF metadata. 45 + </p> 46 + <p> 47 + Grain is an open source project. These Terms apply to your use of the 48 + hosted version at{" "} 49 + <code>grain.social</code>, not to self-hosted instances or forks of 50 + the source code. 51 + </p> 52 + </Section> 53 + 54 + <Section title="Account and Data Ownership"> 55 + <p> 56 + Grain uses the AT Protocol, so users retain full control over their 57 + data. We are an independent project and not affiliated with Bluesky or 58 + the AT Protocol. 59 + </p> 60 + <p> 61 + If you use a <code>grain.social</code>{" "} 62 + handle, your data may be stored on our own self-hosted{" "} 63 + <a 64 + href="https://atproto.com/guides/glossary#pds-personal-data-server" 65 + className="text-sky-500 hover:underline" 66 + target="_blank" 67 + rel="noopener noreferrer" 68 + > 69 + PDS (Personal Data Server) 70 + </a>{" "} 71 + in accordance with protocol standards. 72 + </p> 73 + </Section> 74 + 75 + <Section title="Content"> 76 + <p> 77 + You are responsible for any content you share. Do not upload content 78 + you do not have rights to. All uploads are publicly visible and cannot 79 + currently be set as private. 80 + </p> 81 + </Section> 82 + 83 + <Section title="Analytics"> 84 + <p> 85 + We use{" "} 86 + <a 87 + href="https://www.goatcounter.com/" 88 + className="text-sky-500 hover:underline" 89 + > 90 + Goatcounter 91 + </a>{" "} 92 + for basic analytics. No personal data is collected, tracked, or sold. 93 + </p> 94 + </Section> 95 + 96 + <Section title="Prohibited Conduct"> 97 + <p> 98 + Do not upload illegal content, harass users, impersonate others, or 99 + attempt to disrupt the network. 100 + </p> 101 + </Section> 102 + 103 + <Section title="Disclaimers"> 104 + <p> 105 + Grain is provided "as is." We do not guarantee uptime, data retention, 106 + or uninterrupted access. 107 + </p> 108 + </Section> 109 + 110 + <Section title="Termination"> 111 + <p> 112 + We reserve the right to suspend or terminate your access to Grain at 113 + any time, without prior notice, for conduct that we believe violates 114 + these Terms, our community standards, or is harmful to other users or 115 + the AT Protocol network. Terminated accounts may lose access to 116 + uploaded content unless retained through the protocol’s data 117 + persistence mechanisms. 118 + </p> 119 + </Section> 120 + 121 + <Section title="Changes"> 122 + <p> 123 + We may update these terms periodically. Continued use means acceptance 124 + of any changes. 125 + </p> 126 + </Section> 127 + 128 + <Section title="Contact"> 129 + <p> 130 + For any questions about these Terms, your account, or issues with the 131 + app, you can contact us at{" "} 132 + <a 133 + href="mailto:support@grain.social" 134 + className="text-sky-500 hover:underline" 135 + > 136 + support@grain.social 137 + </a>. 138 + </p> 139 + </Section> 140 + </div> 141 + ); 142 + } 143 + 144 + export function PrivacyPolicy() { 145 + return ( 146 + <div className="px-4 py-4"> 147 + <Breadcrumb 148 + items={[{ label: "support", href: "/support" }, { label: "privacy" }]} 149 + /> 150 + <h1 className="text-3xl font-bold mb-6 text-zinc-900 dark:text-white"> 151 + Privacy Policy 152 + </h1> 153 + <div className="mb-6 text-sm text-zinc-900 dark:text-white"> 154 + Last Updated: June 3, 2025 155 + </div> 156 + <Section title="Data Storage and Access"> 157 + <p> 158 + Your data is stored on the AT Protocol. If you use a{" "} 159 + <code>grain.social</code>{" "} 160 + handle, it may be stored on our PDS. We do not store or access data 161 + beyond the protocol’s standard behavior. 162 + </p> 163 + </Section> 164 + 165 + <Section title="Public Data"> 166 + <p> 167 + All content on Grain is public. Private uploads are not currently 168 + supported. 169 + </p> 170 + </Section> 171 + 172 + { 173 + /* Coming soon */ 174 + /* <Section title="EXIF Metadata"> 175 + <p> 176 + We optionally collect and display EXIF metadata (excluding location) 177 + from your photos. At upload time, you can choose whether to allow this 178 + metadata to be collected. The metadata is stored according to standard 179 + AT Protocol storage mechanisms and is not retained outside the 180 + protocol or used for other purposes. 181 + </p> 182 + <p> 183 + You can learn more about the types of metadata commonly embedded in 184 + photos at{" "} 185 + <a 186 + href="https://exiv2.org/tags.html" 187 + className="text-sky-500 hover:underline" 188 + target="_blank" 189 + rel="noopener noreferrer" 190 + > 191 + exiv2.org 192 + </a> 193 + . 194 + </p> 195 + </Section> */ 196 + } 197 + 198 + <Section title="Analytics"> 199 + <p> 200 + We use{" "} 201 + <a 202 + href="https://www.goatcounter.com/" 203 + className="text-sky-500 hover:underline" 204 + > 205 + Goatcounter 206 + </a>{" "} 207 + for analytics. It is privacy-focused: no IP addresses, cookies, or 208 + personal data is collected. 209 + </p> 210 + </Section> 211 + 212 + <Section title="No Ads or Tracking"> 213 + <p>We do not serve ads, use third-party tracking, or sell user data.</p> 214 + </Section> 215 + 216 + <Section title="Children’s Privacy"> 217 + <p>Grain is not intended for users under 13 years of age.</p> 218 + </Section> 219 + 220 + <Section title="Changes to Policy"> 221 + <p> 222 + This policy may be updated. Material changes will be communicated via 223 + the app or site. 224 + </p> 225 + </Section> 226 + 227 + <Section title="Contact"> 228 + <p> 229 + For privacy questions, contact us at{" "} 230 + <a 231 + href="mailto:support@grain.social" 232 + className="text-sky-500 hover:underline" 233 + > 234 + support@grain.social 235 + </a>. 236 + </p> 237 + </Section> 238 + </div> 239 + ); 240 + } 241 + 242 + export function CopyrightPolicy() { 243 + return ( 244 + <div className="px-4 py-4"> 245 + <Breadcrumb 246 + items={[{ label: "support", href: "/support" }, { label: "copyright" }]} 247 + /> 248 + <h1 className="text-3xl font-bold mb-6 text-zinc-900 dark:text-white"> 249 + Copyright Policy 250 + </h1> 251 + <div className="mb-6 text-sm text-zinc-900 dark:text-white"> 252 + Last Updated: June 3, 2025 253 + </div> 254 + <Section title="Copyright Infringement"> 255 + <p> 256 + Grain respects the intellectual property rights of others and expects 257 + users to do the same. If you believe your copyrighted work has been 258 + used in a way that constitutes infringement, please notify us 259 + promptly. 260 + </p> 261 + </Section> 262 + 263 + <Section title="Notice Requirements"> 264 + <p> 265 + Your infringement notice must include: (1) a description of the 266 + copyrighted work, (2) the location of the infringing material, (3) 267 + your contact information, (4) a statement that you believe in good 268 + faith the use is not authorized, and (5) a statement, under penalty of 269 + perjury, that the information is accurate. 270 + </p> 271 + </Section> 272 + 273 + <Section title="DMCA Compliance"> 274 + <p> 275 + Grain complies with the Digital Millennium Copyright Act (DMCA). If 276 + you are a copyright holder and believe your rights have been violated, 277 + you may file a DMCA notice with the required information to our 278 + designated agent. We will promptly respond to all valid DMCA notices 279 + and take appropriate action, including removal of the infringing 280 + content and disabling access. 281 + </p> 282 + </Section> 283 + 284 + <Section title="Repeat Infringers"> 285 + <p> 286 + Accounts that repeatedly infringe copyright may be suspended or 287 + removed in accordance with AT Protocol and Grain Social’s moderation 288 + guidelines. 289 + </p> 290 + </Section> 291 + 292 + <Section title="Contact"> 293 + <p> 294 + To report infringement or submit a DMCA notice, contact us at{" "} 295 + <a 296 + href="mailto:support@grain.social" 297 + className="text-sky-500 hover:underline" 298 + > 299 + support@grain.social 300 + </a>. 301 + </p> 302 + </Section> 303 + </div> 304 + ); 305 + }
+149 -3
src/lib/actor.ts
··· 1 + import { Record as BskyProfile } from "$lexicon/types/app/bsky/actor/profile.ts"; 2 + import { Record as TangledProfile } from "$lexicon/types/sh/tangled/actor/profile.ts"; 1 3 import { ProfileView } from "$lexicon/types/social/grain/actor/defs.ts"; 2 - import { Record as Profile } from "$lexicon/types/social/grain/actor/profile.ts"; 4 + import { Record as GrainProfile } from "$lexicon/types/social/grain/actor/profile.ts"; 5 + import { Record as Favorite } from "$lexicon/types/social/grain/favorite.ts"; 3 6 import { Record as Gallery } from "$lexicon/types/social/grain/gallery.ts"; 4 7 import { Record as Photo } from "$lexicon/types/social/grain/photo.ts"; 5 8 import { Record as PhotoExif } from "$lexicon/types/social/grain/photo/exif.ts"; ··· 7 10 import { BffContext, WithBffMeta } from "@bigmoves/bff"; 8 11 import { galleryToView, getGalleryItemsAndPhotos } from "./gallery.ts"; 9 12 import { photoToView } from "./photo.ts"; 13 + import type { SocialNetwork } from "./timeline.ts"; 10 14 11 15 export function getActorProfile(did: string, ctx: BffContext) { 12 16 const actor = ctx.indexService.getActor(did); 13 17 if (!actor) return null; 14 - const profileRecord = ctx.indexService.getRecord<WithBffMeta<Profile>>( 18 + const profileRecord = ctx.indexService.getRecord<WithBffMeta<GrainProfile>>( 15 19 `at://${did}/social.grain.actor.profile/self`, 16 20 ); 17 21 return profileRecord ? profileToView(profileRecord, actor.handle) : null; 18 22 } 19 23 20 24 export function profileToView( 21 - record: WithBffMeta<Profile>, 25 + record: WithBffMeta<GrainProfile>, 22 26 handle: string, 23 27 ): Un$Typed<ProfileView> { 24 28 return { ··· 34 38 35 39 export function getActorPhotos(handleOrDid: string, ctx: BffContext) { 36 40 let did: string; 41 + 37 42 if (handleOrDid.includes("did:")) { 38 43 did = handleOrDid; 39 44 } else { ··· 41 46 if (!actor) return []; 42 47 did = actor.did; 43 48 } 49 + 44 50 const photos = ctx.indexService.getRecords<WithBffMeta<Photo>>( 45 51 "social.grain.photo", 46 52 { ··· 66 72 67 73 export function getActorGalleries(handleOrDid: string, ctx: BffContext) { 68 74 let did: string; 75 + 69 76 if (handleOrDid.includes("did:")) { 70 77 did = handleOrDid; 71 78 } else { ··· 73 80 if (!actor) return []; 74 81 did = actor.did; 75 82 } 83 + 76 84 const { items: galleries } = ctx.indexService.getRecords< 77 85 WithBffMeta<Gallery> 78 86 >("social.grain.gallery", { 79 87 where: [{ field: "did", equals: did }], 80 88 orderBy: [{ field: "createdAt", direction: "desc" }], 81 89 }); 90 + 82 91 const galleryPhotosMap = getGalleryItemsAndPhotos(ctx, galleries); 83 92 const creator = getActorProfile(did, ctx); 93 + 84 94 if (!creator) return []; 95 + 85 96 return galleries.map((gallery) => 86 97 galleryToView(gallery, creator, galleryPhotosMap.get(gallery.uri) ?? []) 87 98 ); 88 99 } 100 + 101 + export function getActorGalleryFavs(handleOrDid: string, ctx: BffContext) { 102 + let did: string; 103 + 104 + if (handleOrDid.includes("did:")) { 105 + did = handleOrDid; 106 + } else { 107 + const actor = ctx.indexService.getActorByHandle(handleOrDid); 108 + if (!actor) return []; 109 + did = actor.did; 110 + } 111 + 112 + const { items: favRecords } = ctx.indexService.getRecords< 113 + WithBffMeta<Favorite> 114 + >( 115 + "social.grain.favorite", 116 + { 117 + where: [{ field: "did", equals: did }], 118 + orderBy: [{ field: "createdAt", direction: "desc" }], 119 + }, 120 + ); 121 + 122 + if (!favRecords.length) return []; 123 + 124 + const galleryUris = favRecords.map((fav) => fav.subject); 125 + 126 + const { items: galleries } = ctx.indexService.getRecords< 127 + WithBffMeta<Gallery> 128 + >( 129 + "social.grain.gallery", 130 + { 131 + where: [{ field: "uri", in: galleryUris }], 132 + }, 133 + ); 134 + 135 + // Map gallery uri to gallery object for fast lookup 136 + const galleryMap = new Map(galleries.map((g) => [g.uri, g])); 137 + const galleryPhotosMap = getGalleryItemsAndPhotos(ctx, galleries); 138 + const creators = new Map<string, ReturnType<typeof getActorProfile>>(); 139 + const uniqueDids = Array.from( 140 + new Set(galleries.map((gallery) => gallery.did)), 141 + ); 142 + 143 + const { items: profiles } = ctx.indexService.getRecords< 144 + WithBffMeta<GrainProfile> 145 + >( 146 + "social.grain.actor.profile", 147 + { 148 + where: [{ field: "did", in: uniqueDids }], 149 + }, 150 + ); 151 + 152 + for (const profile of profiles) { 153 + const handle = ctx.indexService.getActor(profile.did)?.handle ?? ""; 154 + creators.set(profile.did, profileToView(profile, handle)); 155 + } 156 + 157 + // Order galleries by the order of favRecords (favorited at) 158 + return favRecords 159 + .map((fav) => { 160 + const gallery = galleryMap.get(fav.subject); 161 + if (!gallery) return null; 162 + const creator = creators.get(gallery.did); 163 + if (!creator) return null; 164 + return galleryToView( 165 + gallery, 166 + creator, 167 + galleryPhotosMap.get(gallery.uri) ?? [], 168 + ); 169 + }) 170 + .filter((g) => g !== null); 171 + } 172 + 173 + export function getActorProfiles( 174 + handleOrDid: string, 175 + ctx: BffContext, 176 + ): SocialNetwork[] { 177 + let did: string; 178 + 179 + if (handleOrDid.includes("did:")) { 180 + did = handleOrDid; 181 + } else { 182 + const actor = ctx.indexService.getActorByHandle(handleOrDid); 183 + if (!actor) return []; 184 + did = actor.did; 185 + } 186 + 187 + const { items: grainProfiles } = ctx.indexService.getRecords< 188 + WithBffMeta<GrainProfile> 189 + >( 190 + "social.grain.actor.profile", 191 + { 192 + where: { 193 + AND: [ 194 + { field: "did", equals: did }, 195 + { field: "uri", contains: "self" }, 196 + ], 197 + }, 198 + }, 199 + ); 200 + 201 + const { items: tangledProfiles } = ctx.indexService.getRecords< 202 + WithBffMeta<TangledProfile> 203 + >( 204 + "sh.tangled.actor.profile", 205 + { 206 + where: { 207 + AND: [ 208 + { field: "did", equals: did }, 209 + { field: "uri", contains: "self" }, 210 + ], 211 + }, 212 + }, 213 + ); 214 + 215 + const { items: bskyProfiles } = ctx.indexService.getRecords< 216 + WithBffMeta<BskyProfile> 217 + >( 218 + "app.bsky.actor.profile", 219 + { 220 + where: { 221 + AND: [ 222 + { field: "did", equals: did }, 223 + { field: "uri", contains: "self" }, 224 + ], 225 + }, 226 + }, 227 + ); 228 + 229 + const profiles: SocialNetwork[] = []; 230 + if (grainProfiles.length) profiles.push("grain"); 231 + if (bskyProfiles.length) profiles.push("bluesky"); 232 + if (tangledProfiles.length) profiles.push("tangled"); 233 + return profiles; 234 + }
+74 -9
src/lib/follow.ts
··· 1 - import { Record as BskyFollow } from "$lexicon/types/app/bsky/graph/follow.ts"; 1 + import { Record as GrainFollow } from "$lexicon/types/social/grain/graph/follow.ts"; 2 2 import { BffContext, WithBffMeta } from "@bigmoves/bff"; 3 + import { getActorProfile } from "./actor.ts"; 3 4 4 5 export function getFollow( 5 6 followeeDid: string, ··· 8 9 ) { 9 10 const { 10 11 items: [follow], 11 - } = ctx.indexService.getRecords<WithBffMeta<BskyFollow>>( 12 - "app.bsky.graph.follow", 12 + } = ctx.indexService.getRecords< 13 + WithBffMeta<GrainFollow> 14 + >( 15 + "social.grain.graph.follow", 13 16 { 14 - where: [ 15 - { 17 + where: { 18 + AND: [{ 16 19 field: "did", 17 20 equals: followerDid, 18 - }, 19 - { 21 + }, { 20 22 field: "subject", 21 23 equals: followeeDid, 22 - }, 23 - ], 24 + }], 25 + }, 24 26 }, 25 27 ); 28 + 26 29 return follow; 27 30 } 31 + 32 + export function getFollowers( 33 + followeeDid: string, 34 + ctx: BffContext, 35 + ): WithBffMeta<GrainFollow>[] { 36 + const { items: followers } = ctx.indexService.getRecords< 37 + WithBffMeta<GrainFollow> 38 + >( 39 + "social.grain.graph.follow", 40 + { 41 + orderBy: [{ field: "createdAt", direction: "desc" }], 42 + where: [{ 43 + field: "subject", 44 + equals: followeeDid, 45 + }], 46 + }, 47 + ); 48 + return followers; 49 + } 50 + 51 + export function getFollowing( 52 + followerDid: string, 53 + ctx: BffContext, 54 + ): WithBffMeta<GrainFollow>[] { 55 + const { items: following } = ctx.indexService.getRecords< 56 + WithBffMeta<GrainFollow> 57 + >( 58 + "social.grain.graph.follow", 59 + { 60 + orderBy: [{ field: "createdAt", direction: "desc" }], 61 + where: [{ 62 + field: "did", 63 + equals: followerDid, 64 + }], 65 + }, 66 + ); 67 + return following; 68 + } 69 + 70 + export function getFollowersWithProfiles( 71 + followeeDid: string, 72 + ctx: BffContext, 73 + ) { 74 + const followers = getFollowers(followeeDid, ctx); 75 + return followers 76 + .map((follow) => getActorProfile(follow.did, ctx)) 77 + .filter((profile): profile is NonNullable<typeof profile> => 78 + profile != null 79 + ); 80 + } 81 + 82 + export function getFollowingWithProfiles( 83 + followerDid: string, 84 + ctx: BffContext, 85 + ) { 86 + const following = getFollowing(followerDid, ctx); 87 + return following 88 + .map((follow) => getActorProfile(follow.subject, ctx)) 89 + .filter((profile): profile is NonNullable<typeof profile> => 90 + profile != null 91 + ); 92 + }
+44 -7
src/lib/gallery.ts
··· 11 11 isPhotoView, 12 12 PhotoView, 13 13 } from "$lexicon/types/social/grain/photo/defs.ts"; 14 + import { Record as PhotoExif } from "$lexicon/types/social/grain/photo/exif.ts"; 14 15 import { Un$Typed } from "$lexicon/util.ts"; 15 16 import { AtUri } from "@atproto/syntax"; 16 17 import { BffContext, WithBffMeta } from "@bigmoves/bff"; 17 18 import { getActorProfile } from "./actor.ts"; 18 19 import { photoToView } from "./photo.ts"; 19 20 21 + type PhotoWithExif = WithBffMeta<Photo> & { 22 + exif?: WithBffMeta<PhotoExif>; 23 + }; 24 + 20 25 export function getGalleryItemsAndPhotos( 21 26 ctx: BffContext, 22 27 galleries: WithBffMeta<Gallery>[], 23 - ): Map<string, WithBffMeta<Photo>[]> { 28 + ): Map<string, PhotoWithExif[]> { 24 29 const galleryUris = galleries.map( 25 30 (gallery) => 26 31 `at://${gallery.did}/social.grain.gallery/${new AtUri(gallery.uri).rkey}`, ··· 45 50 }, 46 51 ); 47 52 48 - const photosMap = new Map<string, WithBffMeta<Photo>>(); 53 + const { items: photosExif } = ctx.indexService.getRecords< 54 + WithBffMeta<PhotoExif> 55 + >( 56 + "social.grain.photo.exif", 57 + { 58 + where: [{ field: "photo", in: photoUris }], 59 + }, 60 + ); 61 + 62 + const photosMap = new Map<string, PhotoWithExif>(); 63 + const exifMap = new Map<string, WithBffMeta<PhotoExif>>(); 64 + for (const exif of photosExif) { 65 + exifMap.set(exif.photo, exif); 66 + } 49 67 for (const photo of photos) { 50 - photosMap.set(photo.uri, photo); 68 + const exif = exifMap.get(photo.uri); 69 + photosMap.set(photo.uri, exif ? { ...photo, exif } : photo); 51 70 } 52 71 53 - const galleryPhotosMap = new Map<string, WithBffMeta<Photo>[]>(); 72 + const galleryPhotosMap = new Map<string, PhotoWithExif[]>(); 54 73 for (const item of galleryItems) { 55 74 const galleryUri = item.gallery; 56 75 const photo = photosMap.get(item.item); ··· 130 149 export function galleryToView( 131 150 record: WithBffMeta<Gallery>, 132 151 creator: Un$Typed<ProfileView>, 133 - items: Photo[], 152 + items: PhotoWithExif[], 134 153 ): Un$Typed<GalleryView> { 135 154 return { 136 155 uri: record.uri, ··· 147 166 function itemToView( 148 167 did: string, 149 168 item: 150 - | WithBffMeta<Photo> 169 + | PhotoWithExif 151 170 | { 152 171 $type: string; 153 172 }, 154 173 ): Un$Typed<PhotoView> | undefined { 155 174 if (isPhoto(item)) { 156 - return photoToView(did, item); 175 + return photoToView(did, item, item.exif); 157 176 } 158 177 return undefined; 159 178 } 179 + 180 + export function getGalleryCameras( 181 + gallery: GalleryView, 182 + ): string[] { 183 + const photos = gallery.items?.filter(isPhotoView) ?? []; 184 + const cameras = new Set<string>(); 185 + for (const photo of photos) { 186 + if (photo.exif?.make) { 187 + // Capitalize first letter of each word for make only, leave model raw 188 + const make = photo.exif.make.charAt(0).toUpperCase() + 189 + photo.exif.make.slice(1).toLowerCase(); 190 + const model = photo.exif.model ?? ""; 191 + console.log(make, model); 192 + cameras.add(`${make} ${model}`.trim()); 193 + } 194 + } 195 + return Array.from(cameras); 196 + }
+27 -14
src/lib/notifications.ts
··· 1 1 import { ProfileView } from "$lexicon/types/social/grain/actor/defs.ts"; 2 2 import { Record as Favorite } from "$lexicon/types/social/grain/favorite.ts"; 3 + import { Record as Follow } from "$lexicon/types/social/grain/graph/follow.ts"; 3 4 import { NotificationView } from "$lexicon/types/social/grain/notification/defs.ts"; 4 5 import { Un$Typed } from "$lexicon/util.ts"; 5 6 import { ActorTable, BffContext, WithBffMeta } from "@bigmoves/bff"; 6 7 import { getActorProfile } from "./actor.ts"; 7 8 8 - export type NotificationRecords = WithBffMeta<Favorite>; 9 + export type NotificationRecords = WithBffMeta<Favorite | Follow>; 9 10 10 11 export function getNotifications( 11 12 currentUser: ActorTable, ··· 13 14 ) { 14 15 const { lastSeenNotifs } = currentUser; 15 16 const notifications = ctx.getNotifications<NotificationRecords>(); 16 - return notifications.map((notification) => { 17 - const actor = ctx.indexService.getActor(notification.did); 18 - const authorProfile = getActorProfile(notification.did, ctx); 19 - if (!actor || !authorProfile) return null; 20 - return notificationToView( 21 - notification, 22 - authorProfile, 23 - lastSeenNotifs, 24 - ); 25 - }).filter((view): view is Un$Typed<NotificationView> => Boolean(view)); 17 + return notifications 18 + .filter( 19 + (notification) => 20 + notification.$type === "social.grain.favorite" || 21 + notification.$type === "social.grain.graph.follow", 22 + ) 23 + .map((notification) => { 24 + const actor = ctx.indexService.getActor(notification.did); 25 + const authorProfile = getActorProfile(notification.did, ctx); 26 + if (!actor || !authorProfile) return null; 27 + return notificationToView( 28 + notification, 29 + authorProfile, 30 + lastSeenNotifs, 31 + ); 32 + }) 33 + .filter((view): view is Un$Typed<NotificationView> => Boolean(view)); 26 34 } 27 35 28 36 export function notificationToView( ··· 30 38 author: Un$Typed<ProfileView>, 31 39 lastSeenNotifs: string | undefined, 32 40 ): Un$Typed<NotificationView> { 33 - const reason = record.$type === "social.grain.favorite" 34 - ? "gallery-favorite" 35 - : "unknown"; 41 + let reason: string; 42 + if (record.$type === "social.grain.favorite") { 43 + reason = "gallery-favorite"; 44 + } else if (record.$type === "social.grain.graph.follow") { 45 + reason = "follow"; 46 + } else { 47 + reason = "unknown"; 48 + } 36 49 const reasonSubject = record.$type === "social.grain.favorite" 37 50 ? record.subject 38 51 : undefined;
+88 -2
src/lib/photo.ts
··· 3 3 import { Record as PhotoExif } from "$lexicon/types/social/grain/photo/exif.ts"; 4 4 import { $Typed } from "$lexicon/util.ts"; 5 5 import { BffContext, WithBffMeta } from "@bigmoves/bff"; 6 + import { format, parseISO } from "date-fns"; 6 7 import { PUBLIC_URL, USE_CDN } from "../env.ts"; 7 8 8 9 export function getPhoto( ··· 64 65 const deserializedExif = deserializeExif(exif); 65 66 return { 66 67 ...deserializedExif, 68 + fNumber: deserializedExif.fNumber 69 + ? formatAperture(deserializedExif.fNumber) 70 + : undefined, 71 + dateTimeOriginal: deserializedExif.dateTimeOriginal 72 + ? format( 73 + parseISO(deserializedExif.dateTimeOriginal), 74 + "MMM d, yyyy, h:mm a", 75 + ) 76 + : undefined, 77 + focalLengthIn35mmFormat: deserializedExif.focalLengthIn35mmFormat 78 + ? `${deserializedExif.focalLengthIn35mmFormat}mm` 79 + : undefined, 80 + exposureTime: deserializedExif.exposureTime !== undefined 81 + ? formatExposureTime(deserializedExif.exposureTime) 82 + : undefined, 67 83 $type: "social.grain.photo.defs#exifView", 68 84 }; 69 85 } 70 86 71 - const EXIF_SCALE = 1000000; 87 + function formatAperture(fNumber: number): string { 88 + return `ƒ/${Number.isInteger(fNumber) ? fNumber : fNumber.toFixed(1)}`; 89 + } 90 + 91 + function formatExposureTime(seconds: number): string { 92 + if (seconds >= 1) { 93 + return `${seconds}s`; 94 + } 95 + 96 + const denominator = Math.round(1 / seconds); 97 + return `1/${denominator}`; 98 + } 99 + 100 + const SCALE_FACTOR = 1000000; 72 101 73 102 export function deserializeExif( 74 103 exif: WithBffMeta<PhotoExif>, 75 - scale: number = EXIF_SCALE, 104 + scale: number = SCALE_FACTOR, 76 105 ): WithBffMeta<PhotoExif> { 77 106 const deserialized: Partial<WithBffMeta<PhotoExif>> = { 78 107 $type: exif.$type, ··· 99 128 100 129 return deserialized as WithBffMeta<PhotoExif>; 101 130 } 131 + 132 + const exifDisplayNames: Record<string, string> = { 133 + Make: "Make", 134 + Model: "Model", 135 + LensMake: "Lens Make", 136 + LensModel: "Lens Model", 137 + FNumber: "Aperture", 138 + FocalLengthIn35mmFormat: "Focal Length", 139 + ExposureTime: "Exposure Time", 140 + ISO: "ISO", 141 + Flash: "Flash", 142 + DateTimeOriginal: "Date Taken", 143 + }; 144 + 145 + const tagOrder = [ 146 + "Make", 147 + "Model", 148 + "LensMake", 149 + "LensModel", 150 + "FNumber", 151 + "FocalLengthIn35mmFormat", 152 + "ExposureTime", 153 + "ISO", 154 + "Flash", 155 + "DateTimeOriginal", 156 + ]; 157 + 158 + export function getOrderedExifData(photo: PhotoView) { 159 + const exif = photo.exif || {}; 160 + const entries = Object.entries(exif) 161 + .filter(([key]) => 162 + tagOrder.some((tag) => tag.toLowerCase() === key.toLowerCase()) 163 + ) 164 + .map(([key, value]) => { 165 + const tagKey = tagOrder.find( 166 + (tag) => tag.toLowerCase() === key.toLowerCase(), 167 + ); 168 + const displayKey = tagKey && exifDisplayNames[tagKey] 169 + ? exifDisplayNames[tagKey] 170 + : key; 171 + return { key, displayKey, value }; 172 + }); 173 + 174 + // Sort according to tagOrder, unknown tags go last in original order 175 + return entries.sort((a, b) => { 176 + const aIdx = tagOrder.findIndex( 177 + (tag) => tag.toLowerCase() === a.key.toLowerCase(), 178 + ); 179 + const bIdx = tagOrder.findIndex( 180 + (tag) => tag.toLowerCase() === b.key.toLowerCase(), 181 + ); 182 + if (aIdx === -1 && bIdx === -1) return 0; 183 + if (aIdx === -1) return 1; 184 + if (bIdx === -1) return -1; 185 + return aIdx - bIdx; 186 + }); 187 + }
+31 -118
src/lib/timeline.ts
··· 1 1 import { Record as BskyFollow } from "$lexicon/types/app/bsky/graph/follow.ts"; 2 + import { Record as TangledFollow } from "$lexicon/types/sh/tangled/graph/follow.ts"; 2 3 import { ProfileView } from "$lexicon/types/social/grain/actor/defs.ts"; 3 - import { Record as Favorite } from "$lexicon/types/social/grain/favorite.ts"; 4 4 import { Record as Gallery } from "$lexicon/types/social/grain/gallery.ts"; 5 5 import { GalleryView } from "$lexicon/types/social/grain/gallery/defs.ts"; 6 + import { Record as GrainFollow } from "$lexicon/types/social/grain/graph/follow.ts"; 6 7 import { Un$Typed } from "$lexicon/util.ts"; 7 8 import { AtUri } from "@atproto/syntax"; 8 9 import { BffContext, QueryOptions, WithBffMeta } from "@bigmoves/bff"; 9 10 import { getActorProfile } from "./actor.ts"; 10 11 import { galleryToView, getGalleryItemsAndPhotos } from "./gallery.ts"; 11 12 12 - type TimelineItemType = "gallery" | "favorite"; 13 + export type TimelineItemType = "gallery"; 14 + 15 + export type SocialNetwork = "bluesky" | "grain" | "tangled"; 13 16 14 17 export type TimelineItem = { 15 18 createdAt: string; ··· 34 37 ? [{ field: "did", equals: options.actorDid }] 35 38 : undefined; 36 39 37 - if (options?.followingDids && options.followingDids.size > 0) { 38 - whereClause = [ 39 - ...(whereClause ?? []), 40 - { field: "did", in: Array.from(options.followingDids) }, 41 - ]; 40 + if (options?.followingDids) { 41 + if (options.followingDids.size > 0) { 42 + whereClause = [ 43 + ...(whereClause ?? []), 44 + { field: "did", in: Array.from(options.followingDids) }, 45 + ]; 46 + } else { 47 + return []; 48 + } 42 49 } 43 50 44 51 const { items: galleries } = ctx.indexService.getRecords< ··· 77 84 return items; 78 85 } 79 86 80 - function processFavs( 81 - ctx: BffContext, 82 - options?: TimelineOptions, 83 - ): TimelineItem[] { 84 - const items: TimelineItem[] = []; 85 - 86 - let whereClause: QueryOptions["where"] = options?.actorDid 87 - ? [{ field: "did", equals: options.actorDid }] 88 - : undefined; 89 - 90 - if (options?.followingDids && options.followingDids.size > 0) { 91 - whereClause = [ 92 - ...(whereClause ?? []), 93 - { field: "did", in: Array.from(options.followingDids) }, 94 - ]; 95 - } 96 - 97 - const { items: favs } = ctx.indexService.getRecords<WithBffMeta<Favorite>>( 98 - "social.grain.favorite", 99 - { 100 - orderBy: [{ field: "createdAt", direction: "desc" }], 101 - where: whereClause, 102 - }, 103 - ); 104 - 105 - if (favs.length === 0) return items; 106 - 107 - // Collect all gallery references from favorites 108 - const galleryRefs = new Map<string, WithBffMeta<Gallery>>(); 109 - 110 - for (const favorite of favs) { 111 - if (!favorite.subject) continue; 112 - 113 - try { 114 - const atUri = new AtUri(favorite.subject); 115 - const galleryDid = atUri.hostname; 116 - const galleryRkey = atUri.rkey; 117 - const galleryUri = 118 - `at://${galleryDid}/social.grain.gallery/${galleryRkey}`; 119 - 120 - const gallery = ctx.indexService.getRecord<WithBffMeta<Gallery>>( 121 - galleryUri, 122 - ); 123 - if (gallery) { 124 - galleryRefs.set(galleryUri, gallery); 125 - } 126 - } catch (e) { 127 - console.error("Error processing favorite:", e); 128 - } 129 - } 130 - 131 - const galleries = Array.from(galleryRefs.values()); 132 - const galleryPhotosMap = getGalleryItemsAndPhotos(ctx, galleries); 133 - 134 - for (const favorite of favs) { 135 - if (!favorite.subject) continue; 136 - 137 - try { 138 - const atUri = new AtUri(favorite.subject); 139 - const galleryDid = atUri.hostname; 140 - const galleryRkey = atUri.rkey; 141 - const galleryUri = 142 - `at://${galleryDid}/social.grain.gallery/${galleryRkey}`; 143 - 144 - const gallery = galleryRefs.get(galleryUri); 145 - if (!gallery) continue; 146 - 147 - const galleryActor = ctx.indexService.getActor(galleryDid); 148 - if (!galleryActor) continue; 149 - const galleryProfile = getActorProfile(galleryActor.did, ctx); 150 - if (!galleryProfile) continue; 151 - 152 - const favActor = ctx.indexService.getActor(favorite.did); 153 - if (!favActor) continue; 154 - const favProfile = getActorProfile(favActor.did, ctx); 155 - if (!favProfile) continue; 156 - 157 - const galleryPhotos = galleryPhotosMap.get(galleryUri) || []; 158 - const galleryView = galleryToView(gallery, galleryProfile, galleryPhotos); 159 - 160 - items.push({ 161 - itemType: "favorite", 162 - createdAt: favorite.createdAt, 163 - itemUri: favorite.uri, 164 - actor: favProfile, 165 - gallery: galleryView, 166 - }); 167 - } catch (e) { 168 - console.error("Error processing favorite:", e); 169 - continue; 170 - } 171 - } 172 - 173 - return items; 174 - } 175 - 176 87 function getTimelineItems( 177 88 ctx: BffContext, 178 89 options?: TimelineOptions, 179 90 ): TimelineItem[] { 180 91 const galleryItems = processGalleries(ctx, options); 181 - const favsItems = processFavs(ctx, options); 182 - const timelineItems = [...galleryItems, ...favsItems]; 183 - 184 - return timelineItems.sort( 92 + return galleryItems.sort( 185 93 (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), 186 94 ); 187 95 } 188 96 189 - function getFollowingDids(ctx: BffContext): Set<string> { 97 + function getFollowingDids(type: SocialNetwork, ctx: BffContext): Set<string> { 190 98 if (!ctx.currentUser?.did) return new Set(); 99 + const typeToCollection: Record<SocialNetwork, string> = { 100 + bluesky: "app.bsky.graph.follow", 101 + grain: "social.grain.graph.follow", 102 + tangled: "sh.tangled.graph.follow", 103 + }; 104 + const collection = typeToCollection[type]; 105 + if (!collection) { 106 + throw new Error(`Unsupported social graph type: ${type}`); 107 + } 191 108 const { items: follows } = ctx.indexService.getRecords< 192 - WithBffMeta<BskyFollow> 109 + WithBffMeta<BskyFollow | GrainFollow | TangledFollow> 193 110 >( 194 - "app.bsky.graph.follow", 111 + collection, 195 112 { where: [{ field: "did", equals: ctx.currentUser.did }] }, 196 113 ); 197 114 return new Set(follows.map((f) => f.subject).filter(Boolean)); ··· 199 116 200 117 export function getTimeline( 201 118 ctx: BffContext, 202 - type: "timeline" | "following" = "timeline", 119 + type: "timeline" | "following", 120 + graph: SocialNetwork, 203 121 ): TimelineItem[] { 204 122 let followingDids: Set<string> | undefined = undefined; 205 123 if (type === "following") { 206 - followingDids = getFollowingDids(ctx); 124 + followingDids = getFollowingDids(graph, ctx); 207 125 } 208 126 const galleryItems = processGalleries(ctx, { followingDids }); 209 - const favsItems = processFavs( 210 - ctx, 211 - followingDids ? { followingDids } : undefined, 212 - ); 213 - const timelineItems = [...galleryItems, ...favsItems]; 214 - return timelineItems.sort( 127 + return galleryItems.sort( 215 128 (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime(), 216 129 ); 217 130 }
+21 -3
src/main.tsx
··· 7 7 import * as actionHandlers from "./routes/actions.tsx"; 8 8 import * as dialogHandlers from "./routes/dialogs.tsx"; 9 9 import { handler as exploreHandler } from "./routes/explore.tsx"; 10 + import { handler as followersHandler } from "./routes/followers.tsx"; 11 + import { handler as followsHandler } from "./routes/follows.tsx"; 10 12 import { handler as galleryHandler } from "./routes/gallery.tsx"; 13 + import * as legalHandlers from "./routes/legal.tsx"; 11 14 import { handler as notificationsHandler } from "./routes/notifications.tsx"; 12 15 import { handler as onboardHandler } from "./routes/onboard.tsx"; 13 16 import { handler as profileHandler } from "./routes/profile.tsx"; 14 17 import { handler as recordHandler } from "./routes/record.ts"; 18 + import { handler as supportHandler } from "./routes/support.tsx"; 15 19 import { handler as timelineHandler } from "./routes/timeline.tsx"; 16 20 import { handler as uploadHandler } from "./routes/upload.tsx"; 17 21 import { appStateMiddleware, type State } from "./state.ts"; ··· 28 32 "social.grain.photo", 29 33 "social.grain.photo.exif", 30 34 "social.grain.favorite", 35 + "social.grain.graph.follow", 31 36 ], 32 37 externalCollections: [ 33 38 "app.bsky.actor.profile", 34 39 "app.bsky.graph.follow", 40 + "sh.tangled.actor.profile", 41 + "sh.tangled.graph.follow", 35 42 ], 36 43 jetstreamUrl: JETSTREAM.WEST_1, 37 44 lexicons, ··· 55 62 route("/explore", exploreHandler), 56 63 route("/notifications", notificationsHandler), 57 64 route("/profile/:handle", profileHandler), 65 + route("/profile/:handle/followers", followersHandler), 66 + route("/profile/:handle/follows", followsHandler), 58 67 route("/profile/:handle/gallery/:rkey", galleryHandler), 59 68 route("/upload", uploadHandler), 60 69 route("/onboard", onboardHandler), 70 + route("/support", supportHandler), 71 + route("/support/privacy", legalHandlers.privacyHandler), 72 + route("/support/terms", legalHandlers.termsHandler), 73 + route("/support/copyright", legalHandlers.copyrightHandler), 74 + route("/dialogs/create-account", dialogHandlers.createAccount), 61 75 route("/dialogs/gallery/new", dialogHandlers.createGallery), 62 76 route("/dialogs/gallery/:rkey", dialogHandlers.editGallery), 63 77 route("/dialogs/gallery/:rkey/sort", dialogHandlers.sortGallery), ··· 68 82 route( 69 83 "/dialogs/photo/:rkey/exif", 70 84 dialogHandlers.photoExif, 85 + ), 86 + route( 87 + "/dialogs/photo/:rkey/exif-overlay", 88 + dialogHandlers.photoExifOverlay, 71 89 ), 72 90 route( 73 91 "/dialogs/photo-select/:galleryRkey", 74 92 dialogHandlers.galleryPhotoSelect, 75 93 ), 76 94 route("/actions/update-seen", ["POST"], actionHandlers.updateSeen), 77 - route("/actions/follow/:did", ["POST"], actionHandlers.follow), 95 + route("/actions/follow/:followeeDid", ["POST"], actionHandlers.follow), 78 96 route( 79 97 "/actions/follow/:followeeDid/:rkey", 80 98 ["DELETE"], ··· 94 112 ), 95 113 route("/actions/photo/:rkey", ["PUT"], actionHandlers.photoEdit), 96 114 route("/actions/photo/:rkey", ["DELETE"], actionHandlers.photoDelete), 115 + route("/actions/photo", ["POST"], actionHandlers.uploadPhoto), 97 116 route("/actions/favorite", ["POST"], actionHandlers.galleryFavorite), 98 - route("/actions/profile/update", ["POST"], actionHandlers.profileUpdate), 117 + route("/actions/profile", ["PUT"], actionHandlers.profileUpdate), 99 118 route( 100 119 "/actions/gallery/:rkey/sort", 101 120 ["POST"], 102 121 actionHandlers.gallerySort, 103 122 ), 104 123 route("/actions/get-blob", ["GET"], actionHandlers.getBlob), 105 - route("/actions/photo/upload", ["POST"], actionHandlers.uploadPhoto), 106 124 route("/:did/:collection/:rkey", recordHandler), 107 125 ], 108 126 });
+62 -33
src/routes/actions.tsx
··· 10 10 import { BffContext, RouteHandler, WithBffMeta } from "@bigmoves/bff"; 11 11 import { FavoriteButton } from "../components/FavoriteButton.tsx"; 12 12 import { FollowButton } from "../components/FollowButton.tsx"; 13 - import { PhotoButton } from "../components/PhotoButton.tsx"; 13 + import { GalleryInfo } from "../components/GalleryInfo.tsx"; 14 + import { GalleryLayout } from "../components/GalleryLayout.tsx"; 14 15 import { PhotoPreview } from "../components/PhotoPreview.tsx"; 15 16 import { PhotoSelectButton } from "../components/PhotoSelectButton.tsx"; 17 + import { getFollowers } from "../lib/follow.ts"; 16 18 import { deleteGallery, getGallery, getGalleryFavs } from "../lib/gallery.ts"; 17 - import { photoToView } from "../lib/photo.ts"; 19 + import { getPhoto, photoToView } from "../lib/photo.ts"; 18 20 import type { State } from "../state.ts"; 19 21 import { galleryLink } from "../utils.ts"; 20 22 ··· 34 36 ctx: BffContext<State>, 35 37 ) => { 36 38 ctx.requireAuth(); 37 - const did = params.did; 38 - if (!did) return ctx.next(); 39 + const followeeDid = params.followeeDid; 40 + if (!followeeDid) return ctx.next(); 39 41 const followUri = await ctx.createRecord<BskyFollow>( 40 - "app.bsky.graph.follow", 42 + "social.grain.graph.follow", 41 43 { 42 - subject: did, 44 + subject: followeeDid, 43 45 createdAt: new Date().toISOString(), 44 46 }, 45 47 ); 46 - return ctx.html(<FollowButton followeeDid={did} followUri={followUri} />); 48 + const followers = getFollowers(followeeDid, ctx); 49 + return ctx.html( 50 + <> 51 + <div hx-swap-oob="innerHTML:#followers-count"> 52 + {followers.length} 53 + </div> 54 + <FollowButton followeeDid={followeeDid} followUri={followUri} /> 55 + </>, 56 + ); 47 57 }; 48 58 49 59 export const unfollow: RouteHandler = async ( ··· 55 65 const followeeDid = params.followeeDid; 56 66 const rkey = params.rkey; 57 67 await ctx.deleteRecord( 58 - `at://${did}/app.bsky.graph.follow/${rkey}`, 68 + `at://${did}/social.grain.graph.follow/${rkey}`, 59 69 ); 70 + const followers = getFollowers(followeeDid, ctx); 60 71 return ctx.html( 61 - <FollowButton followeeDid={followeeDid} followUri={undefined} />, 72 + <> 73 + <div hx-swap-oob="innerHTML:#followers-count"> 74 + {followers.length} 75 + </div> 76 + <FollowButton followeeDid={followeeDid} followUri={undefined} /> 77 + </>, 62 78 ); 63 79 }; 64 80 ··· 129 145 const galleryUri = `at://${did}/social.grain.gallery/${galleryRkey}`; 130 146 const photoUri = `at://${did}/social.grain.photo/${photoRkey}`; 131 147 const gallery = getGallery(did, galleryRkey, ctx); 132 - const photo = ctx.indexService.getRecord<WithBffMeta<Photo>>(photoUri); 148 + const photo = getPhoto(photoUri, ctx); 133 149 if (!gallery || !photo) return ctx.next(); 134 150 if ( 135 151 gallery.items ··· 146 162 }); 147 163 gallery.items = [ 148 164 ...(gallery.items ?? []), 149 - photoToView(photo.did, photo), 165 + photo, 150 166 ]; 151 167 return ctx.html( 152 168 <> 153 - <div hx-swap-oob="beforeend:#masonry-container"> 154 - <PhotoButton 169 + <div hx-swap-oob="beforeend:#gallery-container"> 170 + <GalleryLayout.Item 155 171 key={photo.cid} 156 - photo={photoToView(photo.did, photo)} 172 + photo={photo} 157 173 gallery={gallery} 158 174 /> 159 175 </div> 176 + <div hx-swap-oob="outerHTML:#gallery-info"> 177 + <GalleryInfo gallery={gallery} /> 178 + </div> 160 179 <PhotoSelectButton 161 180 galleryUri={galleryUri} 162 181 itemUris={gallery.items?.filter(isPhotoView).map((item) => item.uri) ?? 163 182 []} 164 - photo={photoToView(photo.did, photo)} 183 + photo={photo} 165 184 /> 166 185 </>, 167 186 ); ··· 202 221 const gallery = getGallery(did, galleryRkey, ctx); 203 222 if (!gallery) return ctx.next(); 204 223 return ctx.html( 205 - <PhotoSelectButton 206 - galleryUri={galleryUri} 207 - itemUris={gallery.items?.filter(isPhotoView).map((item) => item.uri) ?? 208 - []} 209 - photo={photoToView(photo.did, photo)} 210 - />, 224 + <> 225 + <div hx-swap-oob="outerHTML:#gallery-info"> 226 + <GalleryInfo gallery={gallery} /> 227 + </div> 228 + <PhotoSelectButton 229 + galleryUri={galleryUri} 230 + itemUris={gallery.items?.filter(isPhotoView).map((item) => item.uri) ?? 231 + []} 232 + photo={photoToView(photo.did, photo)} 233 + /> 234 + </>, 211 235 ); 212 236 }; 213 237 ··· 498 522 if (exifJsonString) { 499 523 try { 500 524 exif = JSON.parse(exifJsonString); 501 - console.log("Parsed EXIF data:", exif); 502 525 } catch (e) { 503 526 console.error("Failed to parse EXIF data:", e); 504 527 } ··· 530 553 createdAt: new Date().toISOString(), 531 554 }); 532 555 533 - const exifUri = await ctx.createRecord<PhotoExif>( 534 - "social.grain.photo.exif", 535 - { 536 - photo: photoUri, 537 - createdAt: new Date().toISOString(), 538 - ...exif, 539 - }, 540 - ); 556 + let exifUri: string | undefined = undefined; 557 + if (exif) { 558 + exifUri = await ctx.createRecord<PhotoExif>( 559 + "social.grain.photo.exif", 560 + { 561 + photo: photoUri, 562 + createdAt: new Date().toISOString(), 563 + ...exif, 564 + }, 565 + ); 566 + } 541 567 542 568 const photo = ctx.indexService.getRecord<WithBffMeta<Photo>>(photoUri); 543 569 if (!photo) { 544 570 return new Response("Photo not found after creation", { status: 404 }); 545 571 } 546 572 547 - const exifRecord = ctx.indexService.getRecord<WithBffMeta<PhotoExif>>( 548 - exifUri, 549 - ); 573 + let exifRecord: WithBffMeta<PhotoExif> | undefined = undefined; 574 + if (exifUri) { 575 + exifRecord = ctx.indexService.getRecord<WithBffMeta<PhotoExif>>( 576 + exifUri, 577 + ); 578 + } 550 579 551 580 return ctx.html( 552 581 <PhotoPreview
+25
src/routes/dialogs.tsx
··· 6 6 import { BffContext, RouteHandler, WithBffMeta } from "@bigmoves/bff"; 7 7 import { wrap } from "popmotion"; 8 8 import { AvatarDialog } from "../components/AvatarDialog.tsx"; 9 + import { CreateAccountDialog } from "../components/CreateAccountDialog.tsx"; 10 + import { ExifOverlayDialog } from "../components/ExifOverlayDialog.tsx"; 9 11 import { GalleryCreateEditDialog } from "../components/GalleryCreateEditDialog.tsx"; 10 12 import { GallerySortDialog } from "../components/GallerySortDialog.tsx"; 11 13 import { PhotoAltDialog } from "../components/PhotoAltDialog.tsx"; ··· 146 148 ); 147 149 }; 148 150 151 + export const photoExifOverlay: RouteHandler = ( 152 + _req, 153 + params, 154 + ctx: BffContext<State>, 155 + ) => { 156 + const { did } = ctx.requireAuth(); 157 + const photoRkey = params.rkey; 158 + const photoUri = `at://${did}/social.grain.photo/${photoRkey}`; 159 + const photo = getPhoto(photoUri, ctx); 160 + if (!photo) return ctx.next(); 161 + return ctx.html( 162 + <ExifOverlayDialog photo={photo} />, 163 + ); 164 + }; 165 + 149 166 export const galleryPhotoSelect: RouteHandler = ( 150 167 _req, 151 168 params, ··· 169 186 />, 170 187 ); 171 188 }; 189 + 190 + export const createAccount: RouteHandler = ( 191 + _req, 192 + _params, 193 + ctx: BffContext<State>, 194 + ) => { 195 + return ctx.html(<CreateAccountDialog />); 196 + };
+44
src/routes/followers.tsx
··· 1 + import { BffContext, RouteHandler } from "@bigmoves/bff"; 2 + import { Breadcrumb } from "../components/Breadcrumb.tsx"; 3 + import { FollowsList } from "../components/FollowsList.tsx"; 4 + import { Header } from "../components/Header.tsx"; 5 + import { getActorProfile } from "../lib/actor.ts"; 6 + import { getFollowersWithProfiles } from "../lib/follow.ts"; 7 + import { State } from "../state.ts"; 8 + import { profileLink } from "../utils.ts"; 9 + 10 + export const handler: RouteHandler = ( 11 + _req, 12 + params, 13 + ctx: BffContext<State>, 14 + ) => { 15 + const handle = params.handle; 16 + if (!handle) return ctx.next(); 17 + 18 + const actor = ctx.indexService.getActorByHandle(handle); 19 + 20 + if (!actor) return ctx.next(); 21 + 22 + const profile = getActorProfile(actor?.did, ctx); 23 + 24 + if (!actor) return ctx.next(); 25 + 26 + const followers = getFollowersWithProfiles(actor.did, ctx); 27 + 28 + ctx.state.meta = [{ title: `People following @${handle} — Grain` }]; 29 + 30 + return ctx.render( 31 + <div class="p-4"> 32 + <Breadcrumb 33 + items={[{ label: "profile", href: profileLink(handle) }, { 34 + label: "followers", 35 + }]} 36 + /> 37 + <Header>{profile?.displayName}</Header> 38 + <p class="mb-6 text-zinc-600 dark:text-zinc-500"> 39 + {followers.length ?? 0} followers 40 + </p> 41 + <FollowsList profiles={followers} /> 42 + </div>, 43 + ); 44 + };
+44
src/routes/follows.tsx
··· 1 + import { BffContext, RouteHandler } from "@bigmoves/bff"; 2 + import { Breadcrumb } from "../components/Breadcrumb.tsx"; 3 + import { FollowsList } from "../components/FollowsList.tsx"; 4 + import { Header } from "../components/Header.tsx"; 5 + import { getActorProfile } from "../lib/actor.ts"; 6 + import { getFollowingWithProfiles } from "../lib/follow.ts"; 7 + import { State } from "../state.ts"; 8 + import { profileLink } from "../utils.ts"; 9 + 10 + export const handler: RouteHandler = ( 11 + _req, 12 + params, 13 + ctx: BffContext<State>, 14 + ) => { 15 + const handle = params.handle; 16 + if (!handle) return ctx.next(); 17 + 18 + const actor = ctx.indexService.getActorByHandle(handle); 19 + 20 + if (!actor) return ctx.next(); 21 + 22 + const profile = getActorProfile(actor?.did, ctx); 23 + 24 + if (!actor) return ctx.next(); 25 + 26 + const following = getFollowingWithProfiles(actor.did, ctx); 27 + 28 + ctx.state.meta = [{ title: `People followed by @${handle} — Grain` }]; 29 + 30 + return ctx.render( 31 + <div class="p-4"> 32 + <Breadcrumb 33 + items={[{ label: "profile", href: profileLink(handle) }, { 34 + label: "following", 35 + }]} 36 + /> 37 + <Header>{profile?.displayName}</Header> 38 + <p class="mb-6 text-zinc-600 dark:text-zinc-500"> 39 + {following.length ?? 0} following 40 + </p> 41 + <FollowsList profiles={following} /> 42 + </div>, 43 + ); 44 + };
+5
src/routes/gallery.tsx
··· 17 17 const handle = params.handle; 18 18 const rkey = params.rkey; 19 19 const gallery = getGallery(handle, rkey, ctx); 20 + 20 21 if (!gallery) return ctx.next(); 22 + 21 23 favs = getGalleryFavs(gallery.uri, ctx); 24 + 22 25 ctx.state.meta = [ 23 26 { title: `${(gallery.record as Gallery).title} — Grain` }, 24 27 ...getPageMeta(galleryLink(handle, rkey)), 25 28 ...getGalleryMeta(gallery), 26 29 ]; 30 + 27 31 ctx.state.scripts = ["photo_dialog.js", "masonry.js", "sortable.js"]; 32 + 28 33 return ctx.render( 29 34 <GalleryPage favs={favs} gallery={gallery} currentUserDid={did} />, 30 35 );
+36
src/routes/legal.tsx
··· 1 + import { BffContext, RouteHandler } from "@bigmoves/bff"; 2 + import { CopyrightPolicy, PrivacyPolicy, Terms } from "../legal.tsx"; 3 + import type { State } from "../state.ts"; 4 + 5 + export const termsHandler: RouteHandler = ( 6 + _req, 7 + _params, 8 + ctx: BffContext<State>, 9 + ) => { 10 + ctx.state.meta = [{ title: "Terms — Grain" }]; 11 + return ctx.render( 12 + <Terms />, 13 + ); 14 + }; 15 + 16 + export const privacyHandler: RouteHandler = ( 17 + _req, 18 + _params, 19 + ctx: BffContext<State>, 20 + ) => { 21 + ctx.state.meta = [{ title: "Privacy Policy — Grain" }]; 22 + return ctx.render( 23 + <PrivacyPolicy />, 24 + ); 25 + }; 26 + 27 + export const copyrightHandler: RouteHandler = ( 28 + _req, 29 + _params, 30 + ctx: BffContext<State>, 31 + ) => { 32 + ctx.state.meta = [{ title: "Copyright Policy — Grain" }]; 33 + return ctx.render( 34 + <CopyrightPolicy />, 35 + ); 36 + };
+49 -19
src/routes/profile.tsx
··· 1 - import { Record as BskyFollow } from "$lexicon/types/app/bsky/graph/follow.ts"; 2 - import { BffContext, RouteHandler, WithBffMeta } from "@bigmoves/bff"; 3 - import { ProfilePage } from "../components/ProfilePage.tsx"; 4 - import { getActorGalleries, getActorProfile } from "../lib/actor.ts"; 5 - import { getFollow } from "../lib/follow.ts"; 6 - import { getActorTimeline } from "../lib/timeline.ts"; 1 + import { BffContext, RouteHandler } from "@bigmoves/bff"; 2 + import { ProfilePage, ProfileTabs } from "../components/ProfilePage.tsx"; 3 + import { 4 + getActorGalleries, 5 + getActorGalleryFavs, 6 + getActorProfile, 7 + getActorProfiles, 8 + } from "../lib/actor.ts"; 9 + import { getFollow, getFollowers, getFollowing } from "../lib/follow.ts"; 10 + import { type SocialNetwork } from "../lib/timeline.ts"; 7 11 import { getPageMeta } from "../meta.ts"; 8 12 import type { State } from "../state.ts"; 9 13 import { profileLink } from "../utils.ts"; ··· 14 18 ctx: BffContext<State>, 15 19 ) => { 16 20 const url = new URL(req.url); 17 - const tab = url.searchParams.get("tab"); 21 + const tab = url.searchParams.get("tab") as ProfileTabs; 18 22 const handle = params.handle; 19 - const timelineItems = getActorTimeline(handle, ctx); 20 - const galleries = getActorGalleries(handle, ctx); 21 23 const actor = ctx.indexService.getActorByHandle(handle); 24 + const isHxRequest = req.headers.get("hx-request") !== null; 25 + const render = isHxRequest ? ctx.html : ctx.render; 26 + 22 27 if (!actor) return ctx.next(); 28 + 23 29 const profile = getActorProfile(actor.did, ctx); 30 + const galleries = getActorGalleries(handle, ctx); 31 + const followers = getFollowers(actor.did, ctx); 32 + const following = getFollowing(actor.did, ctx); 33 + 24 34 if (!profile) return ctx.next(); 25 - let follow: WithBffMeta<BskyFollow> | undefined; 35 + 36 + let followUri: string | undefined; 37 + let actorProfiles: SocialNetwork[] = []; 38 + let userProfiles: SocialNetwork[] = []; 39 + 26 40 if (ctx.currentUser) { 27 - follow = getFollow(profile.did, ctx.currentUser.did, ctx); 41 + followUri = getFollow(profile.did, ctx.currentUser.did, ctx)?.uri; 42 + actorProfiles = getActorProfiles(ctx.currentUser.did, ctx); 28 43 } 44 + 45 + userProfiles = getActorProfiles(handle, ctx); 46 + 29 47 ctx.state.meta = [ 30 48 { 31 49 title: profile.displayName ··· 34 52 }, 35 53 ...getPageMeta(profileLink(handle)), 36 54 ]; 55 + 37 56 ctx.state.scripts = ["photo_manip.js", "profile_dialog.js"]; 38 - if (tab) { 39 - return ctx.html( 57 + 58 + if (tab === "favs") { 59 + const galleryFavs = getActorGalleryFavs(handle, ctx); 60 + return render( 40 61 <ProfilePage 41 - followUri={follow?.uri} 62 + followersCount={followers.length} 63 + followingCount={following.length} 64 + userProfiles={userProfiles} 65 + actorProfiles={actorProfiles} 66 + followUri={followUri} 42 67 loggedInUserDid={ctx.currentUser?.did} 43 - timelineItems={timelineItems} 44 68 profile={profile} 45 - selectedTab={tab} 69 + selectedTab="favs" 46 70 galleries={galleries} 71 + galleryFavs={galleryFavs} 47 72 />, 48 73 ); 49 74 } 50 - return ctx.render( 75 + return render( 51 76 <ProfilePage 52 - followUri={follow?.uri} 77 + followersCount={followers.length} 78 + followingCount={following.length} 79 + userProfiles={userProfiles} 80 + actorProfiles={actorProfiles} 81 + followUri={followUri} 53 82 loggedInUserDid={ctx.currentUser?.did} 54 - timelineItems={timelineItems} 55 83 profile={profile} 84 + selectedTab="galleries" 85 + galleries={galleries} 56 86 />, 57 87 ); 58 88 };
+33
src/routes/support.tsx
··· 1 + import { RouteHandler } from "@bigmoves/bff"; 2 + 3 + export const handler: RouteHandler = (_req, _params, ctx) => { 4 + ctx.state.meta = [{ title: "Support — Grain" }]; 5 + return ctx.render( 6 + <div className="px-4 py-4"> 7 + <h1 className="text-3xl font-bold mb-4 text-zinc-900 dark:text-white"> 8 + Support 9 + </h1> 10 + <p className="mb-4 text-zinc-700 dark:text-zinc-300"> 11 + For help, questions, or to report issues, please email us at{" "} 12 + <a 13 + href="mailto:support@grain.social" 14 + className="text-sky-500 hover:underline" 15 + > 16 + support@grain.social 17 + </a>. 18 + </p> 19 + <p className="mb-2 text-zinc-700 dark:text-zinc-300"> 20 + You can also review our{" "} 21 + <a href="/support/terms" className="text-sky-500 hover:underline"> 22 + Terms 23 + </a>,{" "} 24 + <a href="/support/privacy" className="text-sky-500 hover:underline"> 25 + Privacy Policy 26 + </a>, and{" "} 27 + <a href="/support/copyright" className="text-sky-500 hover:underline"> 28 + Copyright Policy 29 + </a>. 30 + </p> 31 + </div>, 32 + ); 33 + };
+58 -25
src/routes/timeline.tsx
··· 1 1 import { BffContext, RouteHandler } from "@bigmoves/bff"; 2 2 import { getCookies, setCookie } from "@std/http"; 3 3 import { Timeline } from "../components/Timeline.tsx"; 4 - import { getTimeline } from "../lib/timeline.ts"; 4 + import { getActorProfiles } from "../lib/actor.ts"; 5 + import { getTimeline, SocialNetwork } from "../lib/timeline.ts"; 5 6 import { getPageMeta } from "../meta.ts"; 6 7 import type { State } from "../state.ts"; 7 8 ··· 12 13 ) => { 13 14 const url = new URL(req.url); 14 15 const tabSearchParam = url.searchParams.get("tab") || ""; 15 - const cookieState = getCookieState(req.headers); 16 + const graphSearchParam = url.searchParams.get("graph") as SocialNetwork || 17 + "grain"; 18 + const cookieState = getCookieState(ctx?.currentUser?.did, req.headers); 19 + const isHxRequest = req.headers.get("hx-request") !== null; 20 + const render = isHxRequest ? ctx.html : ctx.render; 21 + 16 22 let tab; 23 + let graph: SocialNetwork = "grain"; 17 24 let headers: Record<string, string> = {}; 18 25 26 + const actorProfiles = getActorProfiles(ctx?.currentUser?.did ?? "", ctx); 27 + 19 28 if (!ctx.currentUser) { 20 29 tab = ""; 21 - } else if (!req.headers.get("hx-request")) { 30 + } else if (!isHxRequest) { 22 31 tab = cookieState.lastSelectedHomeFeed || ""; 32 + graph = cookieState.lastSelectedFollowGraph || "grain"; 23 33 } else { 24 34 tab = tabSearchParam || ""; 35 + graph = graphSearchParam; 25 36 headers = setCookieState(url.hostname, { 37 + did: ctx.currentUser.did, 26 38 lastSelectedHomeFeed: tab, 39 + lastSelectedFollowGraph: graph, 27 40 }); 28 41 } 29 42 43 + if (!graph && actorProfiles.length > 0) { 44 + graph = actorProfiles[0]; 45 + } 46 + 30 47 const items = getTimeline( 31 48 ctx, 32 49 tab === "following" ? "following" : "timeline", 50 + graph, 33 51 ); 34 52 35 53 if (tab === "following") { 36 - if (!req.headers.get("hx-request")) { 37 - ctx.state.meta = [{ title: "Following — Grain" }, ...getPageMeta("")]; 38 - return ctx.render( 39 - <Timeline 40 - isLoggedIn={!!ctx.currentUser} 41 - selectedTab={tab} 42 - items={items} 43 - />, 44 - headers, 45 - ); 46 - } 47 - return ctx.html( 54 + ctx.state.meta = [{ title: "Following — Grain" }, ...getPageMeta("")]; 55 + return render( 48 56 <Timeline 49 57 isLoggedIn={!!ctx.currentUser} 50 58 selectedTab={tab} 51 59 items={items} 60 + selectedGraph={graph} 61 + actorProfiles={actorProfiles} 52 62 />, 53 63 headers, 54 64 ); ··· 56 66 57 67 ctx.state.meta = [{ title: "Timeline — Grain" }, ...getPageMeta("")]; 58 68 59 - return ctx.render( 60 - <Timeline isLoggedIn={!!ctx.currentUser} selectedTab={tab} items={items} />, 69 + return render( 70 + <Timeline 71 + isLoggedIn={!!ctx.currentUser} 72 + selectedTab={tab} 73 + items={items} 74 + selectedGraph={graph} 75 + actorProfiles={actorProfiles} 76 + />, 61 77 headers, 62 78 ); 63 79 }; 64 80 65 81 type GrainStorageState = { 82 + did?: string; 66 83 lastSelectedHomeFeed?: string; 67 - }; 68 - 69 - const defaultGrainStorageState: GrainStorageState = { 70 - lastSelectedHomeFeed: undefined, 84 + lastSelectedFollowGraph?: SocialNetwork; 71 85 }; 72 86 73 87 function setCookieState( ··· 92 106 } 93 107 94 108 function getCookieState( 109 + did: string | undefined, 95 110 headers: Headers, 96 111 ): GrainStorageState { 97 112 const cookies = getCookies(headers); 98 113 if (!cookies.grain_storage) { 99 - return defaultGrainStorageState; 114 + return createDefaultCookieState(did); 100 115 } 101 116 const grainStorage = atob(cookies.grain_storage); 102 117 if (grainStorage) { 103 118 try { 104 - return JSON.parse(grainStorage); 119 + const parsed = JSON.parse(grainStorage); 120 + if (parsed.did && parsed.did !== did) { 121 + // If the did in the cookie doesn't match the current user, reset the state 122 + return createDefaultCookieState(did); 123 + } 124 + if (!parsed.did && did) { 125 + return createDefaultCookieState(did); 126 + } 127 + return parsed as GrainStorageState; 105 128 } catch { 106 - return defaultGrainStorageState; 129 + return createDefaultCookieState(did); 107 130 } 108 131 } 109 - return defaultGrainStorageState; 132 + return createDefaultCookieState(did); 133 + } 134 + 135 + function createDefaultCookieState( 136 + did: string | undefined, 137 + ): GrainStorageState { 138 + return { 139 + did, 140 + lastSelectedHomeFeed: "", 141 + lastSelectedFollowGraph: "grain", 142 + }; 110 143 }
+241
src/static/gallery_layout.ts
··· 1 + type LayoutMode = "justified" | "masonry"; 2 + 3 + type GalleryItem = HTMLElement & { 4 + dataset: { 5 + width: string; 6 + height: string; 7 + [key: string]: string; 8 + }; 9 + }; 10 + 11 + interface GalleryLayoutOptions { 12 + containerSelector?: string; 13 + layoutMode?: LayoutMode; 14 + spacing?: number; 15 + masonryBreakpoint?: number; 16 + } 17 + 18 + /** 19 + * GalleryLayout class for flexible photo gallery layouts (masonry/justified). 20 + * 21 + * Example usage with the GalleryLayout compositional component: 22 + * 23 + * // In your JSX component: 24 + * <GalleryLayout 25 + * layoutButtons={ 26 + * <> 27 + * <GalleryLayout.ModeButton mode="justified" /> 28 + * <GalleryLayout.ModeButton mode="masonry" /> 29 + * </> 30 + * } 31 + * > 32 + * <GalleryLayout.Container> 33 + * {photos.map(photo => ( 34 + * <GalleryLayout.Item key={photo.cid} photo={photo} gallery={gallery} /> 35 + * ))} 36 + * </GalleryLayout.Container> 37 + * </GalleryLayout> 38 + * 39 + * // In your static JS/TS: 40 + * import { GalleryLayout } from "../static/gallery_layout.ts"; 41 + * const galleryLayout = new GalleryLayout({ 42 + * containerSelector: "#gallery-container", 43 + * layoutMode: "justified", 44 + * spacing: 8, 45 + * masonryBreakpoint: 640, 46 + * }); 47 + * galleryLayout.init(); 48 + */ 49 + export class GalleryLayout { 50 + private observerInitialized = false; 51 + private layoutMode: LayoutMode; 52 + private containerSelector: string; 53 + private spacing: number; 54 + private masonryBreakpoint: number; 55 + 56 + constructor(options: GalleryLayoutOptions = {}) { 57 + this.layoutMode = options.layoutMode ?? "justified"; 58 + this.containerSelector = options.containerSelector ?? "#gallery-container"; 59 + this.spacing = options.spacing ?? 8; 60 + this.masonryBreakpoint = options.masonryBreakpoint ?? 640; 61 + } 62 + 63 + public setLayoutMode(mode: LayoutMode) { 64 + this.layoutMode = mode; 65 + this.computeLayout(); 66 + } 67 + 68 + public computeLayout(): void { 69 + if (this.layoutMode === "masonry") { 70 + this.computeMasonry(); 71 + } else { 72 + this.computeJustified(); 73 + } 74 + } 75 + 76 + public computeMasonry(): void { 77 + const container = document.querySelector<HTMLElement>( 78 + this.containerSelector, 79 + ); 80 + if (!container) return; 81 + 82 + const spacing = this.spacing; 83 + const containerWidth = container.offsetWidth; 84 + 85 + if (containerWidth === 0) { 86 + requestAnimationFrame(() => this.computeMasonry()); 87 + return; 88 + } 89 + 90 + const columns = containerWidth < this.masonryBreakpoint ? 1 : 3; 91 + const columnWidth = (containerWidth + spacing) / columns - spacing; 92 + const columnHeights: number[] = new Array(columns).fill(0); 93 + const tiles = container.querySelectorAll<HTMLElement>(".gallery-item"); 94 + 95 + tiles.forEach((tile) => { 96 + const imgW = parseFloat((tile as GalleryItem).dataset.width); 97 + const imgH = parseFloat((tile as GalleryItem).dataset.height); 98 + if (!imgW || !imgH) return; 99 + 100 + const aspectRatio = imgH / imgW; 101 + const renderedHeight = aspectRatio * columnWidth; 102 + 103 + let shortestIndex = 0; 104 + for (let i = 1; i < columns; i++) { 105 + if (columnHeights[i] < columnHeights[shortestIndex]) { 106 + shortestIndex = i; 107 + } 108 + } 109 + 110 + const left = (columnWidth + spacing) * shortestIndex; 111 + const top = columnHeights[shortestIndex]; 112 + 113 + Object.assign(tile.style, { 114 + position: "absolute", 115 + width: `${columnWidth}px`, 116 + height: `${renderedHeight}px`, 117 + left: `${left}px`, 118 + top: `${top}px`, 119 + }); 120 + 121 + columnHeights[shortestIndex] = top + renderedHeight + spacing; 122 + }); 123 + 124 + container.style.height = `${Math.max(...columnHeights)}px`; 125 + } 126 + 127 + public computeJustified(): void { 128 + const container = document.querySelector<HTMLElement>( 129 + this.containerSelector, 130 + ); 131 + if (!container) return; 132 + 133 + const spacing = this.spacing; 134 + const containerWidth = container.offsetWidth; 135 + 136 + if (containerWidth === 0) { 137 + requestAnimationFrame(() => this.computeJustified()); 138 + return; 139 + } 140 + 141 + const tiles = Array.from( 142 + container.querySelectorAll<HTMLElement>(".gallery-item"), 143 + ); 144 + let currentRow: Array< 145 + { tile: HTMLElement; aspectRatio: number; imgW: number; imgH: number } 146 + > = []; 147 + let rowAspectRatioSum = 0; 148 + let yOffset = 0; 149 + 150 + // Clear all styles before layout 151 + tiles.forEach((tile) => { 152 + Object.assign(tile.style, { 153 + position: "absolute", 154 + left: "0px", 155 + top: "0px", 156 + width: "auto", 157 + height: "auto", 158 + }); 159 + }); 160 + 161 + for (let i = 0; i < tiles.length; i++) { 162 + const tile = tiles[i]; 163 + const imgW = parseFloat((tile as GalleryItem).dataset.width); 164 + const imgH = parseFloat((tile as GalleryItem).dataset.height); 165 + if (!imgW || !imgH) continue; 166 + 167 + const aspectRatio = imgW / imgH; 168 + currentRow.push({ tile, aspectRatio, imgW, imgH }); 169 + rowAspectRatioSum += aspectRatio; 170 + 171 + // Estimate if row is "full" enough 172 + const estimatedRowHeight = 173 + (containerWidth - (currentRow.length - 1) * spacing) / 174 + rowAspectRatioSum; 175 + 176 + // If height is reasonable or we're at the end, render the row 177 + if (estimatedRowHeight < 300 || i === tiles.length - 1) { 178 + let xOffset = 0; 179 + 180 + for (const item of currentRow) { 181 + const width = estimatedRowHeight * item.aspectRatio; 182 + Object.assign(item.tile.style, { 183 + position: "absolute", 184 + top: `${yOffset}px`, 185 + left: `${xOffset}px`, 186 + width: `${width}px`, 187 + height: `${estimatedRowHeight}px`, 188 + }); 189 + xOffset += width + spacing; 190 + } 191 + 192 + yOffset += estimatedRowHeight + spacing; 193 + currentRow = []; 194 + rowAspectRatioSum = 0; 195 + } 196 + } 197 + 198 + container.style.position = "relative"; 199 + container.style.height = `${yOffset}px`; 200 + } 201 + 202 + public observe(): void { 203 + if (this.observerInitialized) return; 204 + this.observerInitialized = true; 205 + 206 + const container = document.querySelector<HTMLElement>( 207 + this.containerSelector, 208 + ); 209 + if (!container) return; 210 + 211 + // Observe parent resize 212 + if (typeof ResizeObserver !== "undefined") { 213 + const resizeObserver = new ResizeObserver(() => this.computeLayout()); 214 + if (container.parentElement) { 215 + resizeObserver.observe(container.parentElement); 216 + } 217 + } 218 + 219 + // Observe inner content changes (tiles being added/removed) 220 + const mutationObserver = new MutationObserver(() => { 221 + this.computeLayout(); 222 + }); 223 + 224 + mutationObserver.observe(container, { 225 + childList: true, 226 + subtree: true, 227 + }); 228 + } 229 + 230 + public init(options: GalleryLayoutOptions = {}): void { 231 + document.addEventListener("DOMContentLoaded", () => { 232 + const container = document.querySelector( 233 + options.containerSelector ?? "#gallery-container", 234 + ); 235 + if (container) { 236 + this.computeLayout(); 237 + this.observe(); 238 + } 239 + }); 240 + } 241 + }
+46
src/static/mod.ts
··· 1 + import htmx from "htmx.org"; 2 + import _hyperscript from "hyperscript.org"; 3 + import Sortable from "sortablejs"; 4 + import { GalleryLayout } from "./gallery_layout.ts"; 5 + import { PhotoDialog } from "./photo_dialog.ts"; 6 + import * as PhotoManip from "./photo_manip.ts"; 7 + import { ProfileDialog } from "./profile_dialog.ts"; 8 + import { UploadPage } from "./upload_page.ts"; 9 + 10 + const galleryLayout = new GalleryLayout({ layoutMode: "justified" }); 11 + galleryLayout.init(); 12 + 13 + htmx.onLoad(function (element) { 14 + PhotoDialog.maybeInitForElement(element); 15 + 16 + if (element.id === "gallery-sort-dialog") { 17 + const sortables = element.querySelectorAll(".sortable"); 18 + for (const sortable of Array.from(sortables)) { 19 + new Sortable(sortable, { 20 + animation: 150, 21 + }); 22 + } 23 + } 24 + }); 25 + 26 + _hyperscript.browserInit(); 27 + 28 + type GrainGlobal = typeof globalThis & { 29 + htmx: typeof htmx; 30 + _hyperscript: typeof _hyperscript; 31 + Grain: { 32 + uploadPage?: UploadPage; 33 + profileDialog?: ProfileDialog; 34 + galleryLayout?: GalleryLayout; 35 + photoManip?: typeof PhotoManip; 36 + }; 37 + }; 38 + 39 + const g = globalThis as GrainGlobal; 40 + g.htmx = g.htmx ?? htmx; 41 + g._hyperscript = g._hyperscript ?? _hyperscript; 42 + g.Grain = g.Grain ?? {}; 43 + g.Grain.uploadPage = new UploadPage(); 44 + g.Grain.profileDialog = new ProfileDialog(); 45 + g.Grain.galleryLayout = galleryLayout; 46 + g.Grain.photoManip = PhotoManip;
+94
src/static/photo_dialog.ts
··· 1 + /** 2 + * PhotoDialog class to handle touch events for swiping left or right 3 + * on a photo dialog element. 4 + * 5 + * Example usage (see src/static/mod.ts): 6 + * 7 + * htmx.onLoad(function (element) { 8 + * PhotoDialog.maybeInitForElement(element); 9 + * // ... 10 + * }); 11 + * 12 + * This will automatically initialize swipe event listeners and observation 13 + * when an element with id "photo-dialog" is loaded into the DOM. 14 + * 15 + * Example for htmx+JSX: 16 + * 17 + * <div 18 + * hx-get={photoDialogLink(gallery, nextImage)} 19 + * hx-trigger="keyup[key=='ArrowRight'] from:body, swipeleft from:body" 20 + * hx-target="#photo-dialog" 21 + * hx-swap="innerHTML" 22 + * /> 23 + * 24 + * This pattern allows htmx to swap the photo dialog content and trigger swipe events 25 + * that are handled by PhotoDialog. 26 + */ 27 + export class PhotoDialog { 28 + private startX = 0; 29 + private readonly threshold = 50; 30 + private observer: MutationObserver; 31 + private static initialized = false; 32 + public static id = "photo-dialog"; 33 + 34 + constructor() { 35 + this.observer = new MutationObserver(this.handleMutation.bind(this)); 36 + } 37 + 38 + public connect(): void { 39 + this.onTouchStart = this.onTouchStart.bind(this); 40 + this.onTouchEnd = this.onTouchEnd.bind(this); 41 + console.log("PhotoDialog: Connected touch event handlers."); 42 + } 43 + 44 + public static maybeInitForElement(element: Element): void { 45 + if (element.id !== PhotoDialog.id) return; 46 + if (PhotoDialog.initialized) return; 47 + const dialog = new PhotoDialog(); 48 + dialog.connect(); 49 + if (document && document.body) { 50 + document.body.addEventListener("touchstart", dialog.onTouchStart); 51 + document.body.addEventListener("touchend", dialog.onTouchEnd); 52 + dialog.observe(); 53 + PhotoDialog.initialized = true; 54 + } else { 55 + console.warn( 56 + "document.body not available for PhotoDialog event listeners", 57 + ); 58 + } 59 + } 60 + 61 + public onTouchStart(e: TouchEvent): void { 62 + this.startX = e.touches[0].clientX; 63 + } 64 + 65 + public onTouchEnd(e: TouchEvent): void { 66 + const endX = e.changedTouches[0].clientX; 67 + const diffX = endX - this.startX; 68 + if (Math.abs(diffX) > this.threshold) { 69 + const direction = diffX > 0 ? "swiperight" : "swipeleft"; 70 + (e.target as HTMLElement).dispatchEvent( 71 + new CustomEvent(direction, { bubbles: true }), 72 + ); 73 + } 74 + } 75 + 76 + public observe(): void { 77 + this.observer.observe(document.body, { childList: true, subtree: true }); 78 + } 79 + 80 + public disconnect(): void { 81 + this.observer.disconnect(); 82 + document.body.removeEventListener("touchstart", this.onTouchStart); 83 + document.body.removeEventListener("touchend", this.onTouchEnd); 84 + PhotoDialog.initialized = false; 85 + } 86 + 87 + private handleMutation(): void { 88 + const modal = document.getElementById(PhotoDialog.id); 89 + if (!modal) { 90 + console.log("PhotoDialog not found, removing event listeners"); 91 + this.disconnect(); 92 + } 93 + } 94 + }
+69
src/static/profile_dialog.ts
··· 1 + import htmx from "htmx.org"; 2 + import { dataURLToBlob, doResize, readFileAsDataURL } from "./photo_manip.ts"; 3 + 4 + export class ProfileDialog { 5 + public handleAvatarImageSelect(fileInput: HTMLInputElement): void { 6 + const file = fileInput.files?.[0]; 7 + if (file) { 8 + readFileAsDataURL(file).then((dataUrl) => { 9 + const previewImg = document.createElement("img"); 10 + if (typeof dataUrl === "string") { 11 + previewImg.src = dataUrl; 12 + } else { 13 + console.error( 14 + "Expected dataUrl to be a string, got:", 15 + typeof dataUrl, 16 + ); 17 + previewImg.src = ""; 18 + } 19 + previewImg.className = "rounded-full w-full h-full object-cover"; 20 + previewImg.alt = "Avatar preview"; 21 + 22 + const imagePreview = fileInput.closest("form")?.querySelector( 23 + "#image-preview", 24 + ); 25 + if (imagePreview) { 26 + imagePreview.innerHTML = ""; 27 + imagePreview.appendChild(previewImg); 28 + } 29 + }); 30 + } 31 + } 32 + 33 + public async updateProfile(formElement: HTMLFormElement): Promise<void> { 34 + const formData = new FormData(formElement); 35 + 36 + const avatarFile = formData.get("file"); 37 + if ( 38 + avatarFile instanceof File && avatarFile?.type?.startsWith?.("image/") 39 + ) { 40 + try { 41 + const dataUrl = await readFileAsDataURL(avatarFile); 42 + 43 + if (!dataUrl || typeof dataUrl !== "string") { 44 + console.error("Failed to read file as data URL"); 45 + return; 46 + } 47 + 48 + const resized = await doResize(dataUrl, { 49 + width: 2000, 50 + height: 2000, 51 + maxSize: 1000 * 1000, // 1MB 52 + mode: "contain", 53 + }); 54 + 55 + const blob = dataURLToBlob(resized.path); 56 + formData.set("file", blob, avatarFile.name); 57 + } catch (err) { 58 + console.error("Error resizing image:", err); 59 + formData.delete("file"); 60 + } 61 + } 62 + 63 + htmx.ajax("PUT", "/actions/profile", { 64 + swap: "none", 65 + values: Object.fromEntries(formData), 66 + source: formElement, 67 + }); 68 + } 69 + }
+12
src/static/tags.ts
··· 1 + export const tags = [ 2 + "DateTimeOriginal", 3 + "ExposureTime", 4 + "FNumber", 5 + "Flash", 6 + "FocalLengthIn35mmFormat", 7 + "ISO", 8 + "LensMake", 9 + "LensModel", 10 + "Make", 11 + "Model", 12 + ];
+125
src/static/upload_page.ts
··· 1 + import exifr from "exifr"; 2 + import htmx from "htmx.org"; 3 + import { dataURLToBlob, doResize, readFileAsDataURL } from "./photo_manip.ts"; 4 + import { tags as supportedTags } from "./tags.ts"; 5 + 6 + export class UploadPage { 7 + public async uploadPhotos(formElement: HTMLFormElement): Promise<void> { 8 + const formData = new FormData(formElement); 9 + const fileList = formData.getAll("files") as File[] ?? []; 10 + const parseExif = formData.get("parseExif") === "on"; 11 + 12 + if (fileList.length > 10) { 13 + alert("You can only upload 10 photos at a time"); 14 + return; 15 + } 16 + 17 + const uploadPromises = fileList.map(async (file) => { 18 + let fileDataUri: string | ArrayBuffer | null; 19 + let tags: Exif | undefined = undefined; 20 + let resized; 21 + 22 + try { 23 + fileDataUri = await readFileAsDataURL(file); 24 + if (fileDataUri === null || typeof fileDataUri !== "string") { 25 + console.error("File data URL is not a string:", fileDataUri); 26 + alert("Error reading file."); 27 + return; 28 + } 29 + } catch (err) { 30 + console.error("Error reading file as Data URL:", err); 31 + alert("Error reading file."); 32 + return; 33 + } 34 + 35 + if (parseExif) { 36 + try { 37 + const rawTags = await exifr.parse(file, { pick: supportedTags }); 38 + console.log("EXIF tags:", await exifr.parse(file)); 39 + tags = normalizeExif(rawTags); 40 + } catch (err) { 41 + console.error("Error reading EXIF data:", err); 42 + } 43 + } 44 + 45 + try { 46 + resized = await doResize(fileDataUri, { 47 + width: 2000, 48 + height: 2000, 49 + maxSize: 1000 * 1000, // 1MB 50 + mode: "contain", 51 + }); 52 + } catch (err) { 53 + console.error("Error resizing image:", err); 54 + alert("Error resizing image."); 55 + return; 56 + } 57 + 58 + const blob = dataURLToBlob(resized.path); 59 + 60 + const fd = new FormData(); 61 + fd.append("file", blob, file.name); 62 + fd.append("width", String(resized.width)); 63 + fd.append("height", String(resized.height)); 64 + 65 + if (tags) { 66 + fd.append("exif", JSON.stringify(tags)); 67 + } 68 + 69 + htmx.ajax("POST", "/actions/photo", { 70 + "swap": "afterbegin", 71 + "target": "#image-preview", 72 + "values": Object.fromEntries(fd), 73 + "source": formElement, 74 + }); 75 + }); 76 + 77 + await Promise.all(uploadPromises); 78 + 79 + // Clear the file input after upload 80 + (formElement.querySelector("input[type='file']") as HTMLInputElement)! 81 + .value = ""; 82 + } 83 + } 84 + 85 + const SCALE_FACTOR = 1000000; 86 + 87 + type Exif = Record< 88 + string, 89 + number | string | boolean | Array<number | string> | undefined | Date 90 + >; 91 + 92 + function normalizeExif( 93 + exif: Exif, 94 + scale: number = SCALE_FACTOR, 95 + ): Exif { 96 + const normalized: Record< 97 + string, 98 + number | string | boolean | Array<number | string> | undefined 99 + > = {}; 100 + 101 + for (const [key, value] of Object.entries(exif)) { 102 + const camelKey = key[0].toLowerCase() + key.slice(1); 103 + 104 + if (typeof value === "number") { 105 + normalized[camelKey] = Math.round(value * scale); 106 + } else if (Array.isArray(value)) { 107 + normalized[camelKey] = value.map((v) => 108 + typeof v === "number" ? Math.round(v * scale) : v 109 + ); 110 + } else if (value instanceof Date) { 111 + normalized[camelKey] = value.toISOString(); 112 + } else if (typeof value === "string") { 113 + normalized[camelKey] = value; 114 + } else if (typeof value === "boolean") { 115 + normalized[camelKey] = value; 116 + } else if (value === undefined) { 117 + normalized[camelKey] = undefined; 118 + } else { 119 + // fallback for unknown types 120 + normalized[camelKey] = String(value); 121 + } 122 + } 123 + 124 + return normalized; 125 + }
+10
src/utils.ts
··· 32 32 return `/profile/${handle}`; 33 33 } 34 34 35 + export function followersLink(handle: string) { 36 + return `/profile/${handle}/followers`; 37 + } 38 + 39 + export function followingLink(handle: string) { 40 + return `/profile/${handle}/follows`; 41 + } 42 + 35 43 export function galleryLink(handle: string, galleryRkey: string) { 36 44 return `/profile/${handle}/gallery/${galleryRkey}`; 37 45 } ··· 68 76 externalCollections: [ 69 77 "app.bsky.actor.profile", 70 78 "app.bsky.graph.follow", 79 + "sh.tangled.actor.profile", 80 + "sh.tangled.graph.follow", 71 81 ], 72 82 repos: [actor.did], 73 83 });
+26
static/app.esm.js
··· 1 + var $l=Object.create;var rs=Object.defineProperty;var Jl=Object.getOwnPropertyDescriptor;var Zl=Object.getOwnPropertyNames;var eu=Object.getPrototypeOf,tu=Object.prototype.hasOwnProperty;var ns=(Qr=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(Qr,{get:(Gr,Wr)=>(typeof require<"u"?require:Gr)[Wr]}):Qr)(function(Qr){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+Qr+'" is not supported')});var Qs=(Qr,Gr)=>()=>(Gr||Qr((Gr={exports:{}}).exports,Gr),Gr.exports),ru=(Qr,Gr)=>{for(var Wr in Gr)rs(Qr,Wr,{get:Gr[Wr],enumerable:!0})},nu=(Qr,Gr,Wr,Kr)=>{if(Gr&&typeof Gr=="object"||typeof Gr=="function")for(let Yr of Zl(Gr))!tu.call(Qr,Yr)&&Yr!==Wr&&rs(Qr,Yr,{get:()=>Gr[Yr],enumerable:!(Kr=Jl(Gr,Yr))||Kr.enumerable});return Qr};var po=(Qr,Gr,Wr)=>(Wr=Qr!=null?$l(eu(Qr)):{},nu(Gr||!Qr||!Qr.__esModule?rs(Wr,"default",{value:Qr,enumerable:!0}):Wr,Qr));var mo=Qs((exports,module)=>{(function(Qr,Gr){typeof define=="function"&&define.amd?define([],Gr):typeof module=="object"&&module.exports?module.exports=Gr():Qr.htmx=Qr.htmx||Gr()})(typeof self<"u"?self:exports,function(){return function(){"use strict";var Q={onLoad:F,process:zt,on:de,off:ge,trigger:ce,ajax:Nr,find:C,findAll:f,closest:v,values:function(Qr,Gr){var Wr=dr(Qr,Gr||"post");return Wr.values},remove:_,addClass:z,removeClass:n,toggleClass:$,takeClass:W,defineExtension:Ur,removeExtension:Br,logAll:V,logNone:j,logger:null,config:{historyEnabled:!0,historyCacheSize:10,refreshOnHistoryMiss:!1,defaultSwapStyle:"innerHTML",defaultSwapDelay:0,defaultSettleDelay:20,includeIndicatorStyles:!0,indicatorClass:"htmx-indicator",requestClass:"htmx-request",addedClass:"htmx-added",settlingClass:"htmx-settling",swappingClass:"htmx-swapping",allowEval:!0,allowScriptTags:!0,inlineScriptNonce:"",attributesToSettle:["class","style","width","height"],withCredentials:!1,timeout:0,wsReconnectDelay:"full-jitter",wsBinaryType:"blob",disableSelector:"[hx-disable], [data-hx-disable]",useTemplateFragments:!1,scrollBehavior:"smooth",defaultFocusScroll:!1,getCacheBusterParam:!1,globalViewTransitions:!1,methodsThatUseUrlParams:["get"],selfRequestsOnly:!1,ignoreTitle:!1,scrollIntoViewOnBoost:!0,triggerSpecsCache:null},parseInterval:d,_:t,createEventSource:function(Qr){return new EventSource(Qr,{withCredentials:!0})},createWebSocket:function(Qr){var Gr=new WebSocket(Qr,[]);return Gr.binaryType=Q.config.wsBinaryType,Gr},version:"1.9.12"},r={addTriggerHandler:Lt,bodyContains:se,canAccessLocalStorage:U,findThisElement:xe,filterValues:yr,hasAttribute:o,getAttributeValue:te,getClosestAttributeValue:ne,getClosestMatch:c,getExpressionVars:Hr,getHeaders:xr,getInputValues:dr,getInternalData:ae,getSwapSpecification:wr,getTriggerSpecs:it,getTarget:ye,makeFragment:l,mergeObjects:le,makeSettleInfo:T,oobSwap:Ee,querySelectorExt:ue,selectAndSwap:je,settleImmediately:nr,shouldCancel:ut,triggerEvent:ce,triggerErrorEvent:fe,withExtensions:R},w=["get","post","put","delete","patch"],i=w.map(function(Qr){return"[hx-"+Qr+"], [data-hx-"+Qr+"]"}).join(", "),S=e("head"),q=e("title"),H=e("svg",!0);function e(Qr,Gr){return new RegExp("<"+Qr+"(\\s[^>]*>|>)([\\s\\S]*?)<\\/"+Qr+">",Gr?"gim":"im")}function d(Qr){if(Qr==null)return;let Gr=NaN;return Qr.slice(-2)=="ms"?Gr=parseFloat(Qr.slice(0,-2)):Qr.slice(-1)=="s"?Gr=parseFloat(Qr.slice(0,-1))*1e3:Qr.slice(-1)=="m"?Gr=parseFloat(Qr.slice(0,-1))*1e3*60:Gr=parseFloat(Qr),isNaN(Gr)?void 0:Gr}function ee(Qr,Gr){return Qr.getAttribute&&Qr.getAttribute(Gr)}function o(Qr,Gr){return Qr.hasAttribute&&(Qr.hasAttribute(Gr)||Qr.hasAttribute("data-"+Gr))}function te(Qr,Gr){return ee(Qr,Gr)||ee(Qr,"data-"+Gr)}function u(Qr){return Qr.parentElement}function re(){return document}function c(Qr,Gr){for(;Qr&&!Gr(Qr);)Qr=u(Qr);return Qr||null}function L(Qr,Gr,Wr){var Kr=te(Gr,Wr),Yr=te(Gr,"hx-disinherit");return Qr!==Gr&&Yr&&(Yr==="*"||Yr.split(" ").indexOf(Wr)>=0)?"unset":Kr}function ne(Qr,Gr){var Wr=null;if(c(Qr,function(Kr){return Wr=L(Qr,Kr,Gr)}),Wr!=="unset")return Wr}function h(Qr,Gr){var Wr=Qr.matches||Qr.matchesSelector||Qr.msMatchesSelector||Qr.mozMatchesSelector||Qr.webkitMatchesSelector||Qr.oMatchesSelector;return Wr&&Wr.call(Qr,Gr)}function A(Qr){var Gr=/<([a-z][^\/\0>\x20\t\r\n\f]*)/i,Wr=Gr.exec(Qr);return Wr?Wr[1].toLowerCase():""}function s(Qr,Gr){for(var Wr=new DOMParser,Kr=Wr.parseFromString(Qr,"text/html"),Yr=Kr.body;Gr>0;)Gr--,Yr=Yr.firstChild;return Yr==null&&(Yr=re().createDocumentFragment()),Yr}function N(Qr){return/<body/.test(Qr)}function l(Qr){var Gr=!N(Qr),Wr=A(Qr),Kr=Qr;if(Wr==="head"&&(Kr=Kr.replace(S,"")),Q.config.useTemplateFragments&&Gr){var Yr=s("<body><template>"+Kr+"</template></body>",0),Zr=Yr.querySelector("template").content;return Q.config.allowScriptTags?oe(Zr.querySelectorAll("script"),function(tn){Q.config.inlineScriptNonce&&(tn.nonce=Q.config.inlineScriptNonce),tn.htmxExecuted=navigator.userAgent.indexOf("Firefox")===-1}):oe(Zr.querySelectorAll("script"),function(tn){_(tn)}),Zr}switch(Wr){case"thead":case"tbody":case"tfoot":case"colgroup":case"caption":return s("<table>"+Kr+"</table>",1);case"col":return s("<table><colgroup>"+Kr+"</colgroup></table>",2);case"tr":return s("<table><tbody>"+Kr+"</tbody></table>",2);case"td":case"th":return s("<table><tbody><tr>"+Kr+"</tr></tbody></table>",3);case"script":case"style":return s("<div>"+Kr+"</div>",1);default:return s(Kr,0)}}function ie(Qr){Qr&&Qr()}function I(Qr,Gr){return Object.prototype.toString.call(Qr)==="[object "+Gr+"]"}function k(Qr){return I(Qr,"Function")}function P(Qr){return I(Qr,"Object")}function ae(Qr){var Gr="htmx-internal-data",Wr=Qr[Gr];return Wr||(Wr=Qr[Gr]={}),Wr}function M(Qr){var Gr=[];if(Qr)for(var Wr=0;Wr<Qr.length;Wr++)Gr.push(Qr[Wr]);return Gr}function oe(Qr,Gr){if(Qr)for(var Wr=0;Wr<Qr.length;Wr++)Gr(Qr[Wr])}function X(Qr){var Gr=Qr.getBoundingClientRect(),Wr=Gr.top,Kr=Gr.bottom;return Wr<window.innerHeight&&Kr>=0}function se(Qr){return Qr.getRootNode&&Qr.getRootNode()instanceof window.ShadowRoot?re().body.contains(Qr.getRootNode().host):re().body.contains(Qr)}function D(Qr){return Qr.trim().split(/\s+/)}function le(Qr,Gr){for(var Wr in Gr)Gr.hasOwnProperty(Wr)&&(Qr[Wr]=Gr[Wr]);return Qr}function E(Qr){try{return JSON.parse(Qr)}catch(Gr){return b(Gr),null}}function U(){var Qr="htmx:localStorageTest";try{return localStorage.setItem(Qr,Qr),localStorage.removeItem(Qr),!0}catch{return!1}}function B(Qr){try{var Gr=new URL(Qr);return Gr&&(Qr=Gr.pathname+Gr.search),/^\/$/.test(Qr)||(Qr=Qr.replace(/\/+$/,"")),Qr}catch{return Qr}}function t(e){return Tr(re().body,function(){return eval(e)})}function F(Qr){var Gr=Q.on("htmx:load",function(Wr){Qr(Wr.detail.elt)});return Gr}function V(){Q.logger=function(Qr,Gr,Wr){console&&console.log(Gr,Qr,Wr)}}function j(){Q.logger=null}function C(Qr,Gr){return Gr?Qr.querySelector(Gr):C(re(),Qr)}function f(Qr,Gr){return Gr?Qr.querySelectorAll(Gr):f(re(),Qr)}function _(Qr,Gr){Qr=p(Qr),Gr?setTimeout(function(){_(Qr),Qr=null},Gr):Qr.parentElement.removeChild(Qr)}function z(Qr,Gr,Wr){Qr=p(Qr),Wr?setTimeout(function(){z(Qr,Gr),Qr=null},Wr):Qr.classList&&Qr.classList.add(Gr)}function n(Qr,Gr,Wr){Qr=p(Qr),Wr?setTimeout(function(){n(Qr,Gr),Qr=null},Wr):Qr.classList&&(Qr.classList.remove(Gr),Qr.classList.length===0&&Qr.removeAttribute("class"))}function $(Qr,Gr){Qr=p(Qr),Qr.classList.toggle(Gr)}function W(Qr,Gr){Qr=p(Qr),oe(Qr.parentElement.children,function(Wr){n(Wr,Gr)}),z(Qr,Gr)}function v(Qr,Gr){if(Qr=p(Qr),Qr.closest)return Qr.closest(Gr);do if(Qr==null||h(Qr,Gr))return Qr;while(Qr=Qr&&u(Qr));return null}function g(Qr,Gr){return Qr.substring(0,Gr.length)===Gr}function G(Qr,Gr){return Qr.substring(Qr.length-Gr.length)===Gr}function J(Qr){var Gr=Qr.trim();return g(Gr,"<")&&G(Gr,"/>")?Gr.substring(1,Gr.length-2):Gr}function Z(Qr,Gr){return Gr.indexOf("closest ")===0?[v(Qr,J(Gr.substr(8)))]:Gr.indexOf("find ")===0?[C(Qr,J(Gr.substr(5)))]:Gr==="next"?[Qr.nextElementSibling]:Gr.indexOf("next ")===0?[K(Qr,J(Gr.substr(5)))]:Gr==="previous"?[Qr.previousElementSibling]:Gr.indexOf("previous ")===0?[Y(Qr,J(Gr.substr(9)))]:Gr==="document"?[document]:Gr==="window"?[window]:Gr==="body"?[document.body]:re().querySelectorAll(J(Gr))}var K=function(Qr,Gr){for(var Wr=re().querySelectorAll(Gr),Kr=0;Kr<Wr.length;Kr++){var Yr=Wr[Kr];if(Yr.compareDocumentPosition(Qr)===Node.DOCUMENT_POSITION_PRECEDING)return Yr}},Y=function(Qr,Gr){for(var Wr=re().querySelectorAll(Gr),Kr=Wr.length-1;Kr>=0;Kr--){var Yr=Wr[Kr];if(Yr.compareDocumentPosition(Qr)===Node.DOCUMENT_POSITION_FOLLOWING)return Yr}};function ue(Qr,Gr){return Gr?Z(Qr,Gr)[0]:Z(re().body,Qr)[0]}function p(Qr){return I(Qr,"String")?C(Qr):Qr}function ve(Qr,Gr,Wr){return k(Gr)?{target:re().body,event:Qr,listener:Gr}:{target:p(Qr),event:Gr,listener:Wr}}function de(Qr,Gr,Wr){jr(function(){var Yr=ve(Qr,Gr,Wr);Yr.target.addEventListener(Yr.event,Yr.listener)});var Kr=k(Gr);return Kr?Gr:Wr}function ge(Qr,Gr,Wr){return jr(function(){var Kr=ve(Qr,Gr,Wr);Kr.target.removeEventListener(Kr.event,Kr.listener)}),k(Gr)?Gr:Wr}var pe=re().createElement("output");function me(Qr,Gr){var Wr=ne(Qr,Gr);if(Wr){if(Wr==="this")return[xe(Qr,Gr)];var Kr=Z(Qr,Wr);return Kr.length===0?(b('The selector "'+Wr+'" on '+Gr+" returned no matches!"),[pe]):Kr}}function xe(Qr,Gr){return c(Qr,function(Wr){return te(Wr,Gr)!=null})}function ye(Qr){var Gr=ne(Qr,"hx-target");if(Gr)return Gr==="this"?xe(Qr,"hx-target"):ue(Qr,Gr);var Wr=ae(Qr);return Wr.boosted?re().body:Qr}function be(Qr){for(var Gr=Q.config.attributesToSettle,Wr=0;Wr<Gr.length;Wr++)if(Qr===Gr[Wr])return!0;return!1}function we(Qr,Gr){oe(Qr.attributes,function(Wr){!Gr.hasAttribute(Wr.name)&&be(Wr.name)&&Qr.removeAttribute(Wr.name)}),oe(Gr.attributes,function(Wr){be(Wr.name)&&Qr.setAttribute(Wr.name,Wr.value)})}function Se(Qr,Gr){for(var Wr=Fr(Gr),Kr=0;Kr<Wr.length;Kr++){var Yr=Wr[Kr];try{if(Yr.isInlineSwap(Qr))return!0}catch(Zr){b(Zr)}}return Qr==="outerHTML"}function Ee(Qr,Gr,Wr){var Kr="#"+ee(Gr,"id"),Yr="outerHTML";Qr==="true"||(Qr.indexOf(":")>0?(Yr=Qr.substr(0,Qr.indexOf(":")),Kr=Qr.substr(Qr.indexOf(":")+1,Qr.length)):Yr=Qr);var Zr=re().querySelectorAll(Kr);return Zr?(oe(Zr,function(tn){var ln,dn=Gr.cloneNode(!0);ln=re().createDocumentFragment(),ln.appendChild(dn),Se(Yr,tn)||(ln=dn);var yn={shouldSwap:!0,target:tn,fragment:ln};ce(tn,"htmx:oobBeforeSwap",yn)&&(tn=yn.target,yn.shouldSwap&&Fe(Yr,tn,tn,ln,Wr),oe(Wr.elts,function(wn){ce(wn,"htmx:oobAfterSwap",yn)}))}),Gr.parentNode.removeChild(Gr)):(Gr.parentNode.removeChild(Gr),fe(re().body,"htmx:oobErrorNoTarget",{content:Gr})),Qr}function Ce(Qr,Gr,Wr){var Kr=ne(Qr,"hx-select-oob");if(Kr)for(var Yr=Kr.split(","),Zr=0;Zr<Yr.length;Zr++){var tn=Yr[Zr].split(":",2),ln=tn[0].trim();ln.indexOf("#")===0&&(ln=ln.substring(1));var dn=tn[1]||"true",yn=Gr.querySelector("#"+ln);yn&&Ee(dn,yn,Wr)}oe(f(Gr,"[hx-swap-oob], [data-hx-swap-oob]"),function(wn){var kn=te(wn,"hx-swap-oob");kn!=null&&Ee(kn,wn,Wr)})}function Re(Qr){oe(f(Qr,"[hx-preserve], [data-hx-preserve]"),function(Gr){var Wr=te(Gr,"id"),Kr=re().getElementById(Wr);Kr!=null&&Gr.parentNode.replaceChild(Kr,Gr)})}function Te(Qr,Gr,Wr){oe(Gr.querySelectorAll("[id]"),function(Kr){var Yr=ee(Kr,"id");if(Yr&&Yr.length>0){var Zr=Yr.replace("'","\\'"),tn=Kr.tagName.replace(":","\\:"),ln=Qr.querySelector(tn+"[id='"+Zr+"']");if(ln&&ln!==Qr){var dn=Kr.cloneNode();we(Kr,ln),Wr.tasks.push(function(){we(Kr,dn)})}}})}function Oe(Qr){return function(){n(Qr,Q.config.addedClass),zt(Qr),Nt(Qr),qe(Qr),ce(Qr,"htmx:load")}}function qe(Qr){var Gr="[autofocus]",Wr=h(Qr,Gr)?Qr:Qr.querySelector(Gr);Wr?.focus()}function a(Qr,Gr,Wr,Kr){for(Te(Qr,Wr,Kr);Wr.childNodes.length>0;){var Yr=Wr.firstChild;z(Yr,Q.config.addedClass),Qr.insertBefore(Yr,Gr),Yr.nodeType!==Node.TEXT_NODE&&Yr.nodeType!==Node.COMMENT_NODE&&Kr.tasks.push(Oe(Yr))}}function He(Qr,Gr){for(var Wr=0;Wr<Qr.length;)Gr=(Gr<<5)-Gr+Qr.charCodeAt(Wr++)|0;return Gr}function Le(Qr){var Gr=0;if(Qr.attributes)for(var Wr=0;Wr<Qr.attributes.length;Wr++){var Kr=Qr.attributes[Wr];Kr.value&&(Gr=He(Kr.name,Gr),Gr=He(Kr.value,Gr))}return Gr}function Ae(Qr){var Gr=ae(Qr);if(Gr.onHandlers){for(var Wr=0;Wr<Gr.onHandlers.length;Wr++){let Kr=Gr.onHandlers[Wr];Qr.removeEventListener(Kr.event,Kr.listener)}delete Gr.onHandlers}}function Ne(Qr){var Gr=ae(Qr);Gr.timeout&&clearTimeout(Gr.timeout),Gr.webSocket&&Gr.webSocket.close(),Gr.sseEventSource&&Gr.sseEventSource.close(),Gr.listenerInfos&&oe(Gr.listenerInfos,function(Wr){Wr.on&&Wr.on.removeEventListener(Wr.trigger,Wr.listener)}),Ae(Qr),oe(Object.keys(Gr),function(Wr){delete Gr[Wr]})}function m(Qr){ce(Qr,"htmx:beforeCleanupElement"),Ne(Qr),Qr.children&&oe(Qr.children,function(Gr){m(Gr)})}function Ie(Qr,Gr,Wr){if(Qr.tagName==="BODY")return Ue(Qr,Gr,Wr);var Kr,Yr=Qr.previousSibling;for(a(u(Qr),Qr,Gr,Wr),Yr==null?Kr=u(Qr).firstChild:Kr=Yr.nextSibling,Wr.elts=Wr.elts.filter(function(Zr){return Zr!=Qr});Kr&&Kr!==Qr;)Kr.nodeType===Node.ELEMENT_NODE&&Wr.elts.push(Kr),Kr=Kr.nextElementSibling;m(Qr),u(Qr).removeChild(Qr)}function ke(Qr,Gr,Wr){return a(Qr,Qr.firstChild,Gr,Wr)}function Pe(Qr,Gr,Wr){return a(u(Qr),Qr,Gr,Wr)}function Me(Qr,Gr,Wr){return a(Qr,null,Gr,Wr)}function Xe(Qr,Gr,Wr){return a(u(Qr),Qr.nextSibling,Gr,Wr)}function De(Qr,Gr,Wr){return m(Qr),u(Qr).removeChild(Qr)}function Ue(Qr,Gr,Wr){var Kr=Qr.firstChild;if(a(Qr,Kr,Gr,Wr),Kr){for(;Kr.nextSibling;)m(Kr.nextSibling),Qr.removeChild(Kr.nextSibling);m(Kr),Qr.removeChild(Kr)}}function Be(Qr,Gr,Wr){var Kr=Wr||ne(Qr,"hx-select");if(Kr){var Yr=re().createDocumentFragment();oe(Gr.querySelectorAll(Kr),function(Zr){Yr.appendChild(Zr)}),Gr=Yr}return Gr}function Fe(Qr,Gr,Wr,Kr,Yr){switch(Qr){case"none":return;case"outerHTML":Ie(Wr,Kr,Yr);return;case"afterbegin":ke(Wr,Kr,Yr);return;case"beforebegin":Pe(Wr,Kr,Yr);return;case"beforeend":Me(Wr,Kr,Yr);return;case"afterend":Xe(Wr,Kr,Yr);return;case"delete":De(Wr,Kr,Yr);return;default:for(var Zr=Fr(Gr),tn=0;tn<Zr.length;tn++){var ln=Zr[tn];try{var dn=ln.handleSwap(Qr,Wr,Kr,Yr);if(dn){if(typeof dn.length<"u")for(var yn=0;yn<dn.length;yn++){var wn=dn[yn];wn.nodeType!==Node.TEXT_NODE&&wn.nodeType!==Node.COMMENT_NODE&&Yr.tasks.push(Oe(wn))}return}}catch(kn){b(kn)}}Qr==="innerHTML"?Ue(Wr,Kr,Yr):Fe(Q.config.defaultSwapStyle,Gr,Wr,Kr,Yr)}}function Ve(Qr){if(Qr.indexOf("<title")>-1){var Gr=Qr.replace(H,""),Wr=Gr.match(q);if(Wr)return Wr[2]}}function je(Qr,Gr,Wr,Kr,Yr,Zr){Yr.title=Ve(Kr);var tn=l(Kr);if(tn)return Ce(Wr,tn,Yr),tn=Be(Wr,tn,Zr),Re(tn),Fe(Qr,Wr,Gr,tn,Yr)}function _e(Qr,Gr,Wr){var Kr=Qr.getResponseHeader(Gr);if(Kr.indexOf("{")===0){var Yr=E(Kr);for(var Zr in Yr)if(Yr.hasOwnProperty(Zr)){var tn=Yr[Zr];P(tn)||(tn={value:tn}),ce(Wr,Zr,tn)}}else for(var ln=Kr.split(","),dn=0;dn<ln.length;dn++)ce(Wr,ln[dn].trim(),[])}var ze=/\s/,x=/[\s,]/,$e=/[_$a-zA-Z]/,We=/[_$a-zA-Z0-9]/,Ge=['"',"'","/"],Je=/[^\s]/,Ze=/[{(]/,Ke=/[})]/;function Ye(Qr){for(var Gr=[],Wr=0;Wr<Qr.length;){if($e.exec(Qr.charAt(Wr))){for(var Kr=Wr;We.exec(Qr.charAt(Wr+1));)Wr++;Gr.push(Qr.substr(Kr,Wr-Kr+1))}else if(Ge.indexOf(Qr.charAt(Wr))!==-1){var Yr=Qr.charAt(Wr),Kr=Wr;for(Wr++;Wr<Qr.length&&Qr.charAt(Wr)!==Yr;)Qr.charAt(Wr)==="\\"&&Wr++,Wr++;Gr.push(Qr.substr(Kr,Wr-Kr+1))}else{var Zr=Qr.charAt(Wr);Gr.push(Zr)}Wr++}return Gr}function Qe(Qr,Gr,Wr){return $e.exec(Qr.charAt(0))&&Qr!=="true"&&Qr!=="false"&&Qr!=="this"&&Qr!==Wr&&Gr!=="."}function et(Qr,Gr,Wr){if(Gr[0]==="["){Gr.shift();for(var Kr=1,Yr=" return (function("+Wr+"){ return (",Zr=null;Gr.length>0;){var tn=Gr[0];if(tn==="]"){if(Kr--,Kr===0){Zr===null&&(Yr=Yr+"true"),Gr.shift(),Yr+=")})";try{var ln=Tr(Qr,function(){return Function(Yr)()},function(){return!0});return ln.source=Yr,ln}catch(dn){return fe(re().body,"htmx:syntax:error",{error:dn,source:Yr}),null}}}else tn==="["&&Kr++;Qe(tn,Zr,Wr)?Yr+="(("+Wr+"."+tn+") ? ("+Wr+"."+tn+") : (window."+tn+"))":Yr=Yr+tn,Zr=Gr.shift()}}}function y(Qr,Gr){for(var Wr="";Qr.length>0&&!Gr.test(Qr[0]);)Wr+=Qr.shift();return Wr}function tt(Qr){var Gr;return Qr.length>0&&Ze.test(Qr[0])?(Qr.shift(),Gr=y(Qr,Ke).trim(),Qr.shift()):Gr=y(Qr,x),Gr}var rt="input, textarea, select";function nt(Qr,Gr,Wr){var Kr=[],Yr=Ye(Gr);do{y(Yr,Je);var Zr=Yr.length,tn=y(Yr,/[,\[\s]/);if(tn!=="")if(tn==="every"){var ln={trigger:"every"};y(Yr,Je),ln.pollInterval=d(y(Yr,/[,\[\s]/)),y(Yr,Je);var dn=et(Qr,Yr,"event");dn&&(ln.eventFilter=dn),Kr.push(ln)}else if(tn.indexOf("sse:")===0)Kr.push({trigger:"sse",sseEvent:tn.substr(4)});else{var yn={trigger:tn},dn=et(Qr,Yr,"event");for(dn&&(yn.eventFilter=dn);Yr.length>0&&Yr[0]!==",";){y(Yr,Je);var wn=Yr.shift();if(wn==="changed")yn.changed=!0;else if(wn==="once")yn.once=!0;else if(wn==="consume")yn.consume=!0;else if(wn==="delay"&&Yr[0]===":")Yr.shift(),yn.delay=d(y(Yr,x));else if(wn==="from"&&Yr[0]===":"){if(Yr.shift(),Ze.test(Yr[0]))var kn=tt(Yr);else{var kn=y(Yr,x);if(kn==="closest"||kn==="find"||kn==="next"||kn==="previous"){Yr.shift();var An=tt(Yr);An.length>0&&(kn+=" "+An)}}yn.from=kn}else wn==="target"&&Yr[0]===":"?(Yr.shift(),yn.target=tt(Yr)):wn==="throttle"&&Yr[0]===":"?(Yr.shift(),yn.throttle=d(y(Yr,x))):wn==="queue"&&Yr[0]===":"?(Yr.shift(),yn.queue=y(Yr,x)):wn==="root"&&Yr[0]===":"?(Yr.shift(),yn[wn]=tt(Yr)):wn==="threshold"&&Yr[0]===":"?(Yr.shift(),yn[wn]=y(Yr,x)):fe(Qr,"htmx:syntax:error",{token:Yr.shift()})}Kr.push(yn)}Yr.length===Zr&&fe(Qr,"htmx:syntax:error",{token:Yr.shift()}),y(Yr,Je)}while(Yr[0]===","&&Yr.shift());return Wr&&(Wr[Gr]=Kr),Kr}function it(Qr){var Gr=te(Qr,"hx-trigger"),Wr=[];if(Gr){var Kr=Q.config.triggerSpecsCache;Wr=Kr&&Kr[Gr]||nt(Qr,Gr,Kr)}return Wr.length>0?Wr:h(Qr,"form")?[{trigger:"submit"}]:h(Qr,'input[type="button"], input[type="submit"]')?[{trigger:"click"}]:h(Qr,rt)?[{trigger:"change"}]:[{trigger:"click"}]}function at(Qr){ae(Qr).cancelled=!0}function ot(Qr,Gr,Wr){var Kr=ae(Qr);Kr.timeout=setTimeout(function(){se(Qr)&&Kr.cancelled!==!0&&(ct(Wr,Qr,Wt("hx:poll:trigger",{triggerSpec:Wr,target:Qr}))||Gr(Qr),ot(Qr,Gr,Wr))},Wr.pollInterval)}function st(Qr){return location.hostname===Qr.hostname&&ee(Qr,"href")&&ee(Qr,"href").indexOf("#")!==0}function lt(Qr,Gr,Wr){if(Qr.tagName==="A"&&st(Qr)&&(Qr.target===""||Qr.target==="_self")||Qr.tagName==="FORM"){Gr.boosted=!0;var Kr,Yr;if(Qr.tagName==="A")Kr="get",Yr=ee(Qr,"href");else{var Zr=ee(Qr,"method");Kr=Zr?Zr.toLowerCase():"get",Yr=ee(Qr,"action")}Wr.forEach(function(tn){ht(Qr,function(ln,dn){if(v(ln,Q.config.disableSelector)){m(ln);return}he(Kr,Yr,ln,dn)},Gr,tn,!0)})}}function ut(Qr,Gr){return!!((Qr.type==="submit"||Qr.type==="click")&&(Gr.tagName==="FORM"||h(Gr,'input[type="submit"], button')&&v(Gr,"form")!==null||Gr.tagName==="A"&&Gr.href&&(Gr.getAttribute("href")==="#"||Gr.getAttribute("href").indexOf("#")!==0)))}function ft(Qr,Gr){return ae(Qr).boosted&&Qr.tagName==="A"&&Gr.type==="click"&&(Gr.ctrlKey||Gr.metaKey)}function ct(Qr,Gr,Wr){var Kr=Qr.eventFilter;if(Kr)try{return Kr.call(Gr,Wr)!==!0}catch(Yr){return fe(re().body,"htmx:eventFilter:error",{error:Yr,source:Kr.source}),!0}return!1}function ht(Qr,Gr,Wr,Kr,Yr){var Zr=ae(Qr),tn;Kr.from?tn=Z(Qr,Kr.from):tn=[Qr],Kr.changed&&tn.forEach(function(ln){var dn=ae(ln);dn.lastValue=ln.value}),oe(tn,function(ln){var dn=function(yn){if(!se(Qr)){ln.removeEventListener(Kr.trigger,dn);return}if(!ft(Qr,yn)&&((Yr||ut(yn,Qr))&&yn.preventDefault(),!ct(Kr,Qr,yn))){var wn=ae(yn);if(wn.triggerSpec=Kr,wn.handledFor==null&&(wn.handledFor=[]),wn.handledFor.indexOf(Qr)<0){if(wn.handledFor.push(Qr),Kr.consume&&yn.stopPropagation(),Kr.target&&yn.target&&!h(yn.target,Kr.target))return;if(Kr.once){if(Zr.triggeredOnce)return;Zr.triggeredOnce=!0}if(Kr.changed){var kn=ae(ln);if(kn.lastValue===ln.value)return;kn.lastValue=ln.value}if(Zr.delayed&&clearTimeout(Zr.delayed),Zr.throttle)return;Kr.throttle>0?Zr.throttle||(Gr(Qr,yn),Zr.throttle=setTimeout(function(){Zr.throttle=null},Kr.throttle)):Kr.delay>0?Zr.delayed=setTimeout(function(){Gr(Qr,yn)},Kr.delay):(ce(Qr,"htmx:trigger"),Gr(Qr,yn))}}};Wr.listenerInfos==null&&(Wr.listenerInfos=[]),Wr.listenerInfos.push({trigger:Kr.trigger,listener:dn,on:ln}),ln.addEventListener(Kr.trigger,dn)})}var vt=!1,dt=null;function gt(){dt||(dt=function(){vt=!0},window.addEventListener("scroll",dt),setInterval(function(){vt&&(vt=!1,oe(re().querySelectorAll("[hx-trigger='revealed'],[data-hx-trigger='revealed']"),function(Qr){pt(Qr)}))},200))}function pt(Qr){if(!o(Qr,"data-hx-revealed")&&X(Qr)){Qr.setAttribute("data-hx-revealed","true");var Gr=ae(Qr);Gr.initHash?ce(Qr,"revealed"):Qr.addEventListener("htmx:afterProcessNode",function(Wr){ce(Qr,"revealed")},{once:!0})}}function mt(Qr,Gr,Wr){for(var Kr=D(Wr),Yr=0;Yr<Kr.length;Yr++){var Zr=Kr[Yr].split(/:(.+)/);Zr[0]==="connect"&&xt(Qr,Zr[1],0),Zr[0]==="send"&&bt(Qr)}}function xt(Qr,Gr,Wr){if(se(Qr)){if(Gr.indexOf("/")==0){var Kr=location.hostname+(location.port?":"+location.port:"");location.protocol=="https:"?Gr="wss://"+Kr+Gr:location.protocol=="http:"&&(Gr="ws://"+Kr+Gr)}var Yr=Q.createWebSocket(Gr);Yr.onerror=function(Zr){fe(Qr,"htmx:wsError",{error:Zr,socket:Yr}),yt(Qr)},Yr.onclose=function(Zr){if([1006,1012,1013].indexOf(Zr.code)>=0){var tn=wt(Wr);setTimeout(function(){xt(Qr,Gr,Wr+1)},tn)}},Yr.onopen=function(Zr){Wr=0},ae(Qr).webSocket=Yr,Yr.addEventListener("message",function(Zr){if(!yt(Qr)){var tn=Zr.data;R(Qr,function(An){tn=An.transformResponse(tn,null,Qr)});for(var ln=T(Qr),dn=l(tn),yn=M(dn.children),wn=0;wn<yn.length;wn++){var kn=yn[wn];Ee(te(kn,"hx-swap-oob")||"true",kn,ln)}nr(ln.tasks)}})}}function yt(Qr){if(!se(Qr))return ae(Qr).webSocket.close(),!0}function bt(Qr){var Gr=c(Qr,function(Wr){return ae(Wr).webSocket!=null});Gr?Qr.addEventListener(it(Qr)[0].trigger,function(Wr){var Kr=ae(Gr).webSocket,Yr=xr(Qr,Gr),Zr=dr(Qr,"post"),tn=Zr.errors,ln=Zr.values,dn=Hr(Qr),yn=le(ln,dn),wn=yr(yn,Qr);if(wn.HEADERS=Yr,tn&&tn.length>0){ce(Qr,"htmx:validation:halted",tn);return}Kr.send(JSON.stringify(wn)),ut(Wr,Qr)&&Wr.preventDefault()}):fe(Qr,"htmx:noWebSocketSourceError")}function wt(Qr){var Gr=Q.config.wsReconnectDelay;if(typeof Gr=="function")return Gr(Qr);if(Gr==="full-jitter"){var Wr=Math.min(Qr,6),Kr=1e3*Math.pow(2,Wr);return Kr*Math.random()}b('htmx.config.wsReconnectDelay must either be a function or the string "full-jitter"')}function St(Qr,Gr,Wr){for(var Kr=D(Wr),Yr=0;Yr<Kr.length;Yr++){var Zr=Kr[Yr].split(/:(.+)/);Zr[0]==="connect"&&Et(Qr,Zr[1]),Zr[0]==="swap"&&Ct(Qr,Zr[1])}}function Et(Qr,Gr){var Wr=Q.createEventSource(Gr);Wr.onerror=function(Kr){fe(Qr,"htmx:sseError",{error:Kr,source:Wr}),Tt(Qr)},ae(Qr).sseEventSource=Wr}function Ct(Qr,Gr){var Wr=c(Qr,Ot);if(Wr){var Kr=ae(Wr).sseEventSource,Yr=function(Zr){if(!Tt(Wr)){if(!se(Qr)){Kr.removeEventListener(Gr,Yr);return}var tn=Zr.data;R(Qr,function(wn){tn=wn.transformResponse(tn,null,Qr)});var ln=wr(Qr),dn=ye(Qr),yn=T(Qr);je(ln.swapStyle,dn,Qr,tn,yn),nr(yn.tasks),ce(Qr,"htmx:sseMessage",Zr)}};ae(Qr).sseListener=Yr,Kr.addEventListener(Gr,Yr)}else fe(Qr,"htmx:noSSESourceError")}function Rt(Qr,Gr,Wr){var Kr=c(Qr,Ot);if(Kr){var Yr=ae(Kr).sseEventSource,Zr=function(){Tt(Kr)||(se(Qr)?Gr(Qr):Yr.removeEventListener(Wr,Zr))};ae(Qr).sseListener=Zr,Yr.addEventListener(Wr,Zr)}else fe(Qr,"htmx:noSSESourceError")}function Tt(Qr){if(!se(Qr))return ae(Qr).sseEventSource.close(),!0}function Ot(Qr){return ae(Qr).sseEventSource!=null}function qt(Qr,Gr,Wr,Kr){var Yr=function(){Wr.loaded||(Wr.loaded=!0,Gr(Qr))};Kr>0?setTimeout(Yr,Kr):Yr()}function Ht(Qr,Gr,Wr){var Kr=!1;return oe(w,function(Yr){if(o(Qr,"hx-"+Yr)){var Zr=te(Qr,"hx-"+Yr);Kr=!0,Gr.path=Zr,Gr.verb=Yr,Wr.forEach(function(tn){Lt(Qr,tn,Gr,function(ln,dn){if(v(ln,Q.config.disableSelector)){m(ln);return}he(Yr,Zr,ln,dn)})})}}),Kr}function Lt(Qr,Gr,Wr,Kr){if(Gr.sseEvent)Rt(Qr,Kr,Gr.sseEvent);else if(Gr.trigger==="revealed")gt(),ht(Qr,Kr,Wr,Gr),pt(Qr);else if(Gr.trigger==="intersect"){var Yr={};Gr.root&&(Yr.root=ue(Qr,Gr.root)),Gr.threshold&&(Yr.threshold=parseFloat(Gr.threshold));var Zr=new IntersectionObserver(function(tn){for(var ln=0;ln<tn.length;ln++){var dn=tn[ln];if(dn.isIntersecting){ce(Qr,"intersect");break}}},Yr);Zr.observe(Qr),ht(Qr,Kr,Wr,Gr)}else Gr.trigger==="load"?ct(Gr,Qr,Wt("load",{elt:Qr}))||qt(Qr,Kr,Wr,Gr.delay):Gr.pollInterval>0?(Wr.polling=!0,ot(Qr,Kr,Gr)):ht(Qr,Kr,Wr,Gr)}function At(Qr){if(!Qr.htmxExecuted&&Q.config.allowScriptTags&&(Qr.type==="text/javascript"||Qr.type==="module"||Qr.type==="")){var Gr=re().createElement("script");oe(Qr.attributes,function(Kr){Gr.setAttribute(Kr.name,Kr.value)}),Gr.textContent=Qr.textContent,Gr.async=!1,Q.config.inlineScriptNonce&&(Gr.nonce=Q.config.inlineScriptNonce);var Wr=Qr.parentElement;try{Wr.insertBefore(Gr,Qr)}catch(Kr){b(Kr)}finally{Qr.parentElement&&Qr.parentElement.removeChild(Qr)}}}function Nt(Qr){h(Qr,"script")&&At(Qr),oe(f(Qr,"script"),function(Gr){At(Gr)})}function It(Qr){var Gr=Qr.attributes;if(!Gr)return!1;for(var Wr=0;Wr<Gr.length;Wr++){var Kr=Gr[Wr].name;if(g(Kr,"hx-on:")||g(Kr,"data-hx-on:")||g(Kr,"hx-on-")||g(Kr,"data-hx-on-"))return!0}return!1}function kt(Qr){var Gr=null,Wr=[];if(It(Qr)&&Wr.push(Qr),document.evaluate)for(var Kr=document.evaluate('.//*[@*[ starts-with(name(), "hx-on:") or starts-with(name(), "data-hx-on:") or starts-with(name(), "hx-on-") or starts-with(name(), "data-hx-on-") ]]',Qr);Gr=Kr.iterateNext();)Wr.push(Gr);else if(typeof Qr.getElementsByTagName=="function")for(var Yr=Qr.getElementsByTagName("*"),Zr=0;Zr<Yr.length;Zr++)It(Yr[Zr])&&Wr.push(Yr[Zr]);return Wr}function Pt(Qr){if(Qr.querySelectorAll){var Gr=", [hx-boost] a, [data-hx-boost] a, a[hx-boost], a[data-hx-boost]",Wr=Qr.querySelectorAll(i+Gr+", form, [type='submit'], [hx-sse], [data-hx-sse], [hx-ws], [data-hx-ws], [hx-ext], [data-hx-ext], [hx-trigger], [data-hx-trigger], [hx-on], [data-hx-on]");return Wr}else return[]}function Mt(Qr){var Gr=v(Qr.target,"button, input[type='submit']"),Wr=Dt(Qr);Wr&&(Wr.lastButtonClicked=Gr)}function Xt(Qr){var Gr=Dt(Qr);Gr&&(Gr.lastButtonClicked=null)}function Dt(Qr){var Gr=v(Qr.target,"button, input[type='submit']");if(Gr){var Wr=p("#"+ee(Gr,"form"))||v(Gr,"form");if(Wr)return ae(Wr)}}function Ut(Qr){Qr.addEventListener("click",Mt),Qr.addEventListener("focusin",Mt),Qr.addEventListener("focusout",Xt)}function Bt(Qr){for(var Gr=Ye(Qr),Wr=0,Kr=0;Kr<Gr.length;Kr++){let Yr=Gr[Kr];Yr==="{"?Wr++:Yr==="}"&&Wr--}return Wr}function Ft(Qr,Gr,Wr){var Kr=ae(Qr);Array.isArray(Kr.onHandlers)||(Kr.onHandlers=[]);var Yr,Zr=function(tn){return Tr(Qr,function(){Yr||(Yr=new Function("event",Wr)),Yr.call(Qr,tn)})};Qr.addEventListener(Gr,Zr),Kr.onHandlers.push({event:Gr,listener:Zr})}function Vt(Qr){var Gr=te(Qr,"hx-on");if(Gr){for(var Wr={},Kr=Gr.split(` 2 + `),Yr=null,Zr=0;Kr.length>0;){var tn=Kr.shift(),ln=tn.match(/^\s*([a-zA-Z:\-\.]+:)(.*)/);Zr===0&&ln?(tn.split(":"),Yr=ln[1].slice(0,-1),Wr[Yr]=ln[2]):Wr[Yr]+=tn,Zr+=Bt(tn)}for(var dn in Wr)Ft(Qr,dn,Wr[dn])}}function jt(Qr){Ae(Qr);for(var Gr=0;Gr<Qr.attributes.length;Gr++){var Wr=Qr.attributes[Gr].name,Kr=Qr.attributes[Gr].value;if(g(Wr,"hx-on")||g(Wr,"data-hx-on")){var Yr=Wr.indexOf("-on")+3,Zr=Wr.slice(Yr,Yr+1);if(Zr==="-"||Zr===":"){var tn=Wr.slice(Yr+1);g(tn,":")?tn="htmx"+tn:g(tn,"-")?tn="htmx:"+tn.slice(1):g(tn,"htmx-")&&(tn="htmx:"+tn.slice(5)),Ft(Qr,tn,Kr)}}}}function _t(Qr){if(v(Qr,Q.config.disableSelector)){m(Qr);return}var Gr=ae(Qr);if(Gr.initHash!==Le(Qr)){Ne(Qr),Gr.initHash=Le(Qr),Vt(Qr),ce(Qr,"htmx:beforeProcessNode"),Qr.value&&(Gr.lastValue=Qr.value);var Wr=it(Qr),Kr=Ht(Qr,Gr,Wr);Kr||(ne(Qr,"hx-boost")==="true"?lt(Qr,Gr,Wr):o(Qr,"hx-trigger")&&Wr.forEach(function(tn){Lt(Qr,tn,Gr,function(){})})),(Qr.tagName==="FORM"||ee(Qr,"type")==="submit"&&o(Qr,"form"))&&Ut(Qr);var Yr=te(Qr,"hx-sse");Yr&&St(Qr,Gr,Yr);var Zr=te(Qr,"hx-ws");Zr&&mt(Qr,Gr,Zr),ce(Qr,"htmx:afterProcessNode")}}function zt(Qr){if(Qr=p(Qr),v(Qr,Q.config.disableSelector)){m(Qr);return}_t(Qr),oe(Pt(Qr),function(Gr){_t(Gr)}),oe(kt(Qr),jt)}function $t(Qr){return Qr.replace(/([a-z0-9])([A-Z])/g,"$1-$2").toLowerCase()}function Wt(Qr,Gr){var Wr;return window.CustomEvent&&typeof window.CustomEvent=="function"?Wr=new CustomEvent(Qr,{bubbles:!0,cancelable:!0,detail:Gr}):(Wr=re().createEvent("CustomEvent"),Wr.initCustomEvent(Qr,!0,!0,Gr)),Wr}function fe(Qr,Gr,Wr){ce(Qr,Gr,le({error:Gr},Wr))}function Gt(Qr){return Qr==="htmx:afterProcessNode"}function R(Qr,Gr){oe(Fr(Qr),function(Wr){try{Gr(Wr)}catch(Kr){b(Kr)}})}function b(Qr){console.error?console.error(Qr):console.log&&console.log("ERROR: ",Qr)}function ce(Qr,Gr,Wr){Qr=p(Qr),Wr==null&&(Wr={}),Wr.elt=Qr;var Kr=Wt(Gr,Wr);Q.logger&&!Gt(Gr)&&Q.logger(Qr,Gr,Wr),Wr.error&&(b(Wr.error),ce(Qr,"htmx:error",{errorInfo:Wr}));var Yr=Qr.dispatchEvent(Kr),Zr=$t(Gr);if(Yr&&Zr!==Gr){var tn=Wt(Zr,Kr.detail);Yr=Yr&&Qr.dispatchEvent(tn)}return R(Qr,function(ln){Yr=Yr&&ln.onEvent(Gr,Kr)!==!1&&!Kr.defaultPrevented}),Yr}var Jt=location.pathname+location.search;function Zt(){var Qr=re().querySelector("[hx-history-elt],[data-hx-history-elt]");return Qr||re().body}function Kt(Qr,Gr,Wr,Kr){if(U()){if(Q.config.historyCacheSize<=0){localStorage.removeItem("htmx-history-cache");return}Qr=B(Qr);for(var Yr=E(localStorage.getItem("htmx-history-cache"))||[],Zr=0;Zr<Yr.length;Zr++)if(Yr[Zr].url===Qr){Yr.splice(Zr,1);break}var tn={url:Qr,content:Gr,title:Wr,scroll:Kr};for(ce(re().body,"htmx:historyItemCreated",{item:tn,cache:Yr}),Yr.push(tn);Yr.length>Q.config.historyCacheSize;)Yr.shift();for(;Yr.length>0;)try{localStorage.setItem("htmx-history-cache",JSON.stringify(Yr));break}catch(ln){fe(re().body,"htmx:historyCacheError",{cause:ln,cache:Yr}),Yr.shift()}}}function Yt(Qr){if(!U())return null;Qr=B(Qr);for(var Gr=E(localStorage.getItem("htmx-history-cache"))||[],Wr=0;Wr<Gr.length;Wr++)if(Gr[Wr].url===Qr)return Gr[Wr];return null}function Qt(Qr){var Gr=Q.config.requestClass,Wr=Qr.cloneNode(!0);return oe(f(Wr,"."+Gr),function(Kr){n(Kr,Gr)}),Wr.innerHTML}function er(){var Qr=Zt(),Gr=Jt||location.pathname+location.search,Wr;try{Wr=re().querySelector('[hx-history="false" i],[data-hx-history="false" i]')}catch{Wr=re().querySelector('[hx-history="false"],[data-hx-history="false"]')}Wr||(ce(re().body,"htmx:beforeHistorySave",{path:Gr,historyElt:Qr}),Kt(Gr,Qt(Qr),re().title,window.scrollY)),Q.config.historyEnabled&&history.replaceState({htmx:!0},re().title,window.location.href)}function tr(Qr){Q.config.getCacheBusterParam&&(Qr=Qr.replace(/org\.htmx\.cache-buster=[^&]*&?/,""),(G(Qr,"&")||G(Qr,"?"))&&(Qr=Qr.slice(0,-1))),Q.config.historyEnabled&&history.pushState({htmx:!0},"",Qr),Jt=Qr}function rr(Qr){Q.config.historyEnabled&&history.replaceState({htmx:!0},"",Qr),Jt=Qr}function nr(Qr){oe(Qr,function(Gr){Gr.call()})}function ir(Qr){var Gr=new XMLHttpRequest,Wr={path:Qr,xhr:Gr};ce(re().body,"htmx:historyCacheMiss",Wr),Gr.open("GET",Qr,!0),Gr.setRequestHeader("HX-Request","true"),Gr.setRequestHeader("HX-History-Restore-Request","true"),Gr.setRequestHeader("HX-Current-URL",re().location.href),Gr.onload=function(){if(this.status>=200&&this.status<400){ce(re().body,"htmx:historyCacheMissLoad",Wr);var Kr=l(this.response);Kr=Kr.querySelector("[hx-history-elt],[data-hx-history-elt]")||Kr;var Yr=Zt(),Zr=T(Yr),tn=Ve(this.response);if(tn){var ln=C("title");ln?ln.innerHTML=tn:window.document.title=tn}Ue(Yr,Kr,Zr),nr(Zr.tasks),Jt=Qr,ce(re().body,"htmx:historyRestore",{path:Qr,cacheMiss:!0,serverResponse:this.response})}else fe(re().body,"htmx:historyCacheMissLoadError",Wr)},Gr.send()}function ar(Qr){er(),Qr=Qr||location.pathname+location.search;var Gr=Yt(Qr);if(Gr){var Wr=l(Gr.content),Kr=Zt(),Yr=T(Kr);Ue(Kr,Wr,Yr),nr(Yr.tasks),document.title=Gr.title,setTimeout(function(){window.scrollTo(0,Gr.scroll)},0),Jt=Qr,ce(re().body,"htmx:historyRestore",{path:Qr,item:Gr})}else Q.config.refreshOnHistoryMiss?window.location.reload(!0):ir(Qr)}function or(Qr){var Gr=me(Qr,"hx-indicator");return Gr==null&&(Gr=[Qr]),oe(Gr,function(Wr){var Kr=ae(Wr);Kr.requestCount=(Kr.requestCount||0)+1,Wr.classList.add.call(Wr.classList,Q.config.requestClass)}),Gr}function sr(Qr){var Gr=me(Qr,"hx-disabled-elt");return Gr==null&&(Gr=[]),oe(Gr,function(Wr){var Kr=ae(Wr);Kr.requestCount=(Kr.requestCount||0)+1,Wr.setAttribute("disabled","")}),Gr}function lr(Qr,Gr){oe(Qr,function(Wr){var Kr=ae(Wr);Kr.requestCount=(Kr.requestCount||0)-1,Kr.requestCount===0&&Wr.classList.remove.call(Wr.classList,Q.config.requestClass)}),oe(Gr,function(Wr){var Kr=ae(Wr);Kr.requestCount=(Kr.requestCount||0)-1,Kr.requestCount===0&&Wr.removeAttribute("disabled")})}function ur(Qr,Gr){for(var Wr=0;Wr<Qr.length;Wr++){var Kr=Qr[Wr];if(Kr.isSameNode(Gr))return!0}return!1}function fr(Qr){return Qr.name===""||Qr.name==null||Qr.disabled||v(Qr,"fieldset[disabled]")||Qr.type==="button"||Qr.type==="submit"||Qr.tagName==="image"||Qr.tagName==="reset"||Qr.tagName==="file"?!1:Qr.type==="checkbox"||Qr.type==="radio"?Qr.checked:!0}function cr(Qr,Gr,Wr){if(Qr!=null&&Gr!=null){var Kr=Wr[Qr];Kr===void 0?Wr[Qr]=Gr:Array.isArray(Kr)?Array.isArray(Gr)?Wr[Qr]=Kr.concat(Gr):Kr.push(Gr):Array.isArray(Gr)?Wr[Qr]=[Kr].concat(Gr):Wr[Qr]=[Kr,Gr]}}function hr(Qr,Gr,Wr,Kr,Yr){if(!(Kr==null||ur(Qr,Kr))){if(Qr.push(Kr),fr(Kr)){var Zr=ee(Kr,"name"),tn=Kr.value;Kr.multiple&&Kr.tagName==="SELECT"&&(tn=M(Kr.querySelectorAll("option:checked")).map(function(dn){return dn.value})),Kr.files&&(tn=M(Kr.files)),cr(Zr,tn,Gr),Yr&&vr(Kr,Wr)}if(h(Kr,"form")){var ln=Kr.elements;oe(ln,function(dn){hr(Qr,Gr,Wr,dn,Yr)})}}}function vr(Qr,Gr){Qr.willValidate&&(ce(Qr,"htmx:validation:validate"),Qr.checkValidity()||(Gr.push({elt:Qr,message:Qr.validationMessage,validity:Qr.validity}),ce(Qr,"htmx:validation:failed",{message:Qr.validationMessage,validity:Qr.validity})))}function dr(Qr,Gr){var Wr=[],Kr={},Yr={},Zr=[],tn=ae(Qr);tn.lastButtonClicked&&!se(tn.lastButtonClicked)&&(tn.lastButtonClicked=null);var ln=h(Qr,"form")&&Qr.noValidate!==!0||te(Qr,"hx-validate")==="true";if(tn.lastButtonClicked&&(ln=ln&&tn.lastButtonClicked.formNoValidate!==!0),Gr!=="get"&&hr(Wr,Yr,Zr,v(Qr,"form"),ln),hr(Wr,Kr,Zr,Qr,ln),tn.lastButtonClicked||Qr.tagName==="BUTTON"||Qr.tagName==="INPUT"&&ee(Qr,"type")==="submit"){var dn=tn.lastButtonClicked||Qr,yn=ee(dn,"name");cr(yn,dn.value,Yr)}var wn=me(Qr,"hx-include");return oe(wn,function(kn){hr(Wr,Kr,Zr,kn,ln),h(kn,"form")||oe(kn.querySelectorAll(rt),function(An){hr(Wr,Kr,Zr,An,ln)})}),Kr=le(Kr,Yr),{errors:Zr,values:Kr}}function gr(Qr,Gr,Wr){Qr!==""&&(Qr+="&"),String(Wr)==="[object Object]"&&(Wr=JSON.stringify(Wr));var Kr=encodeURIComponent(Wr);return Qr+=encodeURIComponent(Gr)+"="+Kr,Qr}function pr(Qr){var Gr="";for(var Wr in Qr)if(Qr.hasOwnProperty(Wr)){var Kr=Qr[Wr];Array.isArray(Kr)?oe(Kr,function(Yr){Gr=gr(Gr,Wr,Yr)}):Gr=gr(Gr,Wr,Kr)}return Gr}function mr(Qr){var Gr=new FormData;for(var Wr in Qr)if(Qr.hasOwnProperty(Wr)){var Kr=Qr[Wr];Array.isArray(Kr)?oe(Kr,function(Yr){Gr.append(Wr,Yr)}):Gr.append(Wr,Kr)}return Gr}function xr(Qr,Gr,Wr){var Kr={"HX-Request":"true","HX-Trigger":ee(Qr,"id"),"HX-Trigger-Name":ee(Qr,"name"),"HX-Target":te(Gr,"id"),"HX-Current-URL":re().location.href};return Rr(Qr,"hx-headers",!1,Kr),Wr!==void 0&&(Kr["HX-Prompt"]=Wr),ae(Qr).boosted&&(Kr["HX-Boosted"]="true"),Kr}function yr(Qr,Gr){var Wr=ne(Gr,"hx-params");if(Wr){if(Wr==="none")return{};if(Wr==="*")return Qr;if(Wr.indexOf("not ")===0)return oe(Wr.substr(4).split(","),function(Yr){Yr=Yr.trim(),delete Qr[Yr]}),Qr;var Kr={};return oe(Wr.split(","),function(Yr){Yr=Yr.trim(),Kr[Yr]=Qr[Yr]}),Kr}else return Qr}function br(Qr){return ee(Qr,"href")&&ee(Qr,"href").indexOf("#")>=0}function wr(Qr,Gr){var Wr=Gr||ne(Qr,"hx-swap"),Kr={swapStyle:ae(Qr).boosted?"innerHTML":Q.config.defaultSwapStyle,swapDelay:Q.config.defaultSwapDelay,settleDelay:Q.config.defaultSettleDelay};if(Q.config.scrollIntoViewOnBoost&&ae(Qr).boosted&&!br(Qr)&&(Kr.show="top"),Wr){var Yr=D(Wr);if(Yr.length>0)for(var Zr=0;Zr<Yr.length;Zr++){var tn=Yr[Zr];if(tn.indexOf("swap:")===0)Kr.swapDelay=d(tn.substr(5));else if(tn.indexOf("settle:")===0)Kr.settleDelay=d(tn.substr(7));else if(tn.indexOf("transition:")===0)Kr.transition=tn.substr(11)==="true";else if(tn.indexOf("ignoreTitle:")===0)Kr.ignoreTitle=tn.substr(12)==="true";else if(tn.indexOf("scroll:")===0){var ln=tn.substr(7),dn=ln.split(":"),yn=dn.pop(),wn=dn.length>0?dn.join(":"):null;Kr.scroll=yn,Kr.scrollTarget=wn}else if(tn.indexOf("show:")===0){var kn=tn.substr(5),dn=kn.split(":"),An=dn.pop(),wn=dn.length>0?dn.join(":"):null;Kr.show=An,Kr.showTarget=wn}else if(tn.indexOf("focus-scroll:")===0){var Gn=tn.substr(13);Kr.focusScroll=Gn=="true"}else Zr==0?Kr.swapStyle=tn:b("Unknown modifier in hx-swap: "+tn)}}return Kr}function Sr(Qr){return ne(Qr,"hx-encoding")==="multipart/form-data"||h(Qr,"form")&&ee(Qr,"enctype")==="multipart/form-data"}function Er(Qr,Gr,Wr){var Kr=null;return R(Gr,function(Yr){Kr==null&&(Kr=Yr.encodeParameters(Qr,Wr,Gr))}),Kr??(Sr(Gr)?mr(Wr):pr(Wr))}function T(Qr){return{tasks:[],elts:[Qr]}}function Cr(Qr,Gr){var Wr=Qr[0],Kr=Qr[Qr.length-1];if(Gr.scroll){var Yr=null;Gr.scrollTarget&&(Yr=ue(Wr,Gr.scrollTarget)),Gr.scroll==="top"&&(Wr||Yr)&&(Yr=Yr||Wr,Yr.scrollTop=0),Gr.scroll==="bottom"&&(Kr||Yr)&&(Yr=Yr||Kr,Yr.scrollTop=Yr.scrollHeight)}if(Gr.show){var Yr=null;if(Gr.showTarget){var Zr=Gr.showTarget;Gr.showTarget==="window"&&(Zr="body"),Yr=ue(Wr,Zr)}Gr.show==="top"&&(Wr||Yr)&&(Yr=Yr||Wr,Yr.scrollIntoView({block:"start",behavior:Q.config.scrollBehavior})),Gr.show==="bottom"&&(Kr||Yr)&&(Yr=Yr||Kr,Yr.scrollIntoView({block:"end",behavior:Q.config.scrollBehavior}))}}function Rr(Qr,Gr,Wr,Kr){if(Kr==null&&(Kr={}),Qr==null)return Kr;var Yr=te(Qr,Gr);if(Yr){var Zr=Yr.trim(),tn=Wr;if(Zr==="unset")return null;Zr.indexOf("javascript:")===0?(Zr=Zr.substr(11),tn=!0):Zr.indexOf("js:")===0&&(Zr=Zr.substr(3),tn=!0),Zr.indexOf("{")!==0&&(Zr="{"+Zr+"}");var ln;tn?ln=Tr(Qr,function(){return Function("return ("+Zr+")")()},{}):ln=E(Zr);for(var dn in ln)ln.hasOwnProperty(dn)&&Kr[dn]==null&&(Kr[dn]=ln[dn])}return Rr(u(Qr),Gr,Wr,Kr)}function Tr(Qr,Gr,Wr){return Q.config.allowEval?Gr():(fe(Qr,"htmx:evalDisallowedError"),Wr)}function Or(Qr,Gr){return Rr(Qr,"hx-vars",!0,Gr)}function qr(Qr,Gr){return Rr(Qr,"hx-vals",!1,Gr)}function Hr(Qr){return le(Or(Qr),qr(Qr))}function Lr(Qr,Gr,Wr){if(Wr!==null)try{Qr.setRequestHeader(Gr,Wr)}catch{Qr.setRequestHeader(Gr,encodeURIComponent(Wr)),Qr.setRequestHeader(Gr+"-URI-AutoEncoded","true")}}function Ar(Qr){if(Qr.responseURL&&typeof URL<"u")try{var Gr=new URL(Qr.responseURL);return Gr.pathname+Gr.search}catch{fe(re().body,"htmx:badResponseUrl",{url:Qr.responseURL})}}function O(Qr,Gr){return Gr.test(Qr.getAllResponseHeaders())}function Nr(Qr,Gr,Wr){return Qr=Qr.toLowerCase(),Wr?Wr instanceof Element||I(Wr,"String")?he(Qr,Gr,null,null,{targetOverride:p(Wr),returnPromise:!0}):he(Qr,Gr,p(Wr.source),Wr.event,{handler:Wr.handler,headers:Wr.headers,values:Wr.values,targetOverride:p(Wr.target),swapOverride:Wr.swap,select:Wr.select,returnPromise:!0}):he(Qr,Gr,null,null,{returnPromise:!0})}function Ir(Qr){for(var Gr=[];Qr;)Gr.push(Qr),Qr=Qr.parentElement;return Gr}function kr(Qr,Gr,Wr){var Kr,Yr;if(typeof URL=="function"){Yr=new URL(Gr,document.location.href);var Zr=document.location.origin;Kr=Zr===Yr.origin}else Yr=Gr,Kr=g(Gr,document.location.origin);return Q.config.selfRequestsOnly&&!Kr?!1:ce(Qr,"htmx:validateUrl",le({url:Yr,sameHost:Kr},Wr))}function he(Qr,Gr,Wr,Kr,Yr,Zr){var tn=null,ln=null;if(Yr=Yr??{},Yr.returnPromise&&typeof Promise<"u")var dn=new Promise(function(gn,En){tn=gn,ln=En});Wr==null&&(Wr=re().body);var yn=Yr.handler||Mr,wn=Yr.select||null;if(!se(Wr))return ie(tn),dn;var kn=Yr.targetOverride||ye(Wr);if(kn==null||kn==pe)return fe(Wr,"htmx:targetError",{target:te(Wr,"hx-target")}),ie(ln),dn;var An=ae(Wr),Gn=An.lastButtonClicked;if(Gn){var jn=ee(Gn,"formaction");jn!=null&&(Gr=jn);var Vn=ee(Gn,"formmethod");Vn!=null&&Vn.toLowerCase()!=="dialog"&&(Qr=Vn)}var ti=ne(Wr,"hx-confirm");if(Zr===void 0){var Ti=function(gn){return he(Qr,Gr,Wr,Kr,Yr,!!gn)},fi={target:kn,elt:Wr,path:Gr,verb:Qr,triggeringEvent:Kr,etc:Yr,issueRequest:Ti,question:ti};if(ce(Wr,"htmx:confirm",fi)===!1)return ie(tn),dn}var oi=Wr,Jn=ne(Wr,"hx-sync"),mi=null,ai=!1;if(Jn){var Ui=Jn.split(":"),gi=Ui[0].trim();if(gi==="this"?oi=xe(Wr,"hx-sync"):oi=ue(Wr,gi),Jn=(Ui[1]||"drop").trim(),An=ae(oi),Jn==="drop"&&An.xhr&&An.abortable!==!0)return ie(tn),dn;if(Jn==="abort"){if(An.xhr)return ie(tn),dn;ai=!0}else if(Jn==="replace")ce(oi,"htmx:abort");else if(Jn.indexOf("queue")===0){var ri=Jn.split(" ");mi=(ri[1]||"last").trim()}}if(An.xhr)if(An.abortable)ce(oi,"htmx:abort");else{if(mi==null){if(Kr){var xn=ae(Kr);xn&&xn.triggerSpec&&xn.triggerSpec.queue&&(mi=xn.triggerSpec.queue)}mi==null&&(mi="last")}return An.queuedRequests==null&&(An.queuedRequests=[]),mi==="first"&&An.queuedRequests.length===0?An.queuedRequests.push(function(){he(Qr,Gr,Wr,Kr,Yr)}):mi==="all"?An.queuedRequests.push(function(){he(Qr,Gr,Wr,Kr,Yr)}):mi==="last"&&(An.queuedRequests=[],An.queuedRequests.push(function(){he(Qr,Gr,Wr,Kr,Yr)})),ie(tn),dn}var en=new XMLHttpRequest;An.xhr=en,An.abortable=ai;var an=function(){if(An.xhr=null,An.abortable=!1,An.queuedRequests!=null&&An.queuedRequests.length>0){var gn=An.queuedRequests.shift();gn()}},pn=ne(Wr,"hx-prompt");if(pn){var mn=prompt(pn);if(mn===null||!ce(Wr,"htmx:prompt",{prompt:mn,target:kn}))return ie(tn),an(),dn}if(ti&&!Zr&&!confirm(ti))return ie(tn),an(),dn;var Sn=xr(Wr,kn,mn);Qr!=="get"&&!Sr(Wr)&&(Sn["Content-Type"]="application/x-www-form-urlencoded"),Yr.headers&&(Sn=le(Sn,Yr.headers));var vn=dr(Wr,Qr),bn=vn.errors,hn=vn.values;Yr.values&&(hn=le(hn,Yr.values));var On=Hr(Wr),Pn=le(hn,On),Nn=yr(Pn,Wr);Q.config.getCacheBusterParam&&Qr==="get"&&(Nn["org.htmx.cache-buster"]=ee(kn,"id")||"true"),(Gr==null||Gr==="")&&(Gr=re().location.href);var Dn=Rr(Wr,"hx-request"),Ln=ae(Wr).boosted,Fn=Q.config.methodsThatUseUrlParams.indexOf(Qr)>=0,Mn={boosted:Ln,useUrlParams:Fn,parameters:Nn,unfilteredParameters:Pn,headers:Sn,target:kn,verb:Qr,errors:bn,withCredentials:Yr.credentials||Dn.credentials||Q.config.withCredentials,timeout:Yr.timeout||Dn.timeout||Q.config.timeout,path:Gr,triggeringEvent:Kr};if(!ce(Wr,"htmx:configRequest",Mn))return ie(tn),an(),dn;if(Gr=Mn.path,Qr=Mn.verb,Sn=Mn.headers,Nn=Mn.parameters,bn=Mn.errors,Fn=Mn.useUrlParams,bn&&bn.length>0)return ce(Wr,"htmx:validation:halted",Mn),ie(tn),an(),dn;var Hn=Gr.split("#"),Wn=Hn[0],zn=Hn[1],nn=Gr;if(Fn){nn=Wn;var on=Object.keys(Nn).length!==0;on&&(nn.indexOf("?")<0?nn+="?":nn+="&",nn+=pr(Nn),zn&&(nn+="#"+zn))}if(!kr(Wr,nn,Mn))return fe(Wr,"htmx:invalidPath",Mn),ie(ln),dn;if(en.open(Qr.toUpperCase(),nn,!0),en.overrideMimeType("text/html"),en.withCredentials=Mn.withCredentials,en.timeout=Mn.timeout,!Dn.noHeaders){for(var Jr in Sn)if(Sn.hasOwnProperty(Jr)){var rn=Sn[Jr];Lr(en,Jr,rn)}}var cn={xhr:en,target:kn,requestConfig:Mn,etc:Yr,boosted:Ln,select:wn,pathInfo:{requestPath:Gr,finalRequestPath:nn,anchor:zn}};if(en.onload=function(){try{var gn=Ir(Wr);if(cn.pathInfo.responsePath=Ar(en),yn(Wr,cn),lr(sn,fn),ce(Wr,"htmx:afterRequest",cn),ce(Wr,"htmx:afterOnLoad",cn),!se(Wr)){for(var En=null;gn.length>0&&En==null;){var Tn=gn.shift();se(Tn)&&(En=Tn)}En&&(ce(En,"htmx:afterRequest",cn),ce(En,"htmx:afterOnLoad",cn))}ie(tn),an()}catch(Rn){throw fe(Wr,"htmx:onLoadError",le({error:Rn},cn)),Rn}},en.onerror=function(){lr(sn,fn),fe(Wr,"htmx:afterRequest",cn),fe(Wr,"htmx:sendError",cn),ie(ln),an()},en.onabort=function(){lr(sn,fn),fe(Wr,"htmx:afterRequest",cn),fe(Wr,"htmx:sendAbort",cn),ie(ln),an()},en.ontimeout=function(){lr(sn,fn),fe(Wr,"htmx:afterRequest",cn),fe(Wr,"htmx:timeout",cn),ie(ln),an()},!ce(Wr,"htmx:beforeRequest",cn))return ie(tn),an(),dn;var sn=or(Wr),fn=sr(Wr);oe(["loadstart","loadend","progress","abort"],function(gn){oe([en,en.upload],function(En){En.addEventListener(gn,function(Tn){ce(Wr,"htmx:xhr:"+gn,{lengthComputable:Tn.lengthComputable,loaded:Tn.loaded,total:Tn.total})})})}),ce(Wr,"htmx:beforeSend",cn);var un=Fn?null:Er(en,Wr,Nn);return en.send(un),dn}function Pr(Qr,Gr){var Wr=Gr.xhr,Kr=null,Yr=null;if(O(Wr,/HX-Push:/i)?(Kr=Wr.getResponseHeader("HX-Push"),Yr="push"):O(Wr,/HX-Push-Url:/i)?(Kr=Wr.getResponseHeader("HX-Push-Url"),Yr="push"):O(Wr,/HX-Replace-Url:/i)&&(Kr=Wr.getResponseHeader("HX-Replace-Url"),Yr="replace"),Kr)return Kr==="false"?{}:{type:Yr,path:Kr};var Zr=Gr.pathInfo.finalRequestPath,tn=Gr.pathInfo.responsePath,ln=ne(Qr,"hx-push-url"),dn=ne(Qr,"hx-replace-url"),yn=ae(Qr).boosted,wn=null,kn=null;return ln?(wn="push",kn=ln):dn?(wn="replace",kn=dn):yn&&(wn="push",kn=tn||Zr),kn?kn==="false"?{}:(kn==="true"&&(kn=tn||Zr),Gr.pathInfo.anchor&&kn.indexOf("#")===-1&&(kn=kn+"#"+Gr.pathInfo.anchor),{type:wn,path:kn}):{}}function Mr(Qr,Gr){var Wr=Gr.xhr,Kr=Gr.target,Yr=Gr.etc,Zr=Gr.requestConfig,tn=Gr.select;if(ce(Qr,"htmx:beforeOnLoad",Gr)){if(O(Wr,/HX-Trigger:/i)&&_e(Wr,"HX-Trigger",Qr),O(Wr,/HX-Location:/i)){er();var ln=Wr.getResponseHeader("HX-Location"),dn;ln.indexOf("{")===0&&(dn=E(ln),ln=dn.path,delete dn.path),Nr("GET",ln,dn).then(function(){tr(ln)});return}var yn=O(Wr,/HX-Refresh:/i)&&Wr.getResponseHeader("HX-Refresh")==="true";if(O(Wr,/HX-Redirect:/i)){location.href=Wr.getResponseHeader("HX-Redirect"),yn&&location.reload();return}if(yn){location.reload();return}O(Wr,/HX-Retarget:/i)&&(Wr.getResponseHeader("HX-Retarget")==="this"?Gr.target=Qr:Gr.target=ue(Qr,Wr.getResponseHeader("HX-Retarget")));var wn=Pr(Qr,Gr),kn=Wr.status>=200&&Wr.status<400&&Wr.status!==204,An=Wr.response,Gn=Wr.status>=400,jn=Q.config.ignoreTitle,Vn=le({shouldSwap:kn,serverResponse:An,isError:Gn,ignoreTitle:jn},Gr);if(ce(Kr,"htmx:beforeSwap",Vn)){if(Kr=Vn.target,An=Vn.serverResponse,Gn=Vn.isError,jn=Vn.ignoreTitle,Gr.target=Kr,Gr.failed=Gn,Gr.successful=!Gn,Vn.shouldSwap){Wr.status===286&&at(Qr),R(Qr,function(gi){An=gi.transformResponse(An,Wr,Qr)}),wn.type&&er();var ti=Yr.swapOverride;O(Wr,/HX-Reswap:/i)&&(ti=Wr.getResponseHeader("HX-Reswap"));var dn=wr(Qr,ti);dn.hasOwnProperty("ignoreTitle")&&(jn=dn.ignoreTitle),Kr.classList.add(Q.config.swappingClass);var Ti=null,fi=null,oi=function(){try{var gi=document.activeElement,ri={};try{ri={elt:gi,start:gi?gi.selectionStart:null,end:gi?gi.selectionEnd:null}}catch{}var xn;tn&&(xn=tn),O(Wr,/HX-Reselect:/i)&&(xn=Wr.getResponseHeader("HX-Reselect")),wn.type&&(ce(re().body,"htmx:beforeHistoryUpdate",le({history:wn},Gr)),wn.type==="push"?(tr(wn.path),ce(re().body,"htmx:pushedIntoHistory",{path:wn.path})):(rr(wn.path),ce(re().body,"htmx:replacedInHistory",{path:wn.path})));var en=T(Kr);if(je(dn.swapStyle,Kr,Qr,An,en,xn),ri.elt&&!se(ri.elt)&&ee(ri.elt,"id")){var an=document.getElementById(ee(ri.elt,"id")),pn={preventScroll:dn.focusScroll!==void 0?!dn.focusScroll:!Q.config.defaultFocusScroll};if(an){if(ri.start&&an.setSelectionRange)try{an.setSelectionRange(ri.start,ri.end)}catch{}an.focus(pn)}}if(Kr.classList.remove(Q.config.swappingClass),oe(en.elts,function(vn){vn.classList&&vn.classList.add(Q.config.settlingClass),ce(vn,"htmx:afterSwap",Gr)}),O(Wr,/HX-Trigger-After-Swap:/i)){var mn=Qr;se(Qr)||(mn=re().body),_e(Wr,"HX-Trigger-After-Swap",mn)}var Sn=function(){if(oe(en.tasks,function(On){On.call()}),oe(en.elts,function(On){On.classList&&On.classList.remove(Q.config.settlingClass),ce(On,"htmx:afterSettle",Gr)}),Gr.pathInfo.anchor){var vn=re().getElementById(Gr.pathInfo.anchor);vn&&vn.scrollIntoView({block:"start",behavior:"auto"})}if(en.title&&!jn){var bn=C("title");bn?bn.innerHTML=en.title:window.document.title=en.title}if(Cr(en.elts,dn),O(Wr,/HX-Trigger-After-Settle:/i)){var hn=Qr;se(Qr)||(hn=re().body),_e(Wr,"HX-Trigger-After-Settle",hn)}ie(Ti)};dn.settleDelay>0?setTimeout(Sn,dn.settleDelay):Sn()}catch(vn){throw fe(Qr,"htmx:swapError",Gr),ie(fi),vn}},Jn=Q.config.globalViewTransitions;if(dn.hasOwnProperty("transition")&&(Jn=dn.transition),Jn&&ce(Qr,"htmx:beforeTransition",Gr)&&typeof Promise<"u"&&document.startViewTransition){var mi=new Promise(function(gi,ri){Ti=gi,fi=ri}),ai=oi;oi=function(){document.startViewTransition(function(){return ai(),mi})}}dn.swapDelay>0?setTimeout(oi,dn.swapDelay):oi()}Gn&&fe(Qr,"htmx:responseError",le({error:"Response Status Error Code "+Wr.status+" from "+Gr.pathInfo.requestPath},Gr))}}}var Xr={};function Dr(){return{init:function(Qr){return null},onEvent:function(Qr,Gr){return!0},transformResponse:function(Qr,Gr,Wr){return Qr},isInlineSwap:function(Qr){return!1},handleSwap:function(Qr,Gr,Wr,Kr){return!1},encodeParameters:function(Qr,Gr,Wr){return null}}}function Ur(Qr,Gr){Gr.init&&Gr.init(r),Xr[Qr]=le(Dr(),Gr)}function Br(Qr){delete Xr[Qr]}function Fr(Qr,Gr,Wr){if(Qr==null)return Gr;Gr==null&&(Gr=[]),Wr==null&&(Wr=[]);var Kr=te(Qr,"hx-ext");return Kr&&oe(Kr.split(","),function(Yr){if(Yr=Yr.replace(/ /g,""),Yr.slice(0,7)=="ignore:"){Wr.push(Yr.slice(7));return}if(Wr.indexOf(Yr)<0){var Zr=Xr[Yr];Zr&&Gr.indexOf(Zr)<0&&Gr.push(Zr)}}),Fr(u(Qr),Gr,Wr)}var Vr=!1;re().addEventListener("DOMContentLoaded",function(){Vr=!0});function jr(Qr){Vr||re().readyState==="complete"?Qr():re().addEventListener("DOMContentLoaded",Qr)}function _r(){Q.config.includeIndicatorStyles!==!1&&re().head.insertAdjacentHTML("beforeend","<style> ."+Q.config.indicatorClass+"{opacity:0} ."+Q.config.requestClass+" ."+Q.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} ."+Q.config.requestClass+"."+Q.config.indicatorClass+"{opacity:1; transition: opacity 200ms ease-in;} </style>")}function zr(){var Qr=re().querySelector('meta[name="htmx-config"]');return Qr?E(Qr.content):null}function $r(){var Qr=zr();Qr&&(Q.config=le(Q.config,Qr))}return jr(function(){$r(),_r();var Qr=re().body;zt(Qr);var Gr=re().querySelectorAll("[hx-trigger='restored'],[data-hx-trigger='restored']");Qr.addEventListener("htmx:abort",function(Kr){var Yr=Kr.target,Zr=ae(Yr);Zr&&Zr.xhr&&Zr.xhr.abort()});let Wr=window.onpopstate?window.onpopstate.bind(window):null;window.onpopstate=function(Kr){Kr.state&&Kr.state.htmx?(ar(),oe(Gr,function(Yr){ce(Yr,"htmx:restored",{document:re(),triggerEvent:ce})})):Wr&&Wr(Kr)},setTimeout(function(){ce(Qr,"htmx:load",{}),Qr=null},0)}),Q}()})});var Ys=Qs((go,Ks)=>{(function(Qr,Gr){let Wr=Gr(Qr);typeof go=="object"&&typeof go.nodeName!="string"?Ks.exports=Wr:(Qr._hyperscript=Wr,"document"in Qr&&Qr._hyperscript.browserInit())})(typeof self<"u"?self:go,Qr=>{"use strict";let Gr={dynamicResolvers:[function(xn,en){if(xn==="Fixed")return Number(en).toFixed();if(xn.indexOf("Fixed:")===0){let an=xn.split(":")[1];return Number(en).toFixed(parseInt(an))}}],String:function(xn){return xn.toString?xn.toString():""+xn},Int:function(xn){return parseInt(xn)},Float:function(xn){return parseFloat(xn)},Number:function(xn){return Number(xn)},Date:function(xn){return new Date(xn)},Array:function(xn){return Array.from(xn)},JSON:function(xn){return JSON.stringify(xn)},Object:function(xn){return xn instanceof String&&(xn=xn.toString()),typeof xn=="string"?JSON.parse(xn):Object.assign({},xn)}},Wr={attributes:"_, script, data-script",defaultTransition:"all 500ms ease-in",disableSelector:"[disable-scripting], [data-disable-scripting]",hideShowStrategies:{},conversions:Gr};class Kr{static OP_TABLE={"+":"PLUS","-":"MINUS","*":"MULTIPLY","/":"DIVIDE",".":"PERIOD","..":"ELLIPSIS","\\":"BACKSLASH",":":"COLON","%":"PERCENT","|":"PIPE","!":"EXCLAMATION","?":"QUESTION","#":"POUND","&":"AMPERSAND",$:"DOLLAR",";":"SEMI",",":"COMMA","(":"L_PAREN",")":"R_PAREN","<":"L_ANG",">":"R_ANG","<=":"LTE_ANG",">=":"GTE_ANG","==":"EQ","===":"EQQ","!=":"NEQ","!==":"NEQQ","{":"L_BRACE","}":"R_BRACE","[":"L_BRACKET","]":"R_BRACKET","=":"EQUALS","~":"TILDE"};static isValidCSSClassChar(en){return Kr.isAlpha(en)||Kr.isNumeric(en)||en==="-"||en==="_"||en===":"}static isValidCSSIDChar(en){return Kr.isAlpha(en)||Kr.isNumeric(en)||en==="-"||en==="_"||en===":"}static isWhitespace(en){return en===" "||en===" "||Kr.isNewline(en)}static positionString(en){return"[Line: "+en.line+", Column: "+en.column+"]"}static isNewline(en){return en==="\r"||en===` 3 + `}static isNumeric(en){return en>="0"&&en<="9"}static isAlpha(en){return en>="a"&&en<="z"||en>="A"&&en<="Z"}static isIdentifierChar(en,an){return en==="_"||en==="$"}static isReservedChar(en){return en==="`"||en==="^"}static isValidSingleQuoteStringStart(en){if(en.length>0){var an=en[en.length-1];if(an.type==="IDENTIFIER"||an.type==="CLASS_REF"||an.type==="ID_REF"||an.op&&(an.value===">"||an.value===")"))return!1}return!0}static tokenize(en,an){var pn=[],mn=en,Sn=0,vn=0,bn=1,hn="<START>",On=0;function Pn(){return an&&On===0}for(;Sn<mn.length;)if(un()==="-"&&gn()==="-"&&(Kr.isWhitespace(En(2))||En(2)===""||En(2)==="-")||un()==="/"&&gn()==="/"&&(Kr.isWhitespace(En(2))||En(2)===""||En(2)==="/"))Ln();else if(un()==="/"&&gn()==="*"&&(Kr.isWhitespace(En(2))||En(2)===""||En(2)==="*"))Fn();else if(Kr.isWhitespace(un()))pn.push(Bn());else if(!Rn()&&un()==="."&&(Kr.isAlpha(gn())||gn()==="{"||gn()==="-"))pn.push(Mn());else if(!Rn()&&un()==="#"&&(Kr.isAlpha(gn())||gn()==="{"))pn.push(nn());else if(un()==="["&&gn()==="@")pn.push(Hn());else if(un()==="@")pn.push(Wn());else if(un()==="*"&&Kr.isAlpha(gn()))pn.push(zn());else if(Pn()&&(Kr.isAlpha(un())||un()==="\\"))pn.push(on());else if(!Pn()&&(Kr.isAlpha(un())||Kr.isIdentifierChar(un())))pn.push(Jr());else if(Kr.isNumeric(un()))pn.push(rn());else if(!Pn()&&(un()==='"'||un()==="`"))pn.push(sn());else if(!Pn()&&un()==="'")Kr.isValidSingleQuoteStringStart(pn)?pn.push(sn()):pn.push(cn());else if(Kr.OP_TABLE[un()])hn==="$"&&un()==="{"&&On++,un()==="}"&&On--,pn.push(cn());else if(Pn()||Kr.isReservedChar(un()))pn.push(Dn("RESERVED",Tn()));else if(Sn<mn.length)throw Error("Unknown token: "+un()+" ");return new Yr(pn,[],mn);function Nn(Cn,In){var _n=Dn(Cn,In);return _n.op=!0,_n}function Dn(Cn,In){return{type:Cn,value:In||"",start:Sn,end:Sn+1,column:vn,line:bn}}function Ln(){for(;un()&&!Kr.isNewline(un());)Tn();Tn()}function Fn(){for(;un()&&!(un()==="*"&&gn()==="/");)Tn();Tn(),Tn()}function Mn(){var Cn=Dn("CLASS_REF"),In=Tn();if(un()==="{"){for(Cn.template=!0,In+=Tn();un()&&un()!=="}";)In+=Tn();if(un()!=="}")throw Error("Unterminated class reference");In+=Tn()}else for(;Kr.isValidCSSClassChar(un())||un()==="\\";)un()==="\\"&&Tn(),In+=Tn();return Cn.value=In,Cn.end=Sn,Cn}function Hn(){for(var Cn=Dn("ATTRIBUTE_REF"),In=Tn();Sn<mn.length&&un()!=="]";)In+=Tn();return un()==="]"&&(In+=Tn()),Cn.value=In,Cn.end=Sn,Cn}function Wn(){for(var Cn=Dn("ATTRIBUTE_REF"),In=Tn();Kr.isValidCSSIDChar(un());)In+=Tn();if(un()==="="){if(In+=Tn(),un()==='"'||un()==="'"){let _n=sn();In+=_n.value}else if(Kr.isAlpha(un())||Kr.isNumeric(un())||Kr.isIdentifierChar(un())){let _n=Jr();In+=_n.value}}return Cn.value=In,Cn.end=Sn,Cn}function zn(){for(var Cn=Dn("STYLE_REF"),In=Tn();Kr.isAlpha(un())||un()==="-";)In+=Tn();return Cn.value=In,Cn.end=Sn,Cn}function nn(){var Cn=Dn("ID_REF"),In=Tn();if(un()==="{"){for(Cn.template=!0,In+=Tn();un()&&un()!=="}";)In+=Tn();if(un()!=="}")throw Error("Unterminated id reference");Tn()}else for(;Kr.isValidCSSIDChar(un());)In+=Tn();return Cn.value=In,Cn.end=Sn,Cn}function on(){var Cn=Dn("IDENTIFIER"),In=Tn(),_n=In==="\\";for(_n&&(In="");(Kr.isAlpha(un())||Kr.isNumeric(un())||Kr.isIdentifierChar(un())||un()==="\\"||un()==="{"||un()==="}")&&!(un()==="$"&&_n===!1);)un()==="\\"?(_n=!0,Tn()):(_n=!1,In+=Tn());return un()==="!"&&In==="beep"&&(In+=Tn()),Cn.value=In,Cn.end=Sn,Cn}function Jr(){for(var Cn=Dn("IDENTIFIER"),In=Tn();Kr.isAlpha(un())||Kr.isNumeric(un())||Kr.isIdentifierChar(un());)In+=Tn();return un()==="!"&&In==="beep"&&(In+=Tn()),Cn.value=In,Cn.end=Sn,Cn}function rn(){for(var Cn=Dn("NUMBER"),In=Tn();Kr.isNumeric(un());)In+=Tn();for(un()==="."&&Kr.isNumeric(gn())&&(In+=Tn());Kr.isNumeric(un());)In+=Tn();for((un()==="e"||un()==="E")&&(Kr.isNumeric(gn())?In+=Tn():gn()==="-"&&(In+=Tn(),In+=Tn()));Kr.isNumeric(un());)In+=Tn();return Cn.value=In,Cn.end=Sn,Cn}function cn(){for(var Cn=Nn(),In=Tn();un()&&Kr.OP_TABLE[In+un()];)In+=Tn();return Cn.type=Kr.OP_TABLE[In],Cn.value=In,Cn.end=Sn,Cn}function sn(){var Cn=Dn("STRING"),In=Tn();Cn.template=In==="`";for(var _n="";un()&&un()!==In;)if(un()==="\\"){Tn();let Un=Tn();if(Un==="b")_n+="\b";else if(Un==="f")_n+="\f";else if(Un==="n")_n+=` 4 + `;else if(Un==="r")_n+="\r";else if(Un==="t")_n+=" ";else if(Un==="v")_n+="\v";else if(Cn.template&&Un==="$")_n+="\\$";else if(Un==="x"){let ni=fn();if(Number.isNaN(ni))throw Error("Invalid hexadecimal escape at "+Kr.positionString(Cn));_n+=String.fromCharCode(ni)}else _n+=Un}else _n+=Tn();if(un()!==In)throw Error("Unterminated string at "+Kr.positionString(Cn));return Tn(),Cn.value=_n,Cn.end=Sn,Cn}function fn(){if(!un())return NaN;let In=16*Number.parseInt(Tn(),16);return un()?(In+=Number.parseInt(Tn(),16),In):NaN}function un(){return mn.charAt(Sn)}function gn(){return mn.charAt(Sn+1)}function En(Cn=1){return mn.charAt(Sn+Cn)}function Tn(){return hn=un(),Sn++,vn++,hn}function Rn(){return Kr.isAlpha(hn)||Kr.isNumeric(hn)||hn===")"||hn==='"'||hn==="'"||hn==="`"||hn==="}"||hn==="]"}function Bn(){for(var Cn=Dn("WHITESPACE"),In="";un()&&Kr.isWhitespace(un());)Kr.isNewline(un())&&(vn=0,bn++),In+=Tn();return Cn.value=In,Cn.end=Sn,Cn}}tokenize(en,an){return Kr.tokenize(en,an)}}class Yr{constructor(en,an,pn){this.tokens=en,this.consumed=an,this.source=pn,this.consumeWhitespace()}get list(){return this.tokens}_lastConsumed=null;consumeWhitespace(){for(;this.token(0,!0).type==="WHITESPACE";)this.consumed.push(this.tokens.shift())}raiseError(en,an){Zr.raiseParseError(en,an)}requireOpToken(en){var an=this.matchOpToken(en);if(an)return an;this.raiseError(this,"Expected '"+en+"' but found '"+this.currentToken().value+"'")}matchAnyOpToken(en,an,pn){for(var mn=0;mn<arguments.length;mn++){var Sn=arguments[mn],vn=this.matchOpToken(Sn);if(vn)return vn}}matchAnyToken(en,an,pn){for(var mn=0;mn<arguments.length;mn++){var Sn=arguments[mn],vn=this.matchToken(Sn);if(vn)return vn}}matchOpToken(en){if(this.currentToken()&&this.currentToken().op&&this.currentToken().value===en)return this.consumeToken()}requireTokenType(en,an,pn,mn){var Sn=this.matchTokenType(en,an,pn,mn);if(Sn)return Sn;this.raiseError(this,"Expected one of "+JSON.stringify([en,an,pn]))}matchTokenType(en,an,pn,mn){if(this.currentToken()&&this.currentToken().type&&[en,an,pn,mn].indexOf(this.currentToken().type)>=0)return this.consumeToken()}requireToken(en,an){var pn=this.matchToken(en,an);if(pn)return pn;this.raiseError(this,"Expected '"+en+"' but found '"+this.currentToken().value+"'")}peekToken(en,an,pn){if(an=an||0,pn=pn||"IDENTIFIER",this.tokens[an]&&this.tokens[an].value===en&&this.tokens[an].type===pn)return this.tokens[an]}matchToken(en,an){if(this.follows.indexOf(en)===-1&&(an=an||"IDENTIFIER",this.currentToken()&&this.currentToken().value===en&&this.currentToken().type===an))return this.consumeToken()}consumeToken(){var en=this.tokens.shift();return this.consumed.push(en),this._lastConsumed=en,this.consumeWhitespace(),en}consumeUntil(en,an){for(var pn=[],mn=this.token(0,!0);(an==null||mn.type!==an)&&(en==null||mn.value!==en)&&mn.type!=="EOF";){var Sn=this.tokens.shift();this.consumed.push(Sn),pn.push(mn),mn=this.token(0,!0)}return this.consumeWhitespace(),pn}lastWhitespace(){return this.consumed[this.consumed.length-1]&&this.consumed[this.consumed.length-1].type==="WHITESPACE"?this.consumed[this.consumed.length-1].value:""}consumeUntilWhitespace(){return this.consumeUntil(null,"WHITESPACE")}hasMore(){return this.tokens.length>0}token(en,an){var pn,mn=0;do{if(!an)for(;this.tokens[mn]&&this.tokens[mn].type==="WHITESPACE";)mn++;pn=this.tokens[mn],en--,mn++}while(en>-1);return pn||{type:"EOF",value:"<<<EOF>>>"}}currentToken(){return this.token(0)}lastMatch(){return this._lastConsumed}static sourceFor=function(){return this.programSource.substring(this.startToken.start,this.endToken.end)};static lineFor=function(){return this.programSource.split(` 5 + `)[this.startToken.line-1]};follows=[];pushFollow(en){this.follows.push(en)}popFollow(){this.follows.pop()}clearFollows(){var en=this.follows;return this.follows=[],en}restoreFollows(en){this.follows=en}}class Zr{constructor(en){this.runtime=en,this.possessivesDisabled=!1,this.addGrammarElement("feature",function(an,pn,mn){if(mn.matchOpToken("(")){var Sn=an.requireElement("feature",mn);return mn.requireOpToken(")"),Sn}var vn=an.FEATURES[mn.currentToken().value||""];if(vn)return vn(an,pn,mn)}),this.addGrammarElement("command",function(an,pn,mn){if(mn.matchOpToken("(")){let bn=an.requireElement("command",mn);return mn.requireOpToken(")"),bn}var Sn=an.COMMANDS[mn.currentToken().value||""];let vn;return Sn?vn=Sn(an,pn,mn):mn.currentToken().type==="IDENTIFIER"&&(vn=an.parseElement("pseudoCommand",mn)),vn&&an.parseElement("indirectStatement",mn,vn)}),this.addGrammarElement("commandList",function(an,pn,mn){if(mn.hasMore()){var Sn=an.parseElement("command",mn);if(Sn){mn.matchToken("then");let vn=an.parseElement("commandList",mn);return vn&&(Sn.next=vn),Sn}}return{type:"emptyCommandListCommand",op:function(vn){return pn.findNext(this,vn)},execute:function(vn){return pn.unifiedExec(this,vn)}}}),this.addGrammarElement("leaf",function(an,pn,mn){var Sn=an.parseAnyOf(an.LEAF_EXPRESSIONS,mn);return Sn??an.parseElement("symbol",mn)}),this.addGrammarElement("indirectExpression",function(an,pn,mn,Sn){for(var vn=0;vn<an.INDIRECT_EXPRESSIONS.length;vn++){var bn=an.INDIRECT_EXPRESSIONS[vn];Sn.endToken=mn.lastMatch();var hn=an.parseElement(bn,mn,Sn);if(hn)return hn}return Sn}),this.addGrammarElement("indirectStatement",function(an,pn,mn,Sn){if(mn.matchToken("unless")){Sn.endToken=mn.lastMatch();var vn=an.requireElement("expression",mn),bn={type:"unlessStatementModifier",args:[vn],op:function(hn,On){return On?this.next:Sn},execute:function(hn){return pn.unifiedExec(this,hn)}};return Sn.parent=bn,bn}return Sn}),this.addGrammarElement("primaryExpression",function(an,pn,mn){var Sn=an.parseElement("leaf",mn);if(Sn)return an.parseElement("indirectExpression",mn,Sn);an.raiseParseError(mn,"Unexpected value: "+mn.currentToken().value)})}use(en){return en(this),this}GRAMMAR={};COMMANDS={};FEATURES={};LEAF_EXPRESSIONS=[];INDIRECT_EXPRESSIONS=[];initElt(en,an,pn){en.startToken=an,en.sourceFor=Yr.sourceFor,en.lineFor=Yr.lineFor,en.programSource=pn.source}parseElement(en,an,pn=void 0){var mn=this.GRAMMAR[en];if(mn){var Sn=an.currentToken(),vn=mn(this,this.runtime,an,pn);if(vn){this.initElt(vn,Sn,an),vn.endToken=vn.endToken||an.lastMatch();for(var pn=vn.root;pn!=null;)this.initElt(pn,Sn,an),pn=pn.root}return vn}}requireElement(en,an,pn,mn){var Sn=this.parseElement(en,an,mn);return Sn||Zr.raiseParseError(an,pn||"Expected "+en),Sn}parseAnyOf(en,an){for(var pn=0;pn<en.length;pn++){var mn=en[pn],Sn=this.parseElement(mn,an);if(Sn)return Sn}}addGrammarElement(en,an){this.GRAMMAR[en]=an}addCommand(en,an){var pn=en+"Command",mn=function(Sn,vn,bn){let hn=an(Sn,vn,bn);if(hn)return hn.type=pn,hn.execute=function(On){return On.meta.command=hn,vn.unifiedExec(this,On)},hn};this.GRAMMAR[pn]=mn,this.COMMANDS[en]=mn}addFeature(en,an){var pn=en+"Feature",mn=function(Sn,vn,bn){var hn=an(Sn,vn,bn);if(hn)return hn.isFeature=!0,hn.keyword=en,hn.type=pn,hn};this.GRAMMAR[pn]=mn,this.FEATURES[en]=mn}addLeafExpression(en,an){this.LEAF_EXPRESSIONS.push(en),this.addGrammarElement(en,an)}addIndirectExpression(en,an){this.INDIRECT_EXPRESSIONS.push(en),this.addGrammarElement(en,an)}static createParserContext(en){var an=en.currentToken(),pn=en.source,mn=pn.split(` 6 + `),Sn=an&&an.line?an.line-1:mn.length-1,vn=mn[Sn],bn=an&&an.line?an.column:vn.length-1;return vn+` 7 + `+" ".repeat(bn)+`^^ 8 + 9 + `}static raiseParseError(en,an){an=(an||"Unexpected Token : "+en.currentToken().value)+` 10 + 11 + `+Zr.createParserContext(en);var pn=new Error(an);throw pn.tokens=en,pn}raiseParseError(en,an){Zr.raiseParseError(en,an)}parseHyperScript(en){var an=this.parseElement("hyperscript",en);if(en.hasMore()&&this.raiseParseError(en),an)return an}setParent(en,an){typeof en=="object"&&(en.parent=an,typeof an=="object"&&(an.children=an.children||new Set,an.children.add(en)),this.setParent(en.next,an))}commandStart(en){return this.COMMANDS[en.value||""]}featureStart(en){return this.FEATURES[en.value||""]}commandBoundary(en){return!!(en.value=="end"||en.value=="then"||en.value=="else"||en.value=="otherwise"||en.value==")"||this.commandStart(en)||this.featureStart(en)||en.type=="EOF")}parseStringTemplate(en){var an=[""];do if(an.push(en.lastWhitespace()),en.currentToken().value==="$"){en.consumeToken();var pn=en.matchOpToken("{");an.push(this.requireElement("expression",en)),pn&&en.requireOpToken("}"),an.push("")}else if(en.currentToken().value==="\\")en.consumeToken(),en.consumeToken();else{var mn=en.consumeToken();an[an.length-1]+=mn?mn.value:""}while(en.hasMore());return an.push(en.lastWhitespace()),an}ensureTerminated(en){let an=this.runtime;for(var pn={type:"implicitReturn",op:function(Sn){return Sn.meta.returned=!0,Sn.meta.resolve&&Sn.meta.resolve(),an.HALT},execute:function(Sn){}},mn=en;mn.next;)mn=mn.next;mn.next=pn}}class tn{constructor(en,an){this.lexer=en??new Kr,this.parser=an??new Zr(this).use(fi).use(oi),this.parser.runtime=this}matchesSelector(en,an){var pn=en.matches||en.matchesSelector||en.msMatchesSelector||en.mozMatchesSelector||en.webkitMatchesSelector||en.oMatchesSelector;return pn&&pn.call(en,an)}makeEvent(en,an){var pn;return Qr.Event&&typeof Qr.Event=="function"?(pn=new Event(en,{bubbles:!0,cancelable:!0,composed:!0}),pn.detail=an):(pn=document.createEvent("CustomEvent"),pn.initCustomEvent(en,!0,!0,an)),pn}triggerEvent(en,an,pn,mn){pn=pn||{},pn.sender=mn;var Sn=this.makeEvent(an,pn),vn=en.dispatchEvent(Sn);return vn}isArrayLike(en){return Array.isArray(en)||typeof NodeList<"u"&&(en instanceof NodeList||en instanceof HTMLCollection||en instanceof FileList)}isIterable(en){return typeof en=="object"&&Symbol.iterator in en&&typeof en[Symbol.iterator]=="function"}shouldAutoIterate(en){return en!=null&&en[Gn]||this.isArrayLike(en)}forEach(en,an){if(en!=null)if(this.isIterable(en))for(let mn of en)an(mn);else if(this.isArrayLike(en))for(var pn=0;pn<en.length;pn++)an(en[pn]);else an(en)}implicitLoop(en,an){if(this.shouldAutoIterate(en))for(let pn of en)an(pn);else an(en)}wrapArrays(en){for(var an=[],pn=0;pn<en.length;pn++){var mn=en[pn];Array.isArray(mn)?an.push(Promise.all(mn)):an.push(mn)}return an}unwrapAsyncs(en){for(var an=0;an<en.length;an++){var pn=en[an];if(pn.asyncWrapper&&(en[an]=pn.value),Array.isArray(pn))for(var mn=0;mn<pn.length;mn++){var Sn=pn[mn];Sn.asyncWrapper&&(pn[mn]=Sn.value)}}}static HALT={};HALT=tn.HALT;unifiedExec(en,an){for(;;){try{var pn=this.unifiedEval(en,an)}catch(mn){if(an.meta.handlingFinally)console.error(" Exception in finally block: ",mn),pn=tn.HALT;else if(this.registerHyperTrace(an,mn),an.meta.errorHandler&&!an.meta.handlingError){an.meta.handlingError=!0,an.locals[an.meta.errorSymbol]=mn,en=an.meta.errorHandler;continue}else an.meta.currentException=mn,pn=tn.HALT}if(pn==null){console.error(en," did not return a next element to execute! context: ",an);return}else if(pn.then){pn.then(mn=>{this.unifiedExec(mn,an)}).catch(mn=>{this.unifiedExec({op:function(){throw mn}},an)});return}else if(pn===tn.HALT)if(an.meta.finallyHandler&&!an.meta.handlingFinally)an.meta.handlingFinally=!0,en=an.meta.finallyHandler;else if(an.meta.onHalt&&an.meta.onHalt(),an.meta.currentException)if(an.meta.reject){an.meta.reject(an.meta.currentException);return}else throw an.meta.currentException;else return;else en=pn}}unifiedEval(en,an,pn){var mn=[an],Sn=!1,vn=!1;if(en.args)for(var bn=0;bn<en.args.length;bn++){var hn=en.args[bn];if(hn==null)mn.push(null);else if(Array.isArray(hn)){for(var On=[],Pn=0;Pn<hn.length;Pn++){var Nn=hn[Pn],Dn=Nn?Nn.evaluate(an):null;Dn&&(Dn.then?Sn=!0:Dn.asyncWrapper&&(vn=!0)),On.push(Dn)}mn.push(On)}else if(hn.evaluate){var Dn=hn.evaluate(an);if(Dn&&(Dn.then?Sn=!0:Dn.asyncWrapper&&(vn=!0)),mn.push(Dn),Dn){if(pn===!0)break}else if(pn===!1)break}else mn.push(hn)}return Sn?new Promise((Ln,Fn)=>{mn=this.wrapArrays(mn),Promise.all(mn).then(function(Mn){vn&&this.unwrapAsyncs(Mn);try{var Hn=en.op.apply(en,Mn);Ln(Hn)}catch(Wn){Fn(Wn)}}).catch(function(Mn){Fn(Mn)})}):(vn&&this.unwrapAsyncs(mn),en.op.apply(en,mn))}_scriptAttrs=null;getScriptAttributes(){return this._scriptAttrs==null&&(this._scriptAttrs=Wr.attributes.replace(/ /g,"").split(",")),this._scriptAttrs}getScript(en){for(var an=0;an<this.getScriptAttributes().length;an++){var pn=this.getScriptAttributes()[an];if(en.hasAttribute&&en.hasAttribute(pn))return en.getAttribute(pn)}return en instanceof HTMLScriptElement&&en.type==="text/hyperscript"?en.innerText:null}hyperscriptFeaturesMap=new WeakMap;getHyperscriptFeatures(en){var an=this.hyperscriptFeaturesMap.get(en);return typeof an>"u"&&en&&this.hyperscriptFeaturesMap.set(en,an={}),an}addFeatures(en,an){en&&(Object.assign(an.locals,this.getHyperscriptFeatures(en)),this.addFeatures(en.parentElement,an))}makeContext(en,an,pn,mn){return new kn(en,an,pn,mn,this)}getScriptSelector(){return this.getScriptAttributes().map(function(en){return"["+en+"]"}).join(", ")}convertValue(en,an){for(var pn=Gr.dynamicResolvers,mn=0;mn<pn.length;mn++){var Sn=pn[mn],vn=Sn(an,en);if(vn!==void 0)return vn}if(en==null)return null;var bn=Gr[an];if(bn)return bn(en);throw"Unknown conversion : "+an}parse(en){let an=this.lexer,pn=this.parser;var mn=an.tokenize(en);if(this.parser.commandStart(mn.currentToken())){var Sn=pn.requireElement("commandList",mn);return mn.hasMore()&&pn.raiseParseError(mn),pn.ensureTerminated(Sn),Sn}else if(pn.featureStart(mn.currentToken())){var vn=pn.requireElement("hyperscript",mn);return mn.hasMore()&&pn.raiseParseError(mn),vn}else{var bn=pn.requireElement("expression",mn);return mn.hasMore()&&pn.raiseParseError(mn),bn}}evaluateNoPromise(en,an){let pn=en.evaluate(an);if(pn.next)throw new Error(Yr.sourceFor.call(en)+" returned a Promise in a context that they are not allowed.");return pn}evaluate(en,an,pn){class mn extends EventTarget{constructor(On){super(),this.module=On}toString(){return this.module.id}}var Sn="document"in Qr?Qr.document.body:new mn(pn&&pn.module);an=Object.assign(this.makeContext(Sn,null,Sn,null),an||{});var vn=this.parse(en);if(vn.execute)return vn.execute(an),typeof an.meta.returnValue<"u"?an.meta.returnValue:an.result;return vn.apply?(vn.apply(Sn,Sn,pn),this.getHyperscriptFeatures(Sn)):vn.evaluate(an);function bn(){return{}}}processNode(en){var an=this.getScriptSelector();this.matchesSelector(en,an)&&this.initElement(en,en),en instanceof HTMLScriptElement&&en.type==="text/hyperscript"&&this.initElement(en,document.body),en.querySelectorAll&&this.forEach(en.querySelectorAll(an+", [type='text/hyperscript']"),pn=>{this.initElement(pn,pn instanceof HTMLScriptElement&&pn.type==="text/hyperscript"?document.body:pn)})}initElement(en,an){if(!(en.closest&&en.closest(Wr.disableSelector))){var pn=this.getInternalData(en);if(!pn.initialized){var mn=this.getScript(en);if(mn)try{pn.initialized=!0,pn.script=mn;let bn=this.lexer,hn=this.parser;var Sn=bn.tokenize(mn),vn=hn.parseHyperScript(Sn);if(!vn)return;vn.apply(an||en,en),setTimeout(()=>{this.triggerEvent(an||en,"load",{hyperscript:!0})},1)}catch(bn){this.triggerEvent(en,"exception",{error:bn}),console.error("hyperscript errors were found on the following element:",en,` 12 + 13 + `,bn.message,bn.stack)}}}}internalDataMap=new WeakMap;getInternalData(en){var an=this.internalDataMap.get(en);return typeof an>"u"&&this.internalDataMap.set(en,an={}),an}typeCheck(en,an,pn){if(en==null&&pn)return!0;var mn=Object.prototype.toString.call(en).slice(8,-1);return mn===an}getElementScope(en){var an=en.meta&&en.meta.owner;if(an){var pn=this.getInternalData(an),mn="elementScope";en.meta.feature&&en.meta.feature.behavior&&(mn=en.meta.feature.behavior+"Scope");var Sn=jn(pn,mn);return Sn}else return{}}isReservedWord(en){return["meta","it","result","locals","event","target","detail","sender","body"].includes(en)}isHyperscriptContext(en){return en instanceof kn}resolveSymbol(en,an,pn){if(en==="me"||en==="my"||en==="I")return an.me;if(en==="it"||en==="its"||en==="result")return an.result;if(en==="you"||en==="your"||en==="yourself")return an.you;if(pn==="global")return Qr[en];if(pn==="element"){var mn=this.getElementScope(an);return mn[en]}else{if(pn==="local")return an.locals[en];if(an.meta&&an.meta.context){var Sn=an.meta.context[en];if(typeof Sn<"u"||an.meta.context.detail&&(Sn=an.meta.context.detail[en],typeof Sn<"u"))return Sn}if(this.isHyperscriptContext(an)&&!this.isReservedWord(en))var vn=an.locals[en];else var vn=an[en];if(typeof vn<"u")return vn;var mn=this.getElementScope(an);return vn=mn[en],typeof vn<"u"?vn:Qr[en]}}setSymbol(en,an,pn,mn){if(pn==="global")Qr[en]=mn;else if(pn==="element"){var Sn=this.getElementScope(an);Sn[en]=mn}else if(pn==="local")an.locals[en]=mn;else if(this.isHyperscriptContext(an)&&!this.isReservedWord(en)&&typeof an.locals[en]<"u")an.locals[en]=mn;else{var Sn=this.getElementScope(an),vn=Sn[en];typeof vn<"u"?Sn[en]=mn:this.isHyperscriptContext(an)&&!this.isReservedWord(en)?an.locals[en]=mn:an[en]=mn}}findNext(en,an){if(en)return en.resolveNext?en.resolveNext(an):en.next?en.next:this.findNext(en.parent,an)}flatGet(en,an,pn){if(en!=null){var mn=pn(en,an);if(typeof mn<"u")return mn;if(this.shouldAutoIterate(en)){var Sn=[];for(var vn of en){var bn=pn(vn,an);Sn.push(bn)}return Sn}}}resolveProperty(en,an){return this.flatGet(en,an,(pn,mn)=>pn[mn])}resolveAttribute(en,an){return this.flatGet(en,an,(pn,mn)=>pn.getAttribute&&pn.getAttribute(mn))}resolveStyle(en,an){return this.flatGet(en,an,(pn,mn)=>pn.style&&pn.style[mn])}resolveComputedStyle(en,an){return this.flatGet(en,an,(pn,mn)=>getComputedStyle(pn).getPropertyValue(mn))}assignToNamespace(en,an,pn,mn){let Sn;typeof document<"u"&&en===document.body?Sn=Qr:Sn=this.getHyperscriptFeatures(en);for(var vn;(vn=an.shift())!==void 0;){var bn=Sn[vn];bn==null&&(bn={},Sn[vn]=bn),Sn=bn}Sn[pn]=mn}getHyperTrace(en,an){for(var pn=[],mn=en;mn.meta.caller;)mn=mn.meta.caller;if(mn.meta.traceMap)return mn.meta.traceMap.get(an,pn)}registerHyperTrace(en,an){for(var pn=[],mn=null;en!=null;)pn.push(en),mn=en,en=en.meta.caller;if(mn.meta.traceMap==null&&(mn.meta.traceMap=new Map),!mn.meta.traceMap.get(an)){var Sn={trace:pn,print:function(vn){vn=vn||console.error,vn("hypertrace /// ");for(var bn=0,hn=0;hn<pn.length;hn++)bn=Math.max(bn,pn[hn].meta.feature.displayName.length);for(var hn=0;hn<pn.length;hn++){var On=pn[hn];vn(" ->",On.meta.feature.displayName.padEnd(bn+2),"-",On.meta.owner)}}};mn.meta.traceMap.set(an,Sn)}}escapeSelector(en){return en.replace(/[:&()\[\]\/]/g,function(an){return"\\"+an})}nullCheck(en,an){if(en==null)throw new Error("'"+an.sourceFor()+"' is null")}isEmpty(en){return en==null||en.length===0}doesExist(en){if(en==null)return!1;if(this.shouldAutoIterate(en)){for(let an of en)return!0;return!1}return!0}getRootNode(en){if(en&&en instanceof Node){var an=en.getRootNode();if(an instanceof Document||an instanceof ShadowRoot)return an}return document}getEventQueueFor(en,an){let pn=this.getInternalData(en);var mn=pn.eventQueues;mn==null&&(mn=new Map,pn.eventQueues=mn);var Sn=mn.get(an);return Sn==null&&(Sn={queue:[],executing:!1},mn.set(an,Sn)),Sn}beepValueToConsole(en,an,pn){if(this.triggerEvent(en,"hyperscript:beep",{element:en,expression:an,value:pn})){var mn;pn?pn instanceof An?mn="ElementCollection":pn.constructor?mn=pn.constructor.name:mn="unknown":mn="object (null)";var Sn=pn;mn==="String"?Sn='"'+Sn+'"':pn instanceof An&&(Sn=Array.from(pn)),console.log("///_ BEEP! The expression ("+Yr.sourceFor.call(an).replace("beep! ","")+") evaluates to:",Sn,"of type "+mn)}}hyperscriptUrl="document"in Qr&&document.currentScript?document.currentScript.src:null}function ln(){return document.cookie.split("; ").map(en=>{let an=en.split("=");return{name:an[0],value:decodeURIComponent(an[1])}})}function dn(xn){document.cookie=xn+"=;expires=Thu, 01 Jan 1970 00:00:00 GMT"}function yn(){for(let xn of ln())dn(xn.name)}let wn=new Proxy({},{get(xn,en){if(en==="then"||en==="asyncWrapper")return null;if(en==="length")return ln().length;if(en==="clear")return dn;if(en==="clearAll")return yn;if(typeof en=="string")if(isNaN(en)){let an=document.cookie.split("; ").find(pn=>pn.startsWith(en+"="))?.split("=")[1];if(an)return decodeURIComponent(an)}else return ln()[parseInt(en)];else if(en===Symbol.iterator)return ln()[en]},set(xn,en,an){var pn=null;return typeof an=="string"?(pn=encodeURIComponent(an),pn+=";samesite=lax"):(pn=encodeURIComponent(an.value),an.expires&&(pn+=";expires="+an.maxAge),an.maxAge&&(pn+=";max-age="+an.maxAge),an.partitioned&&(pn+=";partitioned="+an.partitioned),an.path&&(pn+=";path="+an.path),an.samesite&&(pn+=";samesite="+an.path),an.secure&&(pn+=";secure="+an.path)),document.cookie=en+"="+pn,!0}});class kn{constructor(en,an,pn,mn,Sn){this.meta={parser:Sn.parser,lexer:Sn.lexer,runtime:Sn,owner:en,feature:an,iterators:{},ctx:this},this.locals={cookies:wn},this.me=pn,this.you=void 0,this.result=void 0,this.event=mn,this.target=mn?mn.target:null,this.detail=mn?mn.detail:null,this.sender=mn&&mn.detail?mn.detail.sender:null,this.body="document"in Qr?document.body:null,Sn.addFeatures(en,this)}}class An{constructor(en,an,pn){this._css=en,this.relativeToElement=an,this.escape=pn,this[Gn]=!0}get css(){return this.escape?tn.prototype.escapeSelector(this._css):this._css}get className(){return this._css.substr(1)}get id(){return this.className()}contains(en){for(let an of this)if(an.contains(en))return!0;return!1}get length(){return this.selectMatches().length}[Symbol.iterator](){return this.selectMatches()[Symbol.iterator]()}selectMatches(){return tn.prototype.getRootNode(this.relativeToElement).querySelectorAll(this.css)}}let Gn=Symbol();function jn(xn,en){var an=xn[en];if(an)return an;var pn={};return xn[en]=pn,pn}function Vn(xn){try{return JSON.parse(xn)}catch(en){return ti(en),null}}function ti(xn){console.error?console.error(xn):console.log&&console.log("ERROR: ",xn)}function Ti(xn,en){return new(xn.bind.apply(xn,[xn].concat(en)))}function fi(xn){xn.addLeafExpression("parenthesized",function(nn,on,Jr){if(Jr.matchOpToken("(")){var rn=Jr.clearFollows();try{var cn=nn.requireElement("expression",Jr)}finally{Jr.restoreFollows(rn)}return Jr.requireOpToken(")"),cn}}),xn.addLeafExpression("string",function(nn,on,Jr){var rn=Jr.matchTokenType("STRING");if(rn){var cn=rn.value,sn;if(rn.template){var fn=Kr.tokenize(cn,!0);sn=nn.parseStringTemplate(fn)}else sn=[];return{type:"string",token:rn,args:sn,op:function(un){for(var gn="",En=1;En<arguments.length;En++){var Tn=arguments[En];Tn!==void 0&&(gn+=Tn)}return gn},evaluate:function(un){return sn.length===0?cn:on.unifiedEval(this,un)}}}}),xn.addGrammarElement("nakedString",function(nn,on,Jr){if(Jr.hasMore()){var rn=Jr.consumeUntilWhitespace();return Jr.matchTokenType("WHITESPACE"),{type:"nakedString",tokens:rn,evaluate:function(cn){return rn.map(function(sn){return sn.value}).join("")}}}}),xn.addLeafExpression("number",function(nn,on,Jr){var rn=Jr.matchTokenType("NUMBER");if(rn){var cn=rn,sn=parseFloat(rn.value);return{type:"number",value:sn,numberToken:cn,evaluate:function(){return sn}}}}),xn.addLeafExpression("idRef",function(nn,on,Jr){var rn=Jr.matchTokenType("ID_REF");if(rn&&rn.value)if(rn.template){var cn=rn.value.substring(2),sn=Kr.tokenize(cn),fn=nn.requireElement("expression",sn);return{type:"idRefTemplate",args:[fn],op:function(un,gn){return on.getRootNode(un.me).getElementById(gn)},evaluate:function(un){return on.unifiedEval(this,un)}}}else{let un=rn.value.substring(1);return{type:"idRef",css:rn.value,value:un,evaluate:function(gn){return on.getRootNode(gn.me).getElementById(un)}}}}),xn.addLeafExpression("classRef",function(nn,on,Jr){var rn=Jr.matchTokenType("CLASS_REF");if(rn&&rn.value)if(rn.template){var cn=rn.value.substring(2),sn=Kr.tokenize(cn),fn=nn.requireElement("expression",sn);return{type:"classRefTemplate",args:[fn],op:function(un,gn){return new An("."+gn,un.me,!0)},evaluate:function(un){return on.unifiedEval(this,un)}}}else{let un=rn.value;return{type:"classRef",css:un,evaluate:function(gn){return new An(un,gn.me,!0)}}}});class en extends An{constructor(on,Jr,rn){super(on,Jr),this.templateParts=rn,this.elements=rn.filter(cn=>cn instanceof Element)}get css(){let on="",Jr=0;for(let rn of this.templateParts)rn instanceof Element?on+="[data-hs-query-id='"+Jr+++"']":on+=rn;return on}[Symbol.iterator](){this.elements.forEach((Jr,rn)=>Jr.dataset.hsQueryId=rn);let on=super[Symbol.iterator]();return this.elements.forEach(Jr=>Jr.removeAttribute("data-hs-query-id")),on}}xn.addLeafExpression("queryRef",function(nn,on,Jr){var rn=Jr.matchOpToken("<");if(rn){var cn=Jr.consumeUntil("/");Jr.requireOpToken("/"),Jr.requireOpToken(">");var sn=cn.map(function(En){return En.type==="STRING"?'"'+En.value+'"':En.value}).join(""),fn,un,gn;return/\$[^=]/.test(sn)&&(fn=!0,un=Kr.tokenize(sn,!0),gn=nn.parseStringTemplate(un)),{type:"queryRef",css:sn,args:gn,op:function(En,...Tn){return fn?new en(sn,En.me,Tn):new An(sn,En.me)},evaluate:function(En){return on.unifiedEval(this,En)}}}}),xn.addLeafExpression("attributeRef",function(nn,on,Jr){var rn=Jr.matchTokenType("ATTRIBUTE_REF");if(rn&&rn.value){var cn=rn.value;if(cn.indexOf("[")===0)var sn=cn.substring(2,cn.length-1);else var sn=cn.substring(1);var fn="["+sn+"]",un=sn.split("="),gn=un[0],En=un[1];return En&&En.indexOf('"')===0&&(En=En.substring(1,En.length-1)),{type:"attributeRef",name:gn,css:fn,value:En,op:function(Tn){var Rn=Tn.you||Tn.me;if(Rn)return Rn.getAttribute(gn)},evaluate:function(Tn){return on.unifiedEval(this,Tn)}}}}),xn.addLeafExpression("styleRef",function(nn,on,Jr){var rn=Jr.matchTokenType("STYLE_REF");if(rn&&rn.value){var cn=rn.value.substr(1);return cn.startsWith("computed-")?(cn=cn.substr(9),{type:"computedStyleRef",name:cn,op:function(sn){var fn=sn.you||sn.me;if(fn)return on.resolveComputedStyle(fn,cn)},evaluate:function(sn){return on.unifiedEval(this,sn)}}):{type:"styleRef",name:cn,op:function(sn){var fn=sn.you||sn.me;if(fn)return on.resolveStyle(fn,cn)},evaluate:function(sn){return on.unifiedEval(this,sn)}}}}),xn.addGrammarElement("objectKey",function(nn,on,Jr){var rn;if(rn=Jr.matchTokenType("STRING"))return{type:"objectKey",key:rn.value,evaluate:function(){return rn.value}};if(Jr.matchOpToken("[")){var cn=nn.parseElement("expression",Jr);return Jr.requireOpToken("]"),{type:"objectKey",expr:cn,args:[cn],op:function(fn,un){return un},evaluate:function(fn){return on.unifiedEval(this,fn)}}}else{var sn="";do rn=Jr.matchTokenType("IDENTIFIER")||Jr.matchOpToken("-"),rn&&(sn+=rn.value);while(rn);return{type:"objectKey",key:sn,evaluate:function(){return sn}}}}),xn.addLeafExpression("objectLiteral",function(nn,on,Jr){if(Jr.matchOpToken("{")){var rn=[],cn=[];if(!Jr.matchOpToken("}")){do{var sn=nn.requireElement("objectKey",Jr);Jr.requireOpToken(":");var fn=nn.requireElement("expression",Jr);cn.push(fn),rn.push(sn)}while(Jr.matchOpToken(",")&&!Jr.peekToken("}",0,"R_BRACE"));Jr.requireOpToken("}")}return{type:"objectLiteral",args:[rn,cn],op:function(un,gn,En){for(var Tn={},Rn=0;Rn<gn.length;Rn++)Tn[gn[Rn]]=En[Rn];return Tn},evaluate:function(un){return on.unifiedEval(this,un)}}}}),xn.addGrammarElement("nakedNamedArgumentList",function(nn,on,Jr){var rn=[],cn=[];if(Jr.currentToken().type==="IDENTIFIER")do{var sn=Jr.requireTokenType("IDENTIFIER");Jr.requireOpToken(":");var fn=nn.requireElement("expression",Jr);cn.push(fn),rn.push({name:sn,value:fn})}while(Jr.matchOpToken(","));return{type:"namedArgumentList",fields:rn,args:[cn],op:function(un,gn){for(var En={_namedArgList_:!0},Tn=0;Tn<gn.length;Tn++){var Rn=rn[Tn];En[Rn.name.value]=gn[Tn]}return En},evaluate:function(un){return on.unifiedEval(this,un)}}}),xn.addGrammarElement("namedArgumentList",function(nn,on,Jr){if(Jr.matchOpToken("(")){var rn=nn.requireElement("nakedNamedArgumentList",Jr);return Jr.requireOpToken(")"),rn}}),xn.addGrammarElement("symbol",function(nn,on,Jr){var rn="default";Jr.matchToken("global")?rn="global":Jr.matchToken("element")||Jr.matchToken("module")?(rn="element",Jr.matchOpToken("'")&&Jr.requireToken("s")):Jr.matchToken("local")&&(rn="local");let cn=Jr.matchOpToken(":"),sn=Jr.matchTokenType("IDENTIFIER");if(sn&&sn.value){var fn=sn.value;return cn&&(fn=":"+fn),rn==="default"&&(fn.indexOf("$")===0&&(rn="global"),fn.indexOf(":")===0&&(rn="element")),{type:"symbol",token:sn,scope:rn,name:fn,evaluate:function(un){return on.resolveSymbol(fn,un,rn)}}}}),xn.addGrammarElement("implicitMeTarget",function(nn,on,Jr){return{type:"implicitMeTarget",evaluate:function(rn){return rn.you||rn.me}}}),xn.addLeafExpression("boolean",function(nn,on,Jr){var rn=Jr.matchToken("true")||Jr.matchToken("false");if(!rn)return;let cn=rn.value==="true";return{type:"boolean",evaluate:function(sn){return cn}}}),xn.addLeafExpression("null",function(nn,on,Jr){if(Jr.matchToken("null"))return{type:"null",evaluate:function(rn){return null}}}),xn.addLeafExpression("arrayLiteral",function(nn,on,Jr){if(Jr.matchOpToken("[")){var rn=[];if(!Jr.matchOpToken("]")){do{var cn=nn.requireElement("expression",Jr);rn.push(cn)}while(Jr.matchOpToken(","));Jr.requireOpToken("]")}return{type:"arrayLiteral",values:rn,args:[rn],op:function(sn,fn){return fn},evaluate:function(sn){return on.unifiedEval(this,sn)}}}}),xn.addLeafExpression("blockLiteral",function(nn,on,Jr){if(Jr.matchOpToken("\\")){var rn=[],cn=Jr.matchTokenType("IDENTIFIER");if(cn)for(rn.push(cn);Jr.matchOpToken(",");)rn.push(Jr.requireTokenType("IDENTIFIER"));Jr.requireOpToken("-"),Jr.requireOpToken(">");var sn=nn.requireElement("expression",Jr);return{type:"blockLiteral",args:rn,expr:sn,evaluate:function(fn){var un=function(){for(var gn=0;gn<rn.length;gn++)fn.locals[rn[gn].value]=arguments[gn];return sn.evaluate(fn)};return un}}}}),xn.addIndirectExpression("propertyAccess",function(nn,on,Jr,rn){if(Jr.matchOpToken(".")){var cn=Jr.requireTokenType("IDENTIFIER"),sn={type:"propertyAccess",root:rn,prop:cn,args:[rn],op:function(fn,un){var gn=on.resolveProperty(un,cn.value);return gn},evaluate:function(fn){return on.unifiedEval(this,fn)}};return nn.parseElement("indirectExpression",Jr,sn)}}),xn.addIndirectExpression("of",function(nn,on,Jr,rn){if(Jr.matchToken("of")){for(var cn=nn.requireElement("unaryExpression",Jr),sn=null,fn=rn;fn.root;)sn=fn,fn=fn.root;fn.type!=="symbol"&&fn.type!=="attributeRef"&&fn.type!=="styleRef"&&fn.type!=="computedStyleRef"&&nn.raiseParseError(Jr,"Cannot take a property of a non-symbol: "+fn.type);var un=fn.type==="attributeRef",gn=fn.type==="styleRef"||fn.type==="computedStyleRef";if(un||gn)var En=fn;var Tn=fn.name,Rn={type:"ofExpression",prop:fn.token,root:cn,attribute:En,expression:rn,args:[cn],op:function(Bn,Cn){return un?on.resolveAttribute(Cn,Tn):gn?fn.type==="computedStyleRef"?on.resolveComputedStyle(Cn,Tn):on.resolveStyle(Cn,Tn):on.resolveProperty(Cn,Tn)},evaluate:function(Bn){return on.unifiedEval(this,Bn)}};return fn.type==="attributeRef"&&(Rn.attribute=fn),sn?(sn.root=Rn,sn.args=[Rn]):rn=Rn,nn.parseElement("indirectExpression",Jr,rn)}}),xn.addIndirectExpression("possessive",function(nn,on,Jr,rn){if(!nn.possessivesDisabled){var cn=Jr.matchOpToken("'");if(cn||rn.type==="symbol"&&(rn.name==="my"||rn.name==="its"||rn.name==="your")&&(Jr.currentToken().type==="IDENTIFIER"||Jr.currentToken().type==="ATTRIBUTE_REF"||Jr.currentToken().type==="STYLE_REF")){cn&&Jr.requireToken("s");var sn,fn,un;sn=nn.parseElement("attributeRef",Jr),sn==null&&(fn=nn.parseElement("styleRef",Jr),fn==null&&(un=Jr.requireTokenType("IDENTIFIER")));var gn={type:"possessive",root:rn,attribute:sn||fn,prop:un,args:[rn],op:function(En,Tn){if(sn)var Rn=on.resolveAttribute(Tn,sn.name);else if(fn){var Rn;fn.type==="computedStyleRef"?Rn=on.resolveComputedStyle(Tn,fn.name):Rn=on.resolveStyle(Tn,fn.name)}else var Rn=on.resolveProperty(Tn,un.value);return Rn},evaluate:function(En){return on.unifiedEval(this,En)}};return nn.parseElement("indirectExpression",Jr,gn)}}}),xn.addIndirectExpression("inExpression",function(nn,on,Jr,rn){if(Jr.matchToken("in")){var cn=nn.requireElement("unaryExpression",Jr),sn={type:"inExpression",root:rn,args:[rn,cn],op:function(fn,un,gn){var En=[];if(un.css)on.implicitLoop(gn,function(Rn){for(var Bn=Rn.querySelectorAll(un.css),Cn=0;Cn<Bn.length;Cn++)En.push(Bn[Cn])});else if(un instanceof Element){var Tn=!1;if(on.implicitLoop(gn,function(Rn){Rn.contains(un)&&(Tn=!0)}),Tn)return un}else on.implicitLoop(un,function(Rn){on.implicitLoop(gn,function(Bn){Rn===Bn&&En.push(Rn)})});return En},evaluate:function(fn){return on.unifiedEval(this,fn)}};return nn.parseElement("indirectExpression",Jr,sn)}}),xn.addIndirectExpression("asExpression",function(nn,on,Jr,rn){if(Jr.matchToken("as")){Jr.matchToken("a")||Jr.matchToken("an");var cn=nn.requireElement("dotOrColonPath",Jr).evaluate(),sn={type:"asExpression",root:rn,args:[rn],op:function(fn,un){return on.convertValue(un,cn)},evaluate:function(fn){return on.unifiedEval(this,fn)}};return nn.parseElement("indirectExpression",Jr,sn)}}),xn.addIndirectExpression("functionCall",function(nn,on,Jr,rn){if(Jr.matchOpToken("(")){var cn=[];if(!Jr.matchOpToken(")")){do cn.push(nn.requireElement("expression",Jr));while(Jr.matchOpToken(","));Jr.requireOpToken(")")}if(rn.root)var sn={type:"functionCall",root:rn,argExressions:cn,args:[rn.root,cn],op:function(fn,un,gn){on.nullCheck(un,rn.root);var En=un[rn.prop.value];return on.nullCheck(En,rn),En.hyperfunc&&gn.push(fn),En.apply(un,gn)},evaluate:function(fn){return on.unifiedEval(this,fn)}};else var sn={type:"functionCall",root:rn,argExressions:cn,args:[rn,cn],op:function(un,gn,En){on.nullCheck(gn,rn),gn.hyperfunc&&En.push(un);var Tn=gn.apply(null,En);return Tn},evaluate:function(un){return on.unifiedEval(this,un)}};return nn.parseElement("indirectExpression",Jr,sn)}}),xn.addIndirectExpression("attributeRefAccess",function(nn,on,Jr,rn){var cn=nn.parseElement("attributeRef",Jr);if(cn){var sn={type:"attributeRefAccess",root:rn,attribute:cn,args:[rn],op:function(fn,un){var gn=on.resolveAttribute(un,cn.name);return gn},evaluate:function(fn){return on.unifiedEval(this,fn)}};return sn}}),xn.addIndirectExpression("arrayIndex",function(nn,on,Jr,rn){if(Jr.matchOpToken("[")){var cn=!1,sn=!1,fn=null,un=null;if(Jr.matchOpToken(".."))cn=!0,fn=nn.requireElement("expression",Jr);else if(fn=nn.requireElement("expression",Jr),Jr.matchOpToken("..")){sn=!0;var gn=Jr.currentToken();gn.type!=="R_BRACKET"&&(un=nn.parseElement("expression",Jr))}Jr.requireOpToken("]");var En={type:"arrayIndex",root:rn,prop:fn,firstIndex:fn,secondIndex:un,args:[rn,fn,un],op:function(Tn,Rn,Bn,Cn){return Rn==null?null:cn?(Bn<0&&(Bn=Rn.length+Bn),Rn.slice(0,Bn+1)):sn?Cn!=null?(Cn<0&&(Cn=Rn.length+Cn),Rn.slice(Bn,Cn+1)):Rn.slice(Bn):Rn[Bn]},evaluate:function(Tn){return on.unifiedEval(this,Tn)}};return nn.parseElement("indirectExpression",Jr,En)}});var an=["em","ex","cap","ch","ic","rem","lh","rlh","vw","vh","vi","vb","vmin","vmax","cm","mm","Q","pc","pt","px"];xn.addGrammarElement("postfixExpression",function(nn,on,Jr){var rn=nn.parseElement("negativeNumber",Jr);let cn=Jr.matchAnyToken.apply(Jr,an)||Jr.matchOpToken("%");if(cn)return{type:"stringPostfix",postfix:cn.value,args:[rn],op:function(gn,En){return""+En+cn.value},evaluate:function(gn){return on.unifiedEval(this,gn)}};var sn=null;if(Jr.matchToken("s")||Jr.matchToken("seconds")?sn=1e3:(Jr.matchToken("ms")||Jr.matchToken("milliseconds"))&&(sn=1),sn)return{type:"timeExpression",time:rn,factor:sn,args:[rn],op:function(gn,En){return En*sn},evaluate:function(gn){return on.unifiedEval(this,gn)}};if(Jr.matchOpToken(":")){var fn=Jr.requireTokenType("IDENTIFIER");if(!fn.value)return;var un=!Jr.matchOpToken("!");return{type:"typeCheck",typeName:fn,nullOk:un,args:[rn],op:function(gn,En){var Tn=on.typeCheck(En,this.typeName.value,un);if(Tn)return En;throw new Error("Typecheck failed! Expected: "+fn.value)},evaluate:function(gn){return on.unifiedEval(this,gn)}}}else return rn}),xn.addGrammarElement("logicalNot",function(nn,on,Jr){if(Jr.matchToken("not")){var rn=nn.requireElement("unaryExpression",Jr);return{type:"logicalNot",root:rn,args:[rn],op:function(cn,sn){return!sn},evaluate:function(cn){return on.unifiedEval(this,cn)}}}}),xn.addGrammarElement("noExpression",function(nn,on,Jr){if(Jr.matchToken("no")){var rn=nn.requireElement("unaryExpression",Jr);return{type:"noExpression",root:rn,args:[rn],op:function(cn,sn){return on.isEmpty(sn)},evaluate:function(cn){return on.unifiedEval(this,cn)}}}}),xn.addLeafExpression("some",function(nn,on,Jr){if(Jr.matchToken("some")){var rn=nn.requireElement("expression",Jr);return{type:"noExpression",root:rn,args:[rn],op:function(cn,sn){return!on.isEmpty(sn)},evaluate(cn){return on.unifiedEval(this,cn)}}}}),xn.addGrammarElement("negativeNumber",function(nn,on,Jr){if(Jr.matchOpToken("-")){var rn=nn.requireElement("negativeNumber",Jr);return{type:"negativeNumber",root:rn,args:[rn],op:function(cn,sn){return-1*sn},evaluate:function(cn){return on.unifiedEval(this,cn)}}}else return nn.requireElement("primaryExpression",Jr)}),xn.addGrammarElement("unaryExpression",function(nn,on,Jr){return Jr.matchToken("the"),nn.parseAnyOf(["beepExpression","logicalNot","relativePositionalExpression","positionalExpression","noExpression","postfixExpression"],Jr)}),xn.addGrammarElement("beepExpression",function(nn,on,Jr){if(Jr.matchToken("beep!")){var rn=nn.parseElement("unaryExpression",Jr);if(rn){rn.booped=!0;var cn=rn.evaluate;return rn.evaluate=function(sn){let fn=cn.apply(rn,arguments),un=sn.me;return on.beepValueToConsole(un,rn,fn),fn},rn}}});var pn=function(nn,on,Jr,rn){for(var cn=on.querySelectorAll(Jr),sn=0;sn<cn.length;sn++){var fn=cn[sn];if(fn.compareDocumentPosition(nn)===Node.DOCUMENT_POSITION_PRECEDING)return fn}if(rn)return cn[0]},mn=function(nn,on,Jr,rn){for(var cn=on.querySelectorAll(Jr),sn=cn.length-1;sn>=0;sn--){var fn=cn[sn];if(fn.compareDocumentPosition(nn)===Node.DOCUMENT_POSITION_FOLLOWING)return fn}if(rn)return cn[cn.length-1]},Sn=function(nn,on,Jr,rn){var cn=[];tn.prototype.forEach(on,function(gn){(gn.matches(Jr)||gn===nn)&&cn.push(gn)});for(var sn=0;sn<cn.length-1;sn++){var fn=cn[sn];if(fn===nn)return cn[sn+1]}if(rn){var un=cn[0];if(un&&un.matches(Jr))return un}},vn=function(nn,on,Jr,rn){return Sn(nn,Array.from(on).reverse(),Jr,rn)};xn.addGrammarElement("relativePositionalExpression",function(nn,on,Jr){var rn=Jr.matchAnyToken("next","previous");if(rn){var cn=rn.value==="next",sn=nn.parseElement("expression",Jr);if(Jr.matchToken("from")){Jr.pushFollow("in");try{var fn=nn.requireElement("unaryExpression",Jr)}finally{Jr.popFollow()}}else var fn=nn.requireElement("implicitMeTarget",Jr);var un=!1,gn;if(Jr.matchToken("in")){un=!0;var En=nn.requireElement("unaryExpression",Jr)}else Jr.matchToken("within")?gn=nn.requireElement("unaryExpression",Jr):gn=document.body;var Tn=!1;return Jr.matchToken("with")&&(Jr.requireToken("wrapping"),Tn=!0),{type:"relativePositionalExpression",from:fn,forwardSearch:cn,inSearch:un,wrapping:Tn,inElt:En,withinElt:gn,operator:rn.value,args:[sn,fn,En,gn],op:function(Rn,Bn,Cn,In,_n){var Un=Bn.css;if(Un==null)throw"Expected a CSS value to be returned by "+Yr.sourceFor.apply(sn);if(un){if(In)return cn?Sn(Cn,In,Un,Tn):vn(Cn,In,Un,Tn)}else if(_n)return cn?pn(Cn,_n,Un,Tn):mn(Cn,_n,Un,Tn)},evaluate:function(Rn){return on.unifiedEval(this,Rn)}}}}),xn.addGrammarElement("positionalExpression",function(nn,on,Jr){var rn=Jr.matchAnyToken("first","last","random");if(!rn)return;Jr.matchAnyToken("in","from","of");var cn=nn.requireElement("unaryExpression",Jr);let sn=rn.value;return{type:"positionalExpression",rhs:cn,operator:rn.value,args:[cn],op:function(fn,un){if(un&&!Array.isArray(un)&&(un.children?un=un.children:un=Array.from(un)),un){if(sn==="first")return un[0];if(sn==="last")return un[un.length-1];if(sn==="random")return un[Math.floor(Math.random()*un.length)]}},evaluate:function(fn){return on.unifiedEval(this,fn)}}}),xn.addGrammarElement("mathOperator",function(nn,on,Jr){var rn=nn.parseElement("unaryExpression",Jr),cn,sn=null;for(cn=Jr.matchAnyOpToken("+","-","*","/")||Jr.matchToken("mod");cn;){sn=sn||cn;var fn=cn.value;sn.value!==fn&&nn.raiseParseError(Jr,"You must parenthesize math operations with different operators");var un=nn.parseElement("unaryExpression",Jr);rn={type:"mathOperator",lhs:rn,rhs:un,operator:fn,args:[rn,un],op:function(gn,En,Tn){if(fn==="+")return En+Tn;if(fn==="-")return En-Tn;if(fn==="*")return En*Tn;if(fn==="/")return En/Tn;if(fn==="mod")return En%Tn},evaluate:function(gn){return on.unifiedEval(this,gn)}},cn=Jr.matchAnyOpToken("+","-","*","/")||Jr.matchToken("mod")}return rn}),xn.addGrammarElement("mathExpression",function(nn,on,Jr){return nn.parseAnyOf(["mathOperator","unaryExpression"],Jr)});function bn(nn,on,Jr){if(on.contains)return on.contains(Jr);if(on.includes)return on.includes(Jr);throw Error("The value of "+nn.sourceFor()+" does not have a contains or includes method on it")}function hn(nn,on,Jr){if(on.match)return!!on.match(Jr);if(on.matches)return on.matches(Jr);throw Error("The value of "+nn.sourceFor()+" does not have a match or matches method on it")}xn.addGrammarElement("comparisonOperator",function(nn,on,Jr){var rn=nn.parseElement("mathExpression",Jr),cn=Jr.matchAnyOpToken("<",">","<=",">=","==","===","!=","!=="),sn=cn?cn.value:null,fn=!0,un=!1;if(sn==null&&(Jr.matchToken("is")||Jr.matchToken("am")?Jr.matchToken("not")?Jr.matchToken("in")?sn="not in":Jr.matchToken("a")||Jr.matchToken("an")?(sn="not a",un=!0):Jr.matchToken("empty")?(sn="not empty",fn=!1):(Jr.matchToken("really")?sn="!==":sn="!=",Jr.matchToken("equal")&&Jr.matchToken("to")):Jr.matchToken("in")?sn="in":Jr.matchToken("a")||Jr.matchToken("an")?(sn="a",un=!0):Jr.matchToken("empty")?(sn="empty",fn=!1):Jr.matchToken("less")?(Jr.requireToken("than"),Jr.matchToken("or")?(Jr.requireToken("equal"),Jr.requireToken("to"),sn="<="):sn="<"):Jr.matchToken("greater")?(Jr.requireToken("than"),Jr.matchToken("or")?(Jr.requireToken("equal"),Jr.requireToken("to"),sn=">="):sn=">"):(Jr.matchToken("really")?sn="===":sn="==",Jr.matchToken("equal")&&Jr.matchToken("to")):Jr.matchToken("equals")?sn="==":Jr.matchToken("really")?(Jr.requireToken("equals"),sn="==="):Jr.matchToken("exist")||Jr.matchToken("exists")?(sn="exist",fn=!1):Jr.matchToken("matches")||Jr.matchToken("match")?sn="match":Jr.matchToken("contains")||Jr.matchToken("contain")?sn="contain":Jr.matchToken("includes")||Jr.matchToken("include")?sn="include":(Jr.matchToken("do")||Jr.matchToken("does"))&&(Jr.requireToken("not"),Jr.matchToken("matches")||Jr.matchToken("match")?sn="not match":Jr.matchToken("contains")||Jr.matchToken("contain")?sn="not contain":Jr.matchToken("exist")||Jr.matchToken("exist")?(sn="not exist",fn=!1):Jr.matchToken("include")?sn="not include":nn.raiseParseError(Jr,"Expected matches or contains"))),sn){var gn,En,Tn;un?(gn=Jr.requireTokenType("IDENTIFIER"),En=!Jr.matchOpToken("!")):fn&&(Tn=nn.requireElement("mathExpression",Jr),(sn==="match"||sn==="not match")&&(Tn=Tn.css?Tn.css:Tn));var Rn=rn;rn={type:"comparisonOperator",operator:sn,typeName:gn,nullOk:En,lhs:rn,rhs:Tn,args:[rn,Tn],op:function(Bn,Cn,In){if(sn==="==")return Cn==In;if(sn==="!=")return Cn!=In;if(sn==="===")return Cn===In;if(sn==="!==")return Cn!==In;if(sn==="match")return Cn!=null&&hn(Rn,Cn,In);if(sn==="not match")return Cn==null||!hn(Rn,Cn,In);if(sn==="in")return In!=null&&bn(Tn,In,Cn);if(sn==="not in")return In==null||!bn(Tn,In,Cn);if(sn==="contain")return Cn!=null&&bn(Rn,Cn,In);if(sn==="not contain")return Cn==null||!bn(Rn,Cn,In);if(sn==="include")return Cn!=null&&bn(Rn,Cn,In);if(sn==="not include")return Cn==null||!bn(Rn,Cn,In);if(sn==="===")return Cn===In;if(sn==="!==")return Cn!==In;if(sn==="<")return Cn<In;if(sn===">")return Cn>In;if(sn==="<=")return Cn<=In;if(sn===">=")return Cn>=In;if(sn==="empty")return on.isEmpty(Cn);if(sn==="not empty")return!on.isEmpty(Cn);if(sn==="exist")return on.doesExist(Cn);if(sn==="not exist")return!on.doesExist(Cn);if(sn==="a")return on.typeCheck(Cn,gn.value,En);if(sn==="not a")return!on.typeCheck(Cn,gn.value,En);throw"Unknown comparison : "+sn},evaluate:function(Bn){return on.unifiedEval(this,Bn)}}}return rn}),xn.addGrammarElement("comparisonExpression",function(nn,on,Jr){return nn.parseAnyOf(["comparisonOperator","mathExpression"],Jr)}),xn.addGrammarElement("logicalOperator",function(nn,on,Jr){var rn=nn.parseElement("comparisonExpression",Jr),cn,sn=null;for(cn=Jr.matchToken("and")||Jr.matchToken("or");cn;){sn=sn||cn,sn.value!==cn.value&&nn.raiseParseError(Jr,"You must parenthesize logical operations with different operators");var fn=nn.requireElement("comparisonExpression",Jr);let un=cn.value;rn={type:"logicalOperator",operator:un,lhs:rn,rhs:fn,args:[rn,fn],op:function(gn,En,Tn){return un==="and"?En&&Tn:En||Tn},evaluate:function(gn){return on.unifiedEval(this,gn,un==="or")}},cn=Jr.matchToken("and")||Jr.matchToken("or")}return rn}),xn.addGrammarElement("logicalExpression",function(nn,on,Jr){return nn.parseAnyOf(["logicalOperator","mathExpression"],Jr)}),xn.addGrammarElement("asyncExpression",function(nn,on,Jr){if(Jr.matchToken("async")){var rn=nn.requireElement("logicalExpression",Jr),cn={type:"asyncExpression",value:rn,evaluate:function(sn){return{asyncWrapper:!0,value:this.value.evaluate(sn)}}};return cn}else return nn.parseElement("logicalExpression",Jr)}),xn.addGrammarElement("expression",function(nn,on,Jr){return Jr.matchToken("the"),nn.parseElement("asyncExpression",Jr)}),xn.addGrammarElement("assignableExpression",function(nn,on,Jr){Jr.matchToken("the");var rn=nn.parseElement("primaryExpression",Jr);return rn&&(rn.type==="symbol"||rn.type==="ofExpression"||rn.type==="propertyAccess"||rn.type==="attributeRefAccess"||rn.type==="attributeRef"||rn.type==="styleRef"||rn.type==="arrayIndex"||rn.type==="possessive")||nn.raiseParseError(Jr,"A target expression must be writable. The expression type '"+(rn&&rn.type)+"' is not."),rn}),xn.addGrammarElement("hyperscript",function(nn,on,Jr){var rn=[];if(Jr.hasMore())for(;nn.featureStart(Jr.currentToken())||Jr.currentToken().value==="(";){var cn=nn.requireElement("feature",Jr);rn.push(cn),Jr.matchToken("end")}return{type:"hyperscript",features:rn,apply:function(sn,fn,un){for(let gn of rn)gn.install(sn,fn,un)}}});var On=function(nn){var on=[];if(nn.token(0).value==="("&&(nn.token(1).value===")"||nn.token(2).value===","||nn.token(2).value===")")){nn.matchOpToken("(");do on.push(nn.requireTokenType("IDENTIFIER"));while(nn.matchOpToken(","));nn.requireOpToken(")")}return on};xn.addFeature("on",function(nn,on,Jr){if(Jr.matchToken("on")){var rn=!1;Jr.matchToken("every")&&(rn=!0);var cn=[],sn=null;do{var fn=nn.requireElement("eventName",Jr,"Expected event name"),un=fn.evaluate();sn?sn=sn+" or "+un:sn="on "+un;var gn=On(Jr),En=null;Jr.matchOpToken("[")&&(En=nn.requireElement("expression",Jr),Jr.requireOpToken("]"));var Tn,Rn,Bn;if(Jr.currentToken().type==="NUMBER"){var Cn=Jr.consumeToken();if(!Cn.value)return;if(Tn=parseInt(Cn.value),Jr.matchToken("to")){var In=Jr.consumeToken();if(!In.value)return;Rn=parseInt(In.value)}else Jr.matchToken("and")&&(Bn=!0,Jr.requireToken("on"))}var _n,Un;if(un==="intersection"){if(_n={},Jr.matchToken("with")&&(_n.with=nn.requireElement("expression",Jr).evaluate()),Jr.matchToken("having"))do Jr.matchToken("margin")?_n.rootMargin=nn.requireElement("stringLike",Jr).evaluate():Jr.matchToken("threshold")?_n.threshold=nn.requireElement("expression",Jr).evaluate():nn.raiseParseError(Jr,"Unknown intersection config specification");while(Jr.matchToken("and"))}else if(un==="mutation")if(Un={},Jr.matchToken("of"))do if(Jr.matchToken("anything"))Un.attributes=!0,Un.subtree=!0,Un.characterData=!0,Un.childList=!0;else if(Jr.matchToken("childList"))Un.childList=!0;else if(Jr.matchToken("attributes"))Un.attributes=!0,Un.attributeOldValue=!0;else if(Jr.matchToken("subtree"))Un.subtree=!0;else if(Jr.matchToken("characterData"))Un.characterData=!0,Un.characterDataOldValue=!0;else if(Jr.currentToken().type==="ATTRIBUTE_REF"){var ni=Jr.consumeToken();Un.attributeFilter==null&&(Un.attributeFilter=[]),ni.value.indexOf("@")==0?Un.attributeFilter.push(ni.value.substring(1)):nn.raiseParseError(Jr,"Only shorthand attribute references are allowed here")}else nn.raiseParseError(Jr,"Unknown mutation config specification");while(Jr.matchToken("or"));else Un.attributes=!0,Un.characterData=!0,Un.childList=!0;var ii=null,Ri=!1;if(Jr.matchToken("from"))if(Jr.matchToken("elsewhere"))Ri=!0;else{Jr.pushFollow("or");try{ii=nn.requireElement("expression",Jr)}finally{Jr.popFollow()}ii||nn.raiseParseError(Jr,'Expected either target value or "elsewhere".')}if(ii===null&&Ri===!1&&Jr.matchToken("elsewhere")&&(Ri=!0),Jr.matchToken("in"))var Bi=nn.parseElement("unaryExpression",Jr);if(Jr.matchToken("debounced")){Jr.requireToken("at");var si=nn.requireElement("unaryExpression",Jr),ki=si.evaluate({})}else if(Jr.matchToken("throttled")){Jr.requireToken("at");var si=nn.requireElement("unaryExpression",Jr),Hi=si.evaluate({})}cn.push({execCount:0,every:rn,on:un,args:gn,filter:En,from:ii,inExpr:Bi,elsewhere:Ri,startCount:Tn,endCount:Rn,unbounded:Bn,debounceTime:ki,throttleTime:Hi,mutationSpec:Un,intersectionSpec:_n,debounced:void 0,lastExec:void 0})}while(Jr.matchToken("or"));var Ci=!0;if(!rn&&Jr.matchToken("queue"))if(Jr.matchToken("all"))var Ni=!0,Ci=!1;else if(Jr.matchToken("first"))var Ca=!0;else if(Jr.matchToken("none"))var _a=!0;else Jr.requireToken("last");var uo=nn.requireElement("commandList",Jr);nn.ensureTerminated(uo);var ts,co;if(Jr.matchToken("catch")&&(ts=Jr.requireTokenType("IDENTIFIER").value,co=nn.requireElement("commandList",Jr),nn.ensureTerminated(co)),Jr.matchToken("finally")){var Ws=nn.requireElement("commandList",Jr);nn.ensureTerminated(Ws)}var _i={displayName:sn,events:cn,start:uo,every:rn,execCount:0,errorHandler:co,errorSymbol:ts,execute:function(xi){let aa=on.getEventQueueFor(xi.me,_i);if(aa.executing&&rn===!1){if(_a||Ca&&aa.queue.length>0)return;Ci&&(aa.queue.length=0),aa.queue.push(xi);return}_i.execCount++,aa.executing=!0,xi.meta.onHalt=function(){aa.executing=!1;var Li=aa.queue.shift();Li&&setTimeout(function(){_i.execute(Li)},1)},xi.meta.reject=function(Li){console.error(Li.message?Li.message:Li);var $n=on.getHyperTrace(xi,Li);$n&&$n.print(),on.triggerEvent(xi.me,"exception",{error:Li})},uo.execute(xi)},install:function(xi,aa){for(let $n of _i.events){var Li;$n.elsewhere?Li=[document]:$n.from?Li=$n.from.evaluate(on.makeContext(xi,_i,xi,null)):Li=[xi],on.implicitLoop(Li,function(ji){var Ia=$n.on;if(ji==null){console.warn("'%s' feature ignored because target does not exists:",sn,xi);return}if($n.mutationSpec&&(Ia="hyperscript:mutation",new MutationObserver(function(pa,li){_i.executing||on.triggerEvent(ji,Ia,{mutationList:pa,observer:li})}).observe(ji,$n.mutationSpec)),$n.intersectionSpec){Ia="hyperscript:intersection";let ja=new IntersectionObserver(function(pa){for(let fo of pa){var li={observer:ja};li=Object.assign(li,fo),li.intersecting=fo.isIntersecting,on.triggerEvent(ji,Ia,li)}},$n.intersectionSpec);ja.observe(ji)}var Kl=ji.addEventListener||ji.on;Kl.call(ji,Ia,function ja(pa){if(typeof Node<"u"&&xi instanceof Node&&ji!==xi&&!xi.isConnected){ji.removeEventListener(Ia,ja);return}var li=on.makeContext(xi,_i,xi,pa);if(!($n.elsewhere&&xi.contains(pa.target))){$n.from&&(li.result=ji);for(let ho of $n.args){let Xs=li.event[ho.value];Xs!==void 0?li.locals[ho.value]=Xs:"detail"in li.event&&(li.locals[ho.value]=li.event.detail[ho.value])}if(li.meta.errorHandler=co,li.meta.errorSymbol=ts,li.meta.finallyHandler=Ws,$n.filter){var fo=li.meta.context;li.meta.context=li.event;try{var Yl=$n.filter.evaluate(li);if(!Yl)return}finally{li.meta.context=fo}}if($n.inExpr){for(var Oa=pa.target;;)if(Oa.matches&&Oa.matches($n.inExpr.css)){li.result=Oa;break}else if(Oa=Oa.parentElement,Oa==null)return}if($n.execCount++,$n.startCount){if($n.endCount){if($n.execCount<$n.startCount||$n.execCount>$n.endCount)return}else if($n.unbounded){if($n.execCount<$n.startCount)return}else if($n.execCount!==$n.startCount)return}if($n.debounceTime){$n.debounced&&clearTimeout($n.debounced),$n.debounced=setTimeout(function(){_i.execute(li)},$n.debounceTime);return}if($n.throttleTime){if($n.lastExec&&Date.now()<$n.lastExec+$n.throttleTime)return;$n.lastExec=Date.now()}_i.execute(li)}})})}}};return nn.setParent(uo,_i),_i}}),xn.addFeature("def",function(nn,on,Jr){if(Jr.matchToken("def")){var rn=nn.requireElement("dotOrColonPath",Jr),cn=rn.evaluate(),sn=cn.split("."),fn=sn.pop(),un=[];if(Jr.matchOpToken("(")&&!Jr.matchOpToken(")")){do un.push(Jr.requireTokenType("IDENTIFIER"));while(Jr.matchOpToken(","));Jr.requireOpToken(")")}var gn=nn.requireElement("commandList",Jr),En,Tn;if(Jr.matchToken("catch")&&(En=Jr.requireTokenType("IDENTIFIER").value,Tn=nn.parseElement("commandList",Jr)),Jr.matchToken("finally")){var Rn=nn.requireElement("commandList",Jr);nn.ensureTerminated(Rn)}var Bn={displayName:fn+"("+un.map(function(Cn){return Cn.value}).join(", ")+")",name:fn,args:un,start:gn,errorHandler:Tn,errorSymbol:En,finallyHandler:Rn,install:function(Cn,In){var _n=function(){var Un=on.makeContext(In,Bn,Cn,null);Un.meta.errorHandler=Tn,Un.meta.errorSymbol=En,Un.meta.finallyHandler=Rn;for(var ni=0;ni<un.length;ni++){var ii=un[ni],Ri=arguments[ni];ii&&(Un.locals[ii.value]=Ri)}Un.meta.caller=arguments[un.length],Un.meta.caller&&(Un.meta.callingCommand=Un.meta.caller.meta.command);var Bi,si=null,ki=new Promise(function(Hi,Ci){Bi=Hi,si=Ci});return gn.execute(Un),Un.meta.returned?Un.meta.returnValue:(Un.meta.resolve=Bi,Un.meta.reject=si,ki)};_n.hyperfunc=!0,_n.hypername=cn,on.assignToNamespace(Cn,sn,fn,_n)}};return nn.ensureTerminated(gn),Tn&&nn.ensureTerminated(Tn),nn.setParent(gn,Bn),Bn}}),xn.addFeature("set",function(nn,on,Jr){let rn=nn.parseElement("setCommand",Jr);if(rn){rn.target.scope!=="element"&&nn.raiseParseError(Jr,"variables declared at the feature level must be element scoped.");let cn={start:rn,install:function(sn,fn){rn&&rn.execute(on.makeContext(sn,cn,sn,null))}};return nn.ensureTerminated(rn),cn}}),xn.addFeature("init",function(nn,on,Jr){if(Jr.matchToken("init")){var rn=Jr.matchToken("immediately"),cn=nn.requireElement("commandList",Jr),sn={start:cn,install:function(fn,un){let gn=function(){cn&&cn.execute(on.makeContext(fn,sn,fn,null))};rn?gn():setTimeout(gn,0)}};return nn.ensureTerminated(cn),nn.setParent(cn,sn),sn}}),xn.addFeature("worker",function(nn,on,Jr){if(Jr.matchToken("worker")){nn.raiseParseError(Jr,"In order to use the 'worker' feature, include the _hyperscript worker plugin. See https://hyperscript.org/features/worker/ for more info.");return}}),xn.addFeature("behavior",function(nn,on,Jr){if(Jr.matchToken("behavior")){var rn=nn.requireElement("dotOrColonPath",Jr).evaluate(),cn=rn.split("."),sn=cn.pop(),fn=[];if(Jr.matchOpToken("(")&&!Jr.matchOpToken(")")){do fn.push(Jr.requireTokenType("IDENTIFIER").value);while(Jr.matchOpToken(","));Jr.requireOpToken(")")}for(var un=nn.requireElement("hyperscript",Jr),gn=0;gn<un.features.length;gn++){var En=un.features[gn];En.behavior=rn}return{install:function(Tn,Rn){on.assignToNamespace(Qr.document&&Qr.document.body,cn,sn,function(Bn,Cn,In){for(var _n=on.getInternalData(Bn),Un=jn(_n,rn+"Scope"),ni=0;ni<fn.length;ni++)Un[fn[ni]]=In[fn[ni]];un.apply(Bn,Cn)})}}}}),xn.addFeature("install",function(nn,on,Jr){if(Jr.matchToken("install")){var rn=nn.requireElement("dotOrColonPath",Jr).evaluate(),cn=rn.split("."),sn=nn.parseElement("namedArgumentList",Jr),fn;return fn={install:function(un,gn){on.unifiedEval({args:[sn],op:function(En,Tn){for(var Rn=Qr,Bn=0;Bn<cn.length;Bn++)if(Rn=Rn[cn[Bn]],typeof Rn!="object"&&typeof Rn!="function")throw new Error("No such behavior defined as "+rn);if(!(Rn instanceof Function))throw new Error(rn+" is not a behavior");Rn(un,gn,Tn)}},on.makeContext(un,fn,un,null))}}}}),xn.addGrammarElement("jsBody",function(nn,on,Jr){for(var rn=Jr.currentToken().start,cn=Jr.currentToken(),sn=[],fn="",un=!1;Jr.hasMore();){cn=Jr.consumeToken();var gn=Jr.token(0,!0);if(gn.type==="IDENTIFIER"&&gn.value==="end")break;un?cn.type==="IDENTIFIER"||cn.type==="NUMBER"?fn+=cn.value:(fn!==""&&sn.push(fn),fn="",un=!1):cn.type==="IDENTIFIER"&&cn.value==="function"&&(un=!0)}var En=cn.end+1;return{type:"jsBody",exposedFunctionNames:sn,jsSource:Jr.source.substring(rn,En)}}),xn.addFeature("js",function(nn,on,Jr){if(Jr.matchToken("js")){var rn=nn.requireElement("jsBody",Jr),cn=rn.jsSource+` 14 + return { `+rn.exposedFunctionNames.map(function(fn){return fn+":"+fn}).join(",")+" } ",sn=new Function(cn);return{jsSource:cn,function:sn,exposedFunctionNames:rn.exposedFunctionNames,install:function(){Object.assign(Qr,sn())}}}}),xn.addCommand("js",function(nn,on,Jr){if(Jr.matchToken("js")){var rn=[];if(Jr.matchOpToken("(")&&!Jr.matchOpToken(")")){do{var cn=Jr.requireTokenType("IDENTIFIER");rn.push(cn.value)}while(Jr.matchOpToken(","));Jr.requireOpToken(")")}var sn=nn.requireElement("jsBody",Jr);Jr.matchToken("end");var fn=Ti(Function,rn.concat([sn.jsSource])),un={jsSource:sn.jsSource,function:fn,inputs:rn,op:function(gn){var En=[];rn.forEach(function(Rn){En.push(on.resolveSymbol(Rn,gn,"default"))});var Tn=fn.apply(Qr,En);return Tn&&typeof Tn.then=="function"?new Promise(function(Rn){Tn.then(function(Bn){gn.result=Bn,Rn(on.findNext(this,gn))})}):(gn.result=Tn,on.findNext(this,gn))}};return un}}),xn.addCommand("async",function(nn,on,Jr){if(Jr.matchToken("async")){if(Jr.matchToken("do")){for(var rn=nn.requireElement("commandList",Jr),cn=rn;cn.next;)cn=cn.next;cn.next=on.HALT,Jr.requireToken("end")}else var rn=nn.requireElement("command",Jr);var sn={body:rn,op:function(fn){return setTimeout(function(){rn.execute(fn)}),on.findNext(this,fn)}};return nn.setParent(rn,sn),sn}}),xn.addCommand("tell",function(nn,on,Jr){var rn=Jr.currentToken();if(Jr.matchToken("tell")){var cn=nn.requireElement("expression",Jr),sn=nn.requireElement("commandList",Jr);Jr.hasMore()&&!nn.featureStart(Jr.currentToken())&&Jr.requireToken("end");var fn="tell_"+rn.start,un={value:cn,body:sn,args:[cn],resolveNext:function(gn){var En=gn.meta.iterators[fn];return En.index<En.value.length?(gn.you=En.value[En.index++],sn):(gn.you=En.originalYou,this.next?this.next:on.findNext(this.parent,gn))},op:function(gn,En){return En==null?En=[]:Array.isArray(En)||En instanceof NodeList||(En=[En]),gn.meta.iterators[fn]={originalYou:gn.you,index:0,value:En},this.resolveNext(gn)}};return nn.setParent(sn,un),un}}),xn.addCommand("wait",function(nn,on,Jr){if(Jr.matchToken("wait")){var rn;if(Jr.matchToken("for")){Jr.matchToken("a");var cn=[];do{var sn=Jr.token(0);sn.type==="NUMBER"||sn.type==="L_PAREN"?cn.push({time:nn.requireElement("expression",Jr).evaluate()}):cn.push({name:nn.requireElement("dotOrColonPath",Jr,"Expected event name").evaluate(),args:On(Jr)})}while(Jr.matchToken("or"));if(Jr.matchToken("from"))var fn=nn.requireElement("expression",Jr);return rn={event:cn,on:fn,args:[fn],op:function(gn,En){var Tn=En||gn.me;if(!(Tn instanceof EventTarget))throw new Error("Not a valid event target: "+this.on.sourceFor());return new Promise(Rn=>{var Bn=!1;for(let In of cn){var Cn=_n=>{if(gn.result=_n,In.args)for(let Un of In.args)gn.locals[Un.value]=_n[Un.value]||(_n.detail?_n.detail[Un.value]:null);Bn||(Bn=!0,Rn(on.findNext(this,gn)))};In.name?Tn.addEventListener(In.name,Cn,{once:!0}):In.time!=null&&setTimeout(Cn,In.time,In.time)}})}},rn}else{var un;return Jr.matchToken("a")?(Jr.requireToken("tick"),un=0):un=nn.requireElement("expression",Jr),rn={type:"waitCmd",time:un,args:[un],op:function(gn,En){return new Promise(Tn=>{setTimeout(()=>{Tn(on.findNext(this,gn))},En)})},execute:function(gn){return on.unifiedExec(this,gn)}},rn}}}),xn.addGrammarElement("dotOrColonPath",function(nn,on,Jr){var rn=Jr.matchTokenType("IDENTIFIER");if(rn){var cn=[rn.value],sn=Jr.matchOpToken(".")||Jr.matchOpToken(":");if(sn)do cn.push(Jr.requireTokenType("IDENTIFIER","NUMBER").value);while(Jr.matchOpToken(sn.value));return{type:"dotOrColonPath",path:cn,evaluate:function(){return cn.join(sn?sn.value:"")}}}}),xn.addGrammarElement("eventName",function(nn,on,Jr){var rn;return(rn=Jr.matchTokenType("STRING"))?{evaluate:function(){return rn.value}}:nn.parseElement("dotOrColonPath",Jr)});function Pn(nn,on,Jr,rn){var cn=on.requireElement("eventName",rn),sn=on.parseElement("namedArgumentList",rn);if(nn==="send"&&rn.matchToken("to")||nn==="trigger"&&rn.matchToken("on"))var fn=on.requireElement("expression",rn);else var fn=on.requireElement("implicitMeTarget",rn);var un={eventName:cn,details:sn,to:fn,args:[fn,cn,sn],op:function(gn,En,Tn,Rn){return Jr.nullCheck(En,fn),Jr.implicitLoop(En,function(Bn){Jr.triggerEvent(Bn,Tn,Rn,gn.me)}),Jr.findNext(un,gn)}};return un}xn.addCommand("trigger",function(nn,on,Jr){if(Jr.matchToken("trigger"))return Pn("trigger",nn,on,Jr)}),xn.addCommand("send",function(nn,on,Jr){if(Jr.matchToken("send"))return Pn("send",nn,on,Jr)});var Nn=function(nn,on,Jr,rn){if(rn)if(nn.commandBoundary(Jr.currentToken()))nn.raiseParseError(Jr,"'return' commands must return a value. If you do not wish to return a value, use 'exit' instead.");else var cn=nn.requireElement("expression",Jr);var sn={value:cn,args:[cn],op:function(fn,un){var gn=fn.meta.resolve;return fn.meta.returned=!0,fn.meta.returnValue=un,gn&&(un?gn(un):gn()),on.HALT}};return sn};xn.addCommand("return",function(nn,on,Jr){if(Jr.matchToken("return"))return Nn(nn,on,Jr,!0)}),xn.addCommand("exit",function(nn,on,Jr){if(Jr.matchToken("exit"))return Nn(nn,on,Jr,!1)}),xn.addCommand("halt",function(nn,on,Jr){if(Jr.matchToken("halt")){if(Jr.matchToken("the")){Jr.requireToken("event"),Jr.matchOpToken("'")&&Jr.requireToken("s");var rn=!0}if(Jr.matchToken("bubbling"))var cn=!0;else if(Jr.matchToken("default"))var sn=!0;var fn=Nn(nn,on,Jr,!1),un={keepExecuting:!0,bubbling:cn,haltDefault:sn,exit:fn,op:function(gn){if(gn.event)return cn?gn.event.stopPropagation():(sn||gn.event.stopPropagation(),gn.event.preventDefault()),rn?on.findNext(this,gn):fn}};return un}}),xn.addCommand("log",function(nn,on,Jr){if(Jr.matchToken("log")){for(var rn=[nn.parseElement("expression",Jr)];Jr.matchOpToken(",");)rn.push(nn.requireElement("expression",Jr));if(Jr.matchToken("with"))var cn=nn.requireElement("expression",Jr);var sn={exprs:rn,withExpr:cn,args:[cn,rn],op:function(fn,un,gn){return un?un.apply(null,gn):console.log.apply(null,gn),on.findNext(this,fn)}};return sn}}),xn.addCommand("beep!",function(nn,on,Jr){if(Jr.matchToken("beep!")){for(var rn=[nn.parseElement("expression",Jr)];Jr.matchOpToken(",");)rn.push(nn.requireElement("expression",Jr));var cn={exprs:rn,args:[rn],op:function(sn,fn){for(let un=0;un<rn.length;un++){let gn=rn[un],En=fn[un];on.beepValueToConsole(sn.me,gn,En)}return on.findNext(this,sn)}};return cn}}),xn.addCommand("throw",function(nn,on,Jr){if(Jr.matchToken("throw")){var rn=nn.requireElement("expression",Jr),cn={expr:rn,args:[rn],op:function(sn,fn){throw on.registerHyperTrace(sn,fn),fn}};return cn}});var Dn=function(nn,on,Jr){var rn=nn.requireElement("expression",Jr),cn={expr:rn,args:[rn],op:function(sn,fn){return sn.result=fn,on.findNext(cn,sn)}};return cn};xn.addCommand("call",function(nn,on,Jr){if(Jr.matchToken("call")){var rn=Dn(nn,on,Jr);return rn.expr&&rn.expr.type!=="functionCall"&&nn.raiseParseError(Jr,"Must be a function invocation"),rn}}),xn.addCommand("get",function(nn,on,Jr){if(Jr.matchToken("get"))return Dn(nn,on,Jr)}),xn.addCommand("make",function(nn,on,Jr){if(Jr.matchToken("make")){Jr.matchToken("a")||Jr.matchToken("an");var rn=nn.requireElement("expression",Jr),cn=[];if(rn.type!=="queryRef"&&Jr.matchToken("from"))do cn.push(nn.requireElement("expression",Jr));while(Jr.matchOpToken(","));if(Jr.matchToken("called"))var sn=nn.requireElement("symbol",Jr);var fn;return rn.type==="queryRef"?(fn={op:function(un){for(var gn,En="div",Tn,Rn=[],Bn=/(?:(^|#|\.)([^#\. ]+))/g;gn=Bn.exec(rn.css);)gn[1]===""?En=gn[2].trim():gn[1]==="#"?Tn=gn[2].trim():Rn.push(gn[2].trim());var Cn=document.createElement(En);Tn!==void 0&&(Cn.id=Tn);for(var In=0;In<Rn.length;In++){var _n=Rn[In];Cn.classList.add(_n)}return un.result=Cn,sn&&on.setSymbol(sn.name,un,sn.scope,Cn),on.findNext(this,un)}},fn):(fn={args:[rn,cn],op:function(un,gn,En){return un.result=Ti(gn,En),sn&&on.setSymbol(sn.name,un,sn.scope,un.result),on.findNext(this,un)}},fn)}}),xn.addGrammarElement("pseudoCommand",function(nn,on,Jr){let rn=Jr.token(1);if(!(rn&&rn.op&&(rn.value==="."||rn.value==="(")))return null;for(var cn=nn.requireElement("primaryExpression",Jr),sn=cn.root,fn=cn;sn.root!=null;)fn=fn.root,sn=sn.root;if(cn.type!=="functionCall"&&nn.raiseParseError(Jr,"Pseudo-commands must be function calls"),fn.type==="functionCall"&&fn.root.root==null){if(Jr.matchAnyToken("the","to","on","with","into","from","at"))var un=nn.requireElement("expression",Jr);else if(Jr.matchToken("me"))var un=nn.requireElement("implicitMeTarget",Jr)}var gn;return un?gn={type:"pseudoCommand",root:un,argExressions:fn.argExressions,args:[un,fn.argExressions],op:function(En,Tn,Rn){on.nullCheck(Tn,un);var Bn=Tn[fn.root.name];return on.nullCheck(Bn,fn),Bn.hyperfunc&&Rn.push(En),En.result=Bn.apply(Tn,Rn),on.findNext(gn,En)},execute:function(En){return on.unifiedExec(this,En)}}:gn={type:"pseudoCommand",expr:cn,args:[cn],op:function(En,Tn){return En.result=Tn,on.findNext(gn,En)},execute:function(En){return on.unifiedExec(this,En)}},gn});var Ln=function(nn,on,Jr,rn,cn){var sn=rn.type==="symbol",fn=rn.type==="attributeRef",un=rn.type==="styleRef",gn=rn.type==="arrayIndex";!(fn||un||sn)&&rn.root==null&&nn.raiseParseError(Jr,"Can only put directly into symbols, not references");var En=null,Tn=null;if(!sn)if(fn||un){En=nn.requireElement("implicitMeTarget",Jr);var Rn=rn}else if(gn)Tn=rn.firstIndex,En=rn.root;else{Tn=rn.prop?rn.prop.value:null;var Rn=rn.attribute;En=rn.root}var Bn={target:rn,symbolWrite:sn,value:cn,args:[En,Tn,cn],op:function(Cn,In,_n,Un){return sn?on.setSymbol(rn.name,Cn,rn.scope,Un):(on.nullCheck(In,En),gn?In[_n]=Un:on.implicitLoop(In,function(ni){Rn?Rn.type==="attributeRef"?Un==null?ni.removeAttribute(Rn.name):ni.setAttribute(Rn.name,Un):ni.style[Rn.name]=Un:ni[_n]=Un})),on.findNext(this,Cn)}};return Bn};xn.addCommand("default",function(nn,on,Jr){if(Jr.matchToken("default")){var rn=nn.requireElement("assignableExpression",Jr);Jr.requireToken("to");var cn=nn.requireElement("expression",Jr),sn=Ln(nn,on,Jr,rn,cn),fn={target:rn,value:cn,setter:sn,args:[rn],op:function(un,gn){return gn?on.findNext(this,un):sn}};return sn.parent=fn,fn}}),xn.addCommand("set",function(nn,on,Jr){if(Jr.matchToken("set")){if(Jr.currentToken().type==="L_BRACE"){var rn=nn.requireElement("objectLiteral",Jr);Jr.requireToken("on");var cn=nn.requireElement("expression",Jr),sn={objectLiteral:rn,target:cn,args:[rn,cn],op:function(un,gn,En){return Object.assign(En,gn),on.findNext(this,un)}};return sn}try{Jr.pushFollow("to");var cn=nn.requireElement("assignableExpression",Jr)}finally{Jr.popFollow()}Jr.requireToken("to");var fn=nn.requireElement("expression",Jr);return Ln(nn,on,Jr,cn,fn)}}),xn.addCommand("if",function(nn,on,Jr){if(!Jr.matchToken("if"))return;var rn=nn.requireElement("expression",Jr);Jr.matchToken("then");var cn=nn.parseElement("commandList",Jr),sn=!1;let fn=Jr.matchToken("else")||Jr.matchToken("otherwise");if(fn){let En=Jr.peekToken("if");if(sn=En!=null&&En.line===fn.line,sn)var un=nn.parseElement("command",Jr);else var un=nn.parseElement("commandList",Jr)}Jr.hasMore()&&!sn&&Jr.requireToken("end");var gn={expr:rn,trueBranch:cn,falseBranch:un,args:[rn],op:function(En,Tn){return Tn?cn:un||on.findNext(this,En)}};return nn.setParent(cn,gn),nn.setParent(un,gn),gn});var Fn=function(nn,on,Jr,rn){var cn=on.currentToken(),sn;if(on.matchToken("for")||rn){var fn=on.requireTokenType("IDENTIFIER");sn=fn.value,on.requireToken("in");var un=nn.requireElement("expression",on)}else if(on.matchToken("in")){sn="it";var un=nn.requireElement("expression",on)}else if(on.matchToken("while"))var gn=nn.requireElement("expression",on);else if(on.matchToken("until")){var En=!0;if(on.matchToken("event")){var Tn=nn.requireElement("dotOrColonPath",on,"Expected event name");if(on.matchToken("from"))var Rn=nn.requireElement("expression",on)}else var gn=nn.requireElement("expression",on)}else if(!nn.commandBoundary(on.currentToken())&&on.currentToken().value!=="forever"){var Bn=nn.requireElement("expression",on);on.requireToken("times")}else{on.matchToken("forever");var Cn=!0}if(on.matchToken("index"))var fn=on.requireTokenType("IDENTIFIER"),In=fn.value;else if(on.matchToken("indexed")){on.requireToken("by");var fn=on.requireTokenType("IDENTIFIER"),In=fn.value}var _n=nn.parseElement("commandList",on);if(_n&&Tn){for(var Un=_n;Un.next;)Un=Un.next;var ni={type:"waitATick",op:function(){return new Promise(function(si){setTimeout(function(){si(Jr.findNext(ni))},0)})}};Un.next=ni}if(on.hasMore()&&on.requireToken("end"),sn==null){sn="_implicit_repeat_"+cn.start;var ii=sn}else var ii=sn+"_"+cn.start;var Ri={identifier:sn,indexIdentifier:In,slot:ii,expression:un,forever:Cn,times:Bn,until:En,event:Tn,on:Rn,whileExpr:gn,resolveNext:function(){return this},loop:_n,args:[gn,Bn],op:function(si,ki,Hi){var Ci=si.meta.iterators[ii],Ni=!1,Ca=null;if(this.forever)Ni=!0;else if(this.until)Tn?Ni=si.meta.iterators[ii].eventFired===!1:Ni=ki!==!0;else if(gn)Ni=ki;else if(Hi)Ni=Ci.index<Hi;else{var _a=Ci.iterator.next();Ni=!_a.done,Ca=_a.value}return Ni?(Ci.value?si.result=si.locals[sn]=Ca:si.result=Ci.index,In&&(si.locals[In]=Ci.index),Ci.index++,_n):(si.meta.iterators[ii]=null,Jr.findNext(this.parent,si))}};nn.setParent(_n,Ri);var Bi={name:"repeatInit",args:[un,Tn,Rn],op:function(si,ki,Hi,Ci){var Ni={index:0,value:ki,eventFired:!1};if(si.meta.iterators[ii]=Ni,ki&&ki[Symbol.iterator]&&(Ni.iterator=ki[Symbol.iterator]()),Tn){var Ca=Ci||si.me;Ca.addEventListener(Hi,function(_a){si.meta.iterators[ii].eventFired=!0},{once:!0})}return Ri},execute:function(si){return Jr.unifiedExec(this,si)}};return nn.setParent(Ri,Bi),Bi};xn.addCommand("repeat",function(nn,on,Jr){if(Jr.matchToken("repeat"))return Fn(nn,Jr,on,!1)}),xn.addCommand("for",function(nn,on,Jr){if(Jr.matchToken("for"))return Fn(nn,Jr,on,!0)}),xn.addCommand("continue",function(nn,on,Jr){if(Jr.matchToken("continue")){var rn={op:function(cn){for(var sn=this.parent;;sn=sn.parent)if(sn==null&&nn.raiseParseError(Jr,"Command `continue` cannot be used outside of a `repeat` loop."),sn.loop!=null)return sn.resolveNext(cn)}};return rn}}),xn.addCommand("break",function(nn,on,Jr){if(Jr.matchToken("break")){var rn={op:function(cn){for(var sn=this.parent;;sn=sn.parent)if(sn==null&&nn.raiseParseError(Jr,"Command `continue` cannot be used outside of a `repeat` loop."),sn.loop!=null)return on.findNext(sn.parent,cn)}};return rn}}),xn.addGrammarElement("stringLike",function(nn,on,Jr){return nn.parseAnyOf(["string","nakedString"],Jr)}),xn.addCommand("append",function(nn,on,Jr){if(Jr.matchToken("append")){var rn=null,cn=nn.requireElement("expression",Jr),sn={type:"symbol",evaluate:function(gn){return on.resolveSymbol("result",gn)}};Jr.matchToken("to")?rn=nn.requireElement("expression",Jr):rn=sn;var fn=null;(rn.type==="symbol"||rn.type==="attributeRef"||rn.root!=null)&&(fn=Ln(nn,on,Jr,rn,sn));var un={value:cn,target:rn,args:[rn,cn],op:function(gn,En,Tn){if(Array.isArray(En))return En.push(Tn),on.findNext(this,gn);if(En instanceof Element)return Tn instanceof Element?En.insertAdjacentElement("beforeend",Tn):En.insertAdjacentHTML("beforeend",Tn),on.processNode(En),on.findNext(this,gn);if(fn)return gn.result=(En||"")+Tn,fn;throw Error("Unable to append a value!")},execute:function(gn){return on.unifiedExec(this,gn)}};return fn!=null&&(fn.parent=un),un}});function Mn(nn,on,Jr){Jr.matchToken("at")||Jr.matchToken("from");let rn={includeStart:!0,includeEnd:!1};return rn.from=Jr.matchToken("start")?0:nn.requireElement("expression",Jr),(Jr.matchToken("to")||Jr.matchOpToken(".."))&&(Jr.matchToken("end")?rn.toEnd=!0:rn.to=nn.requireElement("expression",Jr)),Jr.matchToken("inclusive")?rn.includeEnd=!0:Jr.matchToken("exclusive")&&(rn.includeStart=!1),rn}class Hn{constructor(on,Jr){this.re=on,this.str=Jr}next(){let on=this.re.exec(this.str);return on===null?{done:!0}:{value:on}}}class Wn{constructor(on,Jr,rn){this.re=on,this.flags=Jr,this.str=rn}[Symbol.iterator](){return new Hn(new RegExp(this.re,this.flags),this.str)}}xn.addCommand("pick",(nn,on,Jr)=>{if(Jr.matchToken("pick")){if(Jr.matchToken("the"),Jr.matchToken("item")||Jr.matchToken("items")||Jr.matchToken("character")||Jr.matchToken("characters")){let rn=Mn(nn,on,Jr);return Jr.requireToken("from"),{args:[nn.requireElement("expression",Jr),rn.from,rn.to],op(sn,fn,un,gn){return rn.toEnd&&(gn=fn.length),rn.includeStart||un++,rn.includeEnd&&gn++,(gn==null||gn==null)&&(gn=un+1),sn.result=fn.slice(un,gn),on.findNext(this,sn)}}}if(Jr.matchToken("match")){Jr.matchToken("of");let rn=nn.parseElement("expression",Jr),cn="";return Jr.matchOpToken("|")&&(cn=Jr.requireTokenType("IDENTIFIER").value),Jr.requireToken("from"),{args:[nn.parseElement("expression",Jr),rn],op(fn,un,gn){return fn.result=new RegExp(gn,cn).exec(un),on.findNext(this,fn)}}}if(Jr.matchToken("matches")){Jr.matchToken("of");let rn=nn.parseElement("expression",Jr),cn="gu";return Jr.matchOpToken("|")&&(cn="g"+Jr.requireTokenType("IDENTIFIER").value.replace("g","")),Jr.requireToken("from"),{args:[nn.parseElement("expression",Jr),rn],op(fn,un,gn){return fn.result=new Wn(gn,cn,un),on.findNext(this,fn)}}}}}),xn.addCommand("increment",function(nn,on,Jr){if(Jr.matchToken("increment")){var rn,cn=nn.parseElement("assignableExpression",Jr);Jr.matchToken("by")&&(rn=nn.requireElement("expression",Jr));var sn={type:"implicitIncrementOp",target:cn,args:[cn,rn],op:function(fn,un,gn){un=un?parseFloat(un):0,gn=rn?parseFloat(gn):1;var En=un+gn;return fn.result=En,En},evaluate:function(fn){return on.unifiedEval(this,fn)}};return Ln(nn,on,Jr,cn,sn)}}),xn.addCommand("decrement",function(nn,on,Jr){if(Jr.matchToken("decrement")){var rn,cn=nn.parseElement("assignableExpression",Jr);Jr.matchToken("by")&&(rn=nn.requireElement("expression",Jr));var sn={type:"implicitDecrementOp",target:cn,args:[cn,rn],op:function(fn,un,gn){un=un?parseFloat(un):0,gn=rn?parseFloat(gn):1;var En=un-gn;return fn.result=En,En},evaluate:function(fn){return on.unifiedEval(this,fn)}};return Ln(nn,on,Jr,cn,sn)}});function zn(nn,on){var Jr="text",rn;return nn.matchToken("a")||nn.matchToken("an"),nn.matchToken("json")||nn.matchToken("Object")?Jr="json":nn.matchToken("response")?Jr="response":nn.matchToken("html")?Jr="html":nn.matchToken("text")||(rn=on.requireElement("dotOrColonPath",nn).evaluate()),{type:Jr,conversion:rn}}xn.addCommand("fetch",function(nn,on,Jr){if(Jr.matchToken("fetch")){var rn=nn.requireElement("stringLike",Jr);if(Jr.matchToken("as"))var cn=zn(Jr,nn);if(Jr.matchToken("with")&&Jr.currentToken().value!=="{")var sn=nn.parseElement("nakedNamedArgumentList",Jr);else var sn=nn.parseElement("objectLiteral",Jr);cn==null&&Jr.matchToken("as")&&(cn=zn(Jr,nn));var fn=cn?cn.type:"text",un=cn?cn.conversion:null,gn={url:rn,argExpressions:sn,args:[rn,sn],op:function(En,Tn,Rn){var Bn=Rn||{};Bn.sender=En.me,Bn.headers=Bn.headers||{};var Cn=new AbortController;let In=En.me.addEventListener("fetch:abort",function(){Cn.abort()},{once:!0});Bn.signal=Cn.signal,on.triggerEvent(En.me,"hyperscript:beforeFetch",Bn),on.triggerEvent(En.me,"fetch:beforeRequest",Bn),Rn=Bn;var _n=!1;return Rn.timeout&&setTimeout(function(){_n||Cn.abort()},Rn.timeout),fetch(Tn,Rn).then(function(Un){let ni={response:Un};return on.triggerEvent(En.me,"fetch:afterResponse",ni),Un=ni.response,fn==="response"?(En.result=Un,on.triggerEvent(En.me,"fetch:afterRequest",{result:Un}),_n=!0,on.findNext(gn,En)):fn==="json"?Un.json().then(function(ii){return En.result=ii,on.triggerEvent(En.me,"fetch:afterRequest",{result:ii}),_n=!0,on.findNext(gn,En)}):Un.text().then(function(ii){return un&&(ii=on.convertValue(ii,un)),fn==="html"&&(ii=on.convertValue(ii,"Fragment")),En.result=ii,on.triggerEvent(En.me,"fetch:afterRequest",{result:ii}),_n=!0,on.findNext(gn,En)})}).catch(function(Un){throw on.triggerEvent(En.me,"fetch:error",{reason:Un}),Un}).finally(function(){En.me.removeEventListener("fetch:abort",In)})}};return gn}})}function oi(xn){xn.addCommand("settle",function(vn,bn,hn){if(hn.matchToken("settle")){if(vn.commandBoundary(hn.currentToken()))var On=vn.requireElement("implicitMeTarget",hn);else var On=vn.requireElement("expression",hn);var Pn={type:"settleCmd",args:[On],op:function(Nn,Dn){bn.nullCheck(Dn,On);var Ln=null,Fn=!1,Mn=!1,Hn=new Promise(function(Wn){Ln=Wn});return Dn.addEventListener("transitionstart",function(){Mn=!0},{once:!0}),setTimeout(function(){!Mn&&!Fn&&Ln(bn.findNext(Pn,Nn))},500),Dn.addEventListener("transitionend",function(){Fn||Ln(bn.findNext(Pn,Nn))},{once:!0}),Hn},execute:function(Nn){return bn.unifiedExec(this,Nn)}};return Pn}}),xn.addCommand("add",function(vn,bn,hn){if(hn.matchToken("add")){var On=vn.parseElement("classRef",hn),Pn=null,Nn=null;if(On==null)Pn=vn.parseElement("attributeRef",hn),Pn==null&&(Nn=vn.parseElement("styleLiteral",hn),Nn==null&&vn.raiseParseError(hn,"Expected either a class reference or attribute expression"));else for(var Dn=[On];On=vn.parseElement("classRef",hn);)Dn.push(On);if(hn.matchToken("to"))var Ln=vn.requireElement("expression",hn);else var Ln=vn.requireElement("implicitMeTarget",hn);if(hn.matchToken("when")){Nn&&vn.raiseParseError(hn,"Only class and properties are supported with a when clause");var Fn=vn.requireElement("expression",hn)}return Dn?{classRefs:Dn,to:Ln,args:[Ln,Dn],op:function(Mn,Hn,Wn){return bn.nullCheck(Hn,Ln),bn.forEach(Wn,function(zn){bn.implicitLoop(Hn,function(nn){Fn?(Mn.result=nn,bn.evaluateNoPromise(Fn,Mn)?nn instanceof Element&&nn.classList.add(zn.className):nn instanceof Element&&nn.classList.remove(zn.className),Mn.result=null):nn instanceof Element&&nn.classList.add(zn.className)})}),bn.findNext(this,Mn)}}:Pn?{type:"addCmd",attributeRef:Pn,to:Ln,args:[Ln],op:function(Mn,Hn,Wn){return bn.nullCheck(Hn,Ln),bn.implicitLoop(Hn,function(zn){Fn?(Mn.result=zn,bn.evaluateNoPromise(Fn,Mn)?zn.setAttribute(Pn.name,Pn.value):zn.removeAttribute(Pn.name),Mn.result=null):zn.setAttribute(Pn.name,Pn.value)}),bn.findNext(this,Mn)},execute:function(Mn){return bn.unifiedExec(this,Mn)}}:{type:"addCmd",cssDeclaration:Nn,to:Ln,args:[Ln,Nn],op:function(Mn,Hn,Wn){return bn.nullCheck(Hn,Ln),bn.implicitLoop(Hn,function(zn){zn.style.cssText+=Wn}),bn.findNext(this,Mn)},execute:function(Mn){return bn.unifiedExec(this,Mn)}}}}),xn.addGrammarElement("styleLiteral",function(vn,bn,hn){if(hn.matchOpToken("{")){for(var On=[""],Pn=[];hn.hasMore();){if(hn.matchOpToken("\\"))hn.consumeToken();else{if(hn.matchOpToken("}"))break;if(hn.matchToken("$")){var Nn=hn.matchOpToken("{"),Dn=vn.parseElement("expression",hn);Nn&&hn.requireOpToken("}"),Pn.push(Dn),On.push("")}else{var Ln=hn.consumeToken();On[On.length-1]+=hn.source.substring(Ln.start,Ln.end)}}On[On.length-1]+=hn.lastWhitespace()}return{type:"styleLiteral",args:[Pn],op:function(Fn,Mn){var Hn="";return On.forEach(function(Wn,zn){Hn+=Wn,zn in Mn&&(Hn+=Mn[zn])}),Hn},evaluate:function(Fn){return bn.unifiedEval(this,Fn)}}}}),xn.addCommand("remove",function(vn,bn,hn){if(hn.matchToken("remove")){var On=vn.parseElement("classRef",hn),Pn=null,Nn=null;if(On==null)Pn=vn.parseElement("attributeRef",hn),Pn==null&&(Nn=vn.parseElement("expression",hn),Nn==null&&vn.raiseParseError(hn,"Expected either a class reference, attribute expression or value expression"));else for(var Dn=[On];On=vn.parseElement("classRef",hn);)Dn.push(On);if(hn.matchToken("from"))var Ln=vn.requireElement("expression",hn);else if(Nn==null)var Ln=vn.requireElement("implicitMeTarget",hn);return Nn?{elementExpr:Nn,from:Ln,args:[Nn,Ln],op:function(Fn,Mn,Hn){return bn.nullCheck(Mn,Nn),bn.implicitLoop(Mn,function(Wn){Wn.parentElement&&(Hn==null||Hn.contains(Wn))&&Wn.parentElement.removeChild(Wn)}),bn.findNext(this,Fn)}}:{classRefs:Dn,attributeRef:Pn,elementExpr:Nn,from:Ln,args:[Dn,Ln],op:function(Fn,Mn,Hn){return bn.nullCheck(Hn,Ln),Mn?bn.forEach(Mn,function(Wn){bn.implicitLoop(Hn,function(zn){zn.classList.remove(Wn.className)})}):bn.implicitLoop(Hn,function(Wn){Wn.removeAttribute(Pn.name)}),bn.findNext(this,Fn)}}}}),xn.addCommand("toggle",function(vn,bn,hn){if(hn.matchToken("toggle")){if(hn.matchAnyToken("the","my"),hn.currentToken().type==="STYLE_REF"){var On=hn.consumeToken().value.substr(1),Pn=!0,Nn=pn(vn,hn,On);if(hn.matchToken("of")){hn.pushFollow("with");try{var Dn=vn.requireElement("expression",hn)}finally{hn.popFollow()}}else var Dn=vn.requireElement("implicitMeTarget",hn)}else if(hn.matchToken("between")){var Ln=!0,Fn=vn.parseElement("classRef",hn);hn.requireToken("and");var Mn=vn.requireElement("classRef",hn)}else{var Fn=vn.parseElement("classRef",hn),Hn=null;if(Fn==null)Hn=vn.parseElement("attributeRef",hn),Hn==null&&vn.raiseParseError(hn,"Expected either a class reference or attribute expression");else for(var Wn=[Fn];Fn=vn.parseElement("classRef",hn);)Wn.push(Fn)}if(Pn!==!0)if(hn.matchToken("on"))var Dn=vn.requireElement("expression",hn);else var Dn=vn.requireElement("implicitMeTarget",hn);if(hn.matchToken("for"))var zn=vn.requireElement("expression",hn);else if(hn.matchToken("until")){var nn=vn.requireElement("dotOrColonPath",hn,"Expected event name");if(hn.matchToken("from"))var on=vn.requireElement("expression",hn)}var Jr={classRef:Fn,classRef2:Mn,classRefs:Wn,attributeRef:Hn,on:Dn,time:zn,evt:nn,from:on,toggle:function(rn,cn,sn,fn){bn.nullCheck(rn,Dn),Pn?bn.implicitLoop(rn,function(un){Nn("toggle",un)}):Ln?bn.implicitLoop(rn,function(un){un.classList.contains(cn.className)?(un.classList.remove(cn.className),un.classList.add(sn.className)):(un.classList.add(cn.className),un.classList.remove(sn.className))}):fn?bn.forEach(fn,function(un){bn.implicitLoop(rn,function(gn){gn.classList.toggle(un.className)})}):bn.implicitLoop(rn,function(un){un.hasAttribute(Hn.name)?un.removeAttribute(Hn.name):un.setAttribute(Hn.name,Hn.value)})},args:[Dn,zn,nn,on,Fn,Mn,Wn],op:function(rn,cn,sn,fn,un,gn,En,Tn){return sn?new Promise(function(Rn){Jr.toggle(cn,gn,En,Tn),setTimeout(function(){Jr.toggle(cn,gn,En,Tn),Rn(bn.findNext(Jr,rn))},sn)}):fn?new Promise(function(Rn){var Bn=un||rn.me;Bn.addEventListener(fn,function(){Jr.toggle(cn,gn,En,Tn),Rn(bn.findNext(Jr,rn))},{once:!0}),Jr.toggle(cn,gn,En,Tn)}):(this.toggle(cn,gn,En,Tn),bn.findNext(Jr,rn))}};return Jr}});var en={display:function(vn,bn,hn){if(hn)bn.style.display=hn;else if(vn==="toggle")getComputedStyle(bn).display==="none"?en.display("show",bn,hn):en.display("hide",bn,hn);else if(vn==="hide"){let On=xn.runtime.getInternalData(bn);On.originalDisplay==null&&(On.originalDisplay=bn.style.display),bn.style.display="none"}else{let On=xn.runtime.getInternalData(bn);On.originalDisplay&&On.originalDisplay!=="none"?bn.style.display=On.originalDisplay:bn.style.removeProperty("display")}},visibility:function(vn,bn,hn){hn?bn.style.visibility=hn:vn==="toggle"?getComputedStyle(bn).visibility==="hidden"?en.visibility("show",bn,hn):en.visibility("hide",bn,hn):vn==="hide"?bn.style.visibility="hidden":bn.style.visibility="visible"},opacity:function(vn,bn,hn){hn?bn.style.opacity=hn:vn==="toggle"?getComputedStyle(bn).opacity==="0"?en.opacity("show",bn,hn):en.opacity("hide",bn,hn):vn==="hide"?bn.style.opacity="0":bn.style.opacity="1"}},an=function(vn,bn,hn){var On,Pn=hn.currentToken();return Pn.value==="when"||Pn.value==="with"||vn.commandBoundary(Pn)?On=vn.parseElement("implicitMeTarget",hn):On=vn.parseElement("expression",hn),On},pn=function(vn,bn,hn){var On=Wr.defaultHideShowStrategy,Pn=en;Wr.hideShowStrategies&&(Pn=Object.assign(Pn,Wr.hideShowStrategies)),hn=hn||On||"display";var Nn=Pn[hn];return Nn==null&&vn.raiseParseError(bn,"Unknown show/hide strategy : "+hn),Nn};xn.addCommand("hide",function(vn,bn,hn){if(hn.matchToken("hide")){var On=an(vn,bn,hn),Pn=null;hn.matchToken("with")&&(Pn=hn.requireTokenType("IDENTIFIER","STYLE_REF").value,Pn.indexOf("*")===0&&(Pn=Pn.substr(1)));var Nn=pn(vn,hn,Pn);return{target:On,args:[On],op:function(Dn,Ln){return bn.nullCheck(Ln,On),bn.implicitLoop(Ln,function(Fn){Nn("hide",Fn)}),bn.findNext(this,Dn)}}}}),xn.addCommand("show",function(vn,bn,hn){if(hn.matchToken("show")){var On=an(vn,bn,hn),Pn=null;hn.matchToken("with")&&(Pn=hn.requireTokenType("IDENTIFIER","STYLE_REF").value,Pn.indexOf("*")===0&&(Pn=Pn.substr(1)));var Nn=null;if(hn.matchOpToken(":")){var Dn=hn.consumeUntilWhitespace();hn.matchTokenType("WHITESPACE"),Nn=Dn.map(function(Mn){return Mn.value}).join("")}if(hn.matchToken("when"))var Ln=vn.requireElement("expression",hn);var Fn=pn(vn,hn,Pn);return{target:On,when:Ln,args:[On],op:function(Mn,Hn){return bn.nullCheck(Hn,On),bn.implicitLoop(Hn,function(Wn){Ln?(Mn.result=Wn,bn.evaluateNoPromise(Ln,Mn)?Fn("show",Wn,Nn):Fn("hide",Wn),Mn.result=null):Fn("show",Wn,Nn)}),bn.findNext(this,Mn)}}}}),xn.addCommand("take",function(vn,bn,hn){if(hn.matchToken("take")){let Fn=null,Mn=[];for(;Fn=vn.parseElement("classRef",hn);)Mn.push(Fn);var On=null,Pn=null;let Hn=Mn.length>0;if(Hn||(On=vn.parseElement("attributeRef",hn),On==null&&vn.raiseParseError(hn,"Expected either a class reference or attribute expression"),hn.matchToken("with")&&(Pn=vn.requireElement("expression",hn))),hn.matchToken("from"))var Nn=vn.requireElement("expression",hn);if(hn.matchToken("for"))var Dn=vn.requireElement("expression",hn);else var Dn=vn.requireElement("implicitMeTarget",hn);if(Hn){var Ln={classRefs:Mn,from:Nn,forElt:Dn,args:[Mn,Nn,Dn],op:function(Wn,zn,nn,on){return bn.nullCheck(on,Dn),bn.implicitLoop(zn,function(Jr){var rn=Jr.className;nn?bn.implicitLoop(nn,function(cn){cn.classList.remove(rn)}):bn.implicitLoop(Jr,function(cn){cn.classList.remove(rn)}),bn.implicitLoop(on,function(cn){cn.classList.add(rn)})}),bn.findNext(this,Wn)}};return Ln}else{var Ln={attributeRef:On,from:Nn,forElt:Dn,args:[Nn,Dn,Pn],op:function(zn,nn,on,Jr){return bn.nullCheck(nn,Nn),bn.nullCheck(on,Dn),bn.implicitLoop(nn,function(rn){Jr?rn.setAttribute(On.name,Jr):rn.removeAttribute(On.name)}),bn.implicitLoop(on,function(rn){rn.setAttribute(On.name,On.value||"")}),bn.findNext(this,zn)}};return Ln}}});function mn(vn,bn,hn,On){if(hn!=null)var Pn=vn.resolveSymbol(hn,bn);else var Pn=bn;if(Pn instanceof Element||Pn instanceof HTMLDocument){for(;Pn.firstChild;)Pn.removeChild(Pn.firstChild);Pn.append(xn.runtime.convertValue(On,"Fragment")),vn.processNode(Pn)}else if(hn!=null)vn.setSymbol(hn,bn,null,On);else throw"Don't know how to put a value into "+typeof bn}xn.addCommand("put",function(vn,bn,hn){if(hn.matchToken("put")){var On=vn.requireElement("expression",hn),Pn=hn.matchAnyToken("into","before","after");Pn==null&&hn.matchToken("at")&&(hn.matchToken("the"),Pn=hn.matchAnyToken("start","end"),hn.requireToken("of")),Pn==null&&vn.raiseParseError(hn,"Expected one of 'into', 'before', 'at start of', 'at end of', 'after'");var Nn=vn.requireElement("expression",hn),Dn=Pn.value,Ln=!1,Fn=!1,Mn=null,Hn=null;if(Nn.type==="arrayIndex"&&Dn==="into")Ln=!0,Hn=Nn.prop,Mn=Nn.root;else if(Nn.prop&&Nn.root&&Dn==="into")Hn=Nn.prop.value,Mn=Nn.root;else if(Nn.type==="symbol"&&Dn==="into")Fn=!0,Hn=Nn.name;else if(Nn.type==="attributeRef"&&Dn==="into"){var Wn=!0;Hn=Nn.name,Mn=vn.requireElement("implicitMeTarget",hn)}else if(Nn.type==="styleRef"&&Dn==="into"){var zn=!0;Hn=Nn.name,Mn=vn.requireElement("implicitMeTarget",hn)}else if(Nn.attribute&&Dn==="into"){var Wn=Nn.attribute.type==="attributeRef",zn=Nn.attribute.type==="styleRef";Hn=Nn.attribute.name,Mn=Nn.root}else Mn=Nn;var nn={target:Nn,operation:Dn,symbolWrite:Fn,value:On,args:[Mn,Hn,On],op:function(on,Jr,rn,cn){if(Fn)mn(bn,on,rn,cn);else if(bn.nullCheck(Jr,Mn),Dn==="into")Wn?bn.implicitLoop(Jr,function(fn){fn.setAttribute(rn,cn)}):zn?bn.implicitLoop(Jr,function(fn){fn.style[rn]=cn}):Ln?Jr[rn]=cn:bn.implicitLoop(Jr,function(fn){mn(bn,fn,rn,cn)});else{var sn=Dn==="before"?Element.prototype.before:Dn==="after"?Element.prototype.after:Dn==="start"?Element.prototype.prepend:Element.prototype.append;bn.implicitLoop(Jr,function(fn){sn.call(fn,cn instanceof Node?cn:bn.convertValue(cn,"Fragment")),fn.parentElement?bn.processNode(fn.parentElement):bn.processNode(fn)})}return bn.findNext(this,on)}};return nn}});function Sn(vn,bn,hn){var On;if(hn.matchToken("the")||hn.matchToken("element")||hn.matchToken("elements")||hn.currentToken().type==="CLASS_REF"||hn.currentToken().type==="ID_REF"||hn.currentToken().op&&hn.currentToken().value==="<"){vn.possessivesDisabled=!0;try{On=vn.parseElement("expression",hn)}finally{delete vn.possessivesDisabled}hn.matchOpToken("'")&&hn.requireToken("s")}else if(hn.currentToken().type==="IDENTIFIER"&&hn.currentToken().value==="its"){var Pn=hn.matchToken("its");On={type:"pseudopossessiveIts",token:Pn,name:Pn.value,evaluate:function(Nn){return bn.resolveSymbol("it",Nn)}}}else hn.matchToken("my")||hn.matchToken("me"),On=vn.parseElement("implicitMeTarget",hn);return On}xn.addCommand("transition",function(vn,bn,hn){if(hn.matchToken("transition")){for(var On=Sn(vn,bn,hn),Pn=[],Nn=[],Dn=[],Ln=hn.currentToken();!vn.commandBoundary(Ln)&&Ln.value!=="over"&&Ln.value!=="using";){if(hn.currentToken().type==="STYLE_REF"){let zn=hn.consumeToken().value.substr(1);Pn.push({type:"styleRefValue",evaluate:function(){return zn}})}else Pn.push(vn.requireElement("stringLike",hn));hn.matchToken("from")?Nn.push(vn.requireElement("expression",hn)):Nn.push(null),hn.requireToken("to"),hn.matchToken("initial")?Dn.push({type:"initial_literal",evaluate:function(){return"initial"}}):Dn.push(vn.requireElement("expression",hn)),Ln=hn.currentToken()}if(hn.matchToken("over"))var Fn=vn.requireElement("expression",hn);else if(hn.matchToken("using"))var Mn=vn.requireElement("expression",hn);var Hn={to:Dn,args:[On,Pn,Nn,Dn,Mn,Fn],op:function(Wn,zn,nn,on,Jr,rn,cn){bn.nullCheck(zn,On);var sn=[];return bn.implicitLoop(zn,function(fn){var un=new Promise(function(gn,En){var Tn=fn.style.transition;cn?fn.style.transition="all "+cn+"ms ease-in":rn?fn.style.transition=rn:fn.style.transition=Wr.defaultTransition;for(var Rn=bn.getInternalData(fn),Bn=getComputedStyle(fn),Cn={},In=0;In<Bn.length;In++){var _n=Bn[In],Un=Bn[_n];Cn[_n]=Un}Rn.initialStyles||(Rn.initialStyles=Cn);for(var In=0;In<nn.length;In++){var ni=nn[In],ii=on[In];ii==="computed"||ii==null?fn.style[ni]=Cn[ni]:fn.style[ni]=ii}var Ri=!1,Bi=!1;fn.addEventListener("transitionend",function(){Bi||(fn.style.transition=Tn,Bi=!0,gn())},{once:!0}),fn.addEventListener("transitionstart",function(){Ri=!0},{once:!0}),setTimeout(function(){!Bi&&!Ri&&(fn.style.transition=Tn,Bi=!0,gn())},100),setTimeout(function(){for(var si=[],ki=0;ki<nn.length;ki++){var Hi=nn[ki],Ci=Jr[ki];if(Ci==="initial"){var Ni=Rn.initialStyles[Hi];fn.style[Hi]=Ni}else fn.style[Hi]=Ci}},0)});sn.push(un)}),Promise.all(sn).then(function(){return bn.findNext(Hn,Wn)})}};return Hn}}),xn.addCommand("measure",function(vn,bn,hn){if(hn.matchToken("measure")){var On=Sn(vn,bn,hn),Pn=[];if(!vn.commandBoundary(hn.currentToken()))do Pn.push(hn.matchTokenType("IDENTIFIER").value);while(hn.matchOpToken(","));return{properties:Pn,args:[On],op:function(Nn,Dn){bn.nullCheck(Dn,On),0 in Dn&&(Dn=Dn[0]);var Ln=Dn.getBoundingClientRect(),Fn={top:Dn.scrollTop,left:Dn.scrollLeft,topMax:Dn.scrollTopMax,leftMax:Dn.scrollLeftMax,height:Dn.scrollHeight,width:Dn.scrollWidth};return Nn.result={x:Ln.x,y:Ln.y,left:Ln.left,top:Ln.top,right:Ln.right,bottom:Ln.bottom,width:Ln.width,height:Ln.height,bounds:Ln,scrollLeft:Fn.left,scrollTop:Fn.top,scrollLeftMax:Fn.leftMax,scrollTopMax:Fn.topMax,scrollWidth:Fn.width,scrollHeight:Fn.height,scroll:Fn},bn.forEach(Pn,function(Mn){if(Mn in Nn.result)Nn.locals[Mn]=Nn.result[Mn];else throw"No such measurement as "+Mn}),bn.findNext(this,Nn)}}}}),xn.addLeafExpression("closestExpr",function(vn,bn,hn){if(hn.matchToken("closest")){if(hn.matchToken("parent"))var On=!0;var Pn=null;if(hn.currentToken().type==="ATTRIBUTE_REF"){var Nn=vn.requireElement("attributeRefAccess",hn,null);Pn="["+Nn.attribute.name+"]"}if(Pn==null){var Dn=vn.requireElement("expression",hn);Dn.css==null?vn.raiseParseError(hn,"Expected a CSS expression"):Pn=Dn.css}if(hn.matchToken("to"))var Ln=vn.parseElement("expression",hn);else var Ln=vn.parseElement("implicitMeTarget",hn);var Fn={type:"closestExpr",parentSearch:On,expr:Dn,css:Pn,to:Ln,args:[Ln],op:function(Mn,Hn){if(Hn==null)return null;{let Wn=[];return bn.implicitLoop(Hn,function(zn){On?Wn.push(zn.parentElement?zn.parentElement.closest(Pn):null):Wn.push(zn.closest(Pn))}),bn.shouldAutoIterate(Hn)?Wn:Wn[0]}},evaluate:function(Mn){return bn.unifiedEval(this,Mn)}};return Nn?(Nn.root=Fn,Nn.args=[Fn],Nn):Fn}}),xn.addCommand("go",function(vn,bn,hn){if(hn.matchToken("go")){if(hn.matchToken("back"))var On=!0;else if(hn.matchToken("to"),hn.matchToken("url")){var Pn=vn.requireElement("stringLike",hn),Nn=!0;if(hn.matchToken("in")){hn.requireToken("new"),hn.requireToken("window");var Dn=!0}}else{hn.matchToken("the");var Ln=hn.matchAnyToken("top","middle","bottom"),Fn=hn.matchAnyToken("left","center","right");(Ln||Fn)&&hn.requireToken("of");var Pn=vn.requireElement("unaryExpression",hn),Mn=hn.matchAnyOpToken("+","-");if(Mn){hn.pushFollow("px");try{var Hn=vn.requireElement("expression",hn)}finally{hn.popFollow()}}hn.matchToken("px");var Wn=hn.matchAnyToken("smoothly","instantly"),zn={block:"start",inline:"nearest"};Ln&&(Ln.value==="top"?zn.block="start":Ln.value==="bottom"?zn.block="end":Ln.value==="middle"&&(zn.block="center")),Fn&&(Fn.value==="left"?zn.inline="start":Fn.value==="center"?zn.inline="center":Fn.value==="right"&&(zn.inline="end")),Wn&&(Wn.value==="smoothly"?zn.behavior="smooth":Wn.value==="instantly"&&(zn.behavior="instant"))}var nn={target:Pn,args:[Pn,Hn],op:function(on,Jr,rn){return On?window.history.back():Nn?Jr&&(Dn?window.open(Jr):window.location.href=Jr):bn.implicitLoop(Jr,function(cn){if(cn===window&&(cn=document.body),Mn){let sn=cn.getBoundingClientRect(),fn=document.createElement("div"),un=Mn.value==="+"?rn:rn*-1,gn=zn.inline=="start"||zn.inline=="end"?un:0,En=zn.block=="start"||zn.block=="end"?un:0;fn.style.position="absolute",fn.style.top=sn.top+window.scrollY+En+"px",fn.style.left=sn.left+window.scrollX+gn+"px",fn.style.height=sn.height+"px",fn.style.width=sn.width+"px",fn.style.zIndex=""+Number.MIN_SAFE_INTEGER,fn.style.opacity="0",document.body.appendChild(fn),setTimeout(function(){document.body.removeChild(fn)},100),cn=fn}cn.scrollIntoView(zn)}),bn.findNext(nn,on)}};return nn}}),Wr.conversions.dynamicResolvers.push(function(vn,bn){if(!(vn==="Values"||vn.indexOf("Values:")===0))return;var hn=vn.split(":")[1],On={},Pn=xn.runtime.implicitLoop.bind(xn.runtime);if(Pn(bn,function(Ln){var Fn=Dn(Ln);if(Fn!==void 0){On[Fn.name]=Fn.value;return}if(Ln.querySelectorAll!=null){var Mn=Ln.querySelectorAll("input,select,textarea");Mn.forEach(Nn)}}),hn){if(hn==="JSON")return JSON.stringify(On);if(hn==="Form")return new URLSearchParams(On).toString();throw"Unknown conversion: "+hn}else return On;function Nn(Ln){var Fn=Dn(Ln);if(Fn!=null){if(On[Fn.name]==null){On[Fn.name]=Fn.value;return}if(Array.isArray(On[Fn.name])&&Array.isArray(Fn.value)){On[Fn.name]=[].concat(On[Fn.name],Fn.value);return}}}function Dn(Ln){try{var Fn={name:Ln.name,value:Ln.value};if(Fn.name==null||Fn.value==null||Ln.type=="radio"&&Ln.checked==!1)return;if(Ln.type=="checkbox"&&(Ln.checked==!1?Fn.value=void 0:typeof Fn.value=="string"&&(Fn.value=[Fn.value])),Ln.type=="select-multiple"){var Mn=Ln.querySelectorAll("option[selected]");Fn.value=[];for(var Hn=0;Hn<Mn.length;Hn++)Fn.value.push(Mn[Hn].value)}return Fn}catch{return}}}),Wr.conversions.HTML=function(vn){var bn=function(hn){if(hn instanceof Array)return hn.map(function(Dn){return bn(Dn)}).join("");if(hn instanceof HTMLElement)return hn.outerHTML;if(hn instanceof NodeList){for(var On="",Pn=0;Pn<hn.length;Pn++){var Nn=hn[Pn];Nn instanceof HTMLElement&&(On+=Nn.outerHTML)}return On}return hn.toString?hn.toString():""};return bn(vn)},Wr.conversions.Fragment=function(vn){var bn=document.createDocumentFragment();return xn.runtime.implicitLoop(vn,function(hn){if(hn instanceof Node)bn.append(hn);else{var On=document.createElement("template");On.innerHTML=hn,bn.append(On.content)}}),bn}}let Jn=new tn,mi=Jn.lexer,ai=Jn.parser;function Ui(xn,en){return Jn.evaluate(xn,en)}function gi(){var xn=Array.from(Qr.document.querySelectorAll("script[type='text/hyperscript'][src]"));Promise.all(xn.map(function(mn){return fetch(mn.src).then(function(Sn){return Sn.text()})})).then(mn=>mn.forEach(Sn=>ri(Sn))).then(()=>en(function(){pn(),Jn.processNode(document.documentElement),document.dispatchEvent(new Event("hyperscript:ready")),Qr.document.addEventListener("htmx:load",function(mn){Jn.processNode(mn.detail.elt)})}));function en(mn){document.readyState!=="loading"?setTimeout(mn):document.addEventListener("DOMContentLoaded",mn)}function an(){var mn=document.querySelector('meta[name="htmx-config"]');return mn?Vn(mn.content):null}function pn(){var mn=an();mn&&Object.assign(Wr,mn)}}let ri=Object.assign(Ui,{config:Wr,use(xn){xn(ri)},internals:{lexer:mi,parser:ai,runtime:Jn,Lexer:Kr,Tokens:Yr,Parser:Zr,Runtime:tn},ElementCollection:An,addFeature:ai.addFeature.bind(ai),addCommand:ai.addCommand.bind(ai),addLeafExpression:ai.addLeafExpression.bind(ai),addIndirectExpression:ai.addIndirectExpression.bind(ai),evaluate:Jn.evaluate.bind(Jn),parse:Jn.parse.bind(Jn),processNode:Jn.processNode.bind(Jn),version:"0.9.14",browserInit:gi});return ri})});var zs=po(mo()),Vs=po(Ys());function $s(Qr,Gr){var Wr=Object.keys(Qr);if(Object.getOwnPropertySymbols){var Kr=Object.getOwnPropertySymbols(Qr);Gr&&(Kr=Kr.filter(function(Yr){return Object.getOwnPropertyDescriptor(Qr,Yr).enumerable})),Wr.push.apply(Wr,Kr)}return Wr}function Qi(Qr){for(var Gr=1;Gr<arguments.length;Gr++){var Wr=arguments[Gr]!=null?arguments[Gr]:{};Gr%2?$s(Object(Wr),!0).forEach(function(Kr){iu(Qr,Kr,Wr[Kr])}):Object.getOwnPropertyDescriptors?Object.defineProperties(Qr,Object.getOwnPropertyDescriptors(Wr)):$s(Object(Wr)).forEach(function(Kr){Object.defineProperty(Qr,Kr,Object.getOwnPropertyDescriptor(Wr,Kr))})}return Qr}function To(Qr){"@babel/helpers - typeof";return typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?To=function(Gr){return typeof Gr}:To=function(Gr){return Gr&&typeof Symbol=="function"&&Gr.constructor===Symbol&&Gr!==Symbol.prototype?"symbol":typeof Gr},To(Qr)}function iu(Qr,Gr,Wr){return Gr in Qr?Object.defineProperty(Qr,Gr,{value:Wr,enumerable:!0,configurable:!0,writable:!0}):Qr[Gr]=Wr,Qr}function ta(){return ta=Object.assign||function(Qr){for(var Gr=1;Gr<arguments.length;Gr++){var Wr=arguments[Gr];for(var Kr in Wr)Object.prototype.hasOwnProperty.call(Wr,Kr)&&(Qr[Kr]=Wr[Kr])}return Qr},ta.apply(this,arguments)}function au(Qr,Gr){if(Qr==null)return{};var Wr={},Kr=Object.keys(Qr),Yr,Zr;for(Zr=0;Zr<Kr.length;Zr++)Yr=Kr[Zr],!(Gr.indexOf(Yr)>=0)&&(Wr[Yr]=Qr[Yr]);return Wr}function ou(Qr,Gr){if(Qr==null)return{};var Wr=au(Qr,Gr),Kr,Yr;if(Object.getOwnPropertySymbols){var Zr=Object.getOwnPropertySymbols(Qr);for(Yr=0;Yr<Zr.length;Yr++)Kr=Zr[Yr],!(Gr.indexOf(Kr)>=0)&&Object.prototype.propertyIsEnumerable.call(Qr,Kr)&&(Wr[Kr]=Qr[Kr])}return Wr}var su="1.15.6";function ea(Qr){if(typeof window<"u"&&window.navigator)return!!navigator.userAgent.match(Qr)}var ra=ea(/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i),Ja=ea(/Edge/i),Js=ea(/firefox/i),Xa=ea(/safari/i)&&!ea(/chrome/i)&&!ea(/android/i),vs=ea(/iP(ad|od|hone)/i),ol=ea(/chrome/i)&&ea(/android/i),sl={capture:!1,passive:!1};function ei(Qr,Gr,Wr){Qr.addEventListener(Gr,Wr,!ra&&sl)}function Zn(Qr,Gr,Wr){Qr.removeEventListener(Gr,Wr,!ra&&sl)}function Co(Qr,Gr){if(Gr){if(Gr[0]===">"&&(Gr=Gr.substring(1)),Qr)try{if(Qr.matches)return Qr.matches(Gr);if(Qr.msMatchesSelector)return Qr.msMatchesSelector(Gr);if(Qr.webkitMatchesSelector)return Qr.webkitMatchesSelector(Gr)}catch{return!1}return!1}}function ll(Qr){return Qr.host&&Qr!==document&&Qr.host.nodeType?Qr.host:Qr.parentNode}function Vi(Qr,Gr,Wr,Kr){if(Qr){Wr=Wr||document;do{if(Gr!=null&&(Gr[0]===">"?Qr.parentNode===Wr&&Co(Qr,Gr):Co(Qr,Gr))||Kr&&Qr===Wr)return Qr;if(Qr===Wr)break}while(Qr=ll(Qr))}return null}var Zs=/\s+/g;function Mi(Qr,Gr,Wr){if(Qr&&Gr)if(Qr.classList)Qr.classList[Wr?"add":"remove"](Gr);else{var Kr=(" "+Qr.className+" ").replace(Zs," ").replace(" "+Gr+" "," ");Qr.className=(Kr+(Wr?" "+Gr:"")).replace(Zs," ")}}function Xn(Qr,Gr,Wr){var Kr=Qr&&Qr.style;if(Kr){if(Wr===void 0)return document.defaultView&&document.defaultView.getComputedStyle?Wr=document.defaultView.getComputedStyle(Qr,""):Qr.currentStyle&&(Wr=Qr.currentStyle),Gr===void 0?Wr:Wr[Gr];!(Gr in Kr)&&Gr.indexOf("webkit")===-1&&(Gr="-webkit-"+Gr),Kr[Gr]=Wr+(typeof Wr=="string"?"":"px")}}function Na(Qr,Gr){var Wr="";if(typeof Qr=="string")Wr=Qr;else do{var Kr=Xn(Qr,"transform");Kr&&Kr!=="none"&&(Wr=Kr+" "+Wr)}while(!Gr&&(Qr=Qr.parentNode));var Yr=window.DOMMatrix||window.WebKitCSSMatrix||window.CSSMatrix||window.MSCSSMatrix;return Yr&&new Yr(Wr)}function ul(Qr,Gr,Wr){if(Qr){var Kr=Qr.getElementsByTagName(Gr),Yr=0,Zr=Kr.length;if(Wr)for(;Yr<Zr;Yr++)Wr(Kr[Yr],Yr);return Kr}return[]}function Xi(){var Qr=document.scrollingElement;return Qr||document.documentElement}function yi(Qr,Gr,Wr,Kr,Yr){if(!(!Qr.getBoundingClientRect&&Qr!==window)){var Zr,tn,ln,dn,yn,wn,kn;if(Qr!==window&&Qr.parentNode&&Qr!==Xi()?(Zr=Qr.getBoundingClientRect(),tn=Zr.top,ln=Zr.left,dn=Zr.bottom,yn=Zr.right,wn=Zr.height,kn=Zr.width):(tn=0,ln=0,dn=window.innerHeight,yn=window.innerWidth,wn=window.innerHeight,kn=window.innerWidth),(Gr||Wr)&&Qr!==window&&(Yr=Yr||Qr.parentNode,!ra))do if(Yr&&Yr.getBoundingClientRect&&(Xn(Yr,"transform")!=="none"||Wr&&Xn(Yr,"position")!=="static")){var An=Yr.getBoundingClientRect();tn-=An.top+parseInt(Xn(Yr,"border-top-width")),ln-=An.left+parseInt(Xn(Yr,"border-left-width")),dn=tn+Zr.height,yn=ln+Zr.width;break}while(Yr=Yr.parentNode);if(Kr&&Qr!==window){var Gn=Na(Yr||Qr),jn=Gn&&Gn.a,Vn=Gn&&Gn.d;Gn&&(tn/=Vn,ln/=jn,kn/=jn,wn/=Vn,dn=tn+wn,yn=ln+kn)}return{top:tn,left:ln,bottom:dn,right:yn,width:kn,height:wn}}}function el(Qr,Gr,Wr){for(var Kr=la(Qr,!0),Yr=yi(Qr)[Gr];Kr;){var Zr=yi(Kr)[Wr],tn=void 0;if(Wr==="top"||Wr==="left"?tn=Yr>=Zr:tn=Yr<=Zr,!tn)return Kr;if(Kr===Xi())break;Kr=la(Kr,!1)}return!1}function La(Qr,Gr,Wr,Kr){for(var Yr=0,Zr=0,tn=Qr.children;Zr<tn.length;){if(tn[Zr].style.display!=="none"&&tn[Zr]!==Qn.ghost&&(Kr||tn[Zr]!==Qn.dragged)&&Vi(tn[Zr],Wr.draggable,Qr,!1)){if(Yr===Gr)return tn[Zr];Yr++}Zr++}return null}function ys(Qr,Gr){for(var Wr=Qr.lastElementChild;Wr&&(Wr===Qn.ghost||Xn(Wr,"display")==="none"||Gr&&!Co(Wr,Gr));)Wr=Wr.previousElementSibling;return Wr||null}function Gi(Qr,Gr){var Wr=0;if(!Qr||!Qr.parentNode)return-1;for(;Qr=Qr.previousElementSibling;)Qr.nodeName.toUpperCase()!=="TEMPLATE"&&Qr!==Qn.clone&&(!Gr||Co(Qr,Gr))&&Wr++;return Wr}function tl(Qr){var Gr=0,Wr=0,Kr=Xi();if(Qr)do{var Yr=Na(Qr),Zr=Yr.a,tn=Yr.d;Gr+=Qr.scrollLeft*Zr,Wr+=Qr.scrollTop*tn}while(Qr!==Kr&&(Qr=Qr.parentNode));return[Gr,Wr]}function lu(Qr,Gr){for(var Wr in Qr)if(Qr.hasOwnProperty(Wr)){for(var Kr in Gr)if(Gr.hasOwnProperty(Kr)&&Gr[Kr]===Qr[Wr][Kr])return Number(Wr)}return-1}function la(Qr,Gr){if(!Qr||!Qr.getBoundingClientRect)return Xi();var Wr=Qr,Kr=!1;do if(Wr.clientWidth<Wr.scrollWidth||Wr.clientHeight<Wr.scrollHeight){var Yr=Xn(Wr);if(Wr.clientWidth<Wr.scrollWidth&&(Yr.overflowX=="auto"||Yr.overflowX=="scroll")||Wr.clientHeight<Wr.scrollHeight&&(Yr.overflowY=="auto"||Yr.overflowY=="scroll")){if(!Wr.getBoundingClientRect||Wr===document.body)return Xi();if(Kr||Gr)return Wr;Kr=!0}}while(Wr=Wr.parentNode);return Xi()}function uu(Qr,Gr){if(Qr&&Gr)for(var Wr in Gr)Gr.hasOwnProperty(Wr)&&(Qr[Wr]=Gr[Wr]);return Qr}function is(Qr,Gr){return Math.round(Qr.top)===Math.round(Gr.top)&&Math.round(Qr.left)===Math.round(Gr.left)&&Math.round(Qr.height)===Math.round(Gr.height)&&Math.round(Qr.width)===Math.round(Gr.width)}var Qa;function cl(Qr,Gr){return function(){if(!Qa){var Wr=arguments,Kr=this;Wr.length===1?Qr.call(Kr,Wr[0]):Qr.apply(Kr,Wr),Qa=setTimeout(function(){Qa=void 0},Gr)}}}function cu(){clearTimeout(Qa),Qa=void 0}function fl(Qr,Gr,Wr){Qr.scrollLeft+=Gr,Qr.scrollTop+=Wr}function hl(Qr){var Gr=window.Polymer,Wr=window.jQuery||window.Zepto;return Gr&&Gr.dom?Gr.dom(Qr).cloneNode(!0):Wr?Wr(Qr).clone(!0)[0]:Qr.cloneNode(!0)}function dl(Qr,Gr,Wr){var Kr={};return Array.from(Qr.children).forEach(function(Yr){var Zr,tn,ln,dn;if(!(!Vi(Yr,Gr.draggable,Qr,!1)||Yr.animated||Yr===Wr)){var yn=yi(Yr);Kr.left=Math.min((Zr=Kr.left)!==null&&Zr!==void 0?Zr:1/0,yn.left),Kr.top=Math.min((tn=Kr.top)!==null&&tn!==void 0?tn:1/0,yn.top),Kr.right=Math.max((ln=Kr.right)!==null&&ln!==void 0?ln:-1/0,yn.right),Kr.bottom=Math.max((dn=Kr.bottom)!==null&&dn!==void 0?dn:-1/0,yn.bottom)}}),Kr.width=Kr.right-Kr.left,Kr.height=Kr.bottom-Kr.top,Kr.x=Kr.left,Kr.y=Kr.top,Kr}var Pi="Sortable"+new Date().getTime();function fu(){var Qr=[],Gr;return{captureAnimationState:function(){if(Qr=[],!!this.options.animation){var Kr=[].slice.call(this.el.children);Kr.forEach(function(Yr){if(!(Xn(Yr,"display")==="none"||Yr===Qn.ghost)){Qr.push({target:Yr,rect:yi(Yr)});var Zr=Qi({},Qr[Qr.length-1].rect);if(Yr.thisAnimationDuration){var tn=Na(Yr,!0);tn&&(Zr.top-=tn.f,Zr.left-=tn.e)}Yr.fromRect=Zr}})}},addAnimationState:function(Kr){Qr.push(Kr)},removeAnimationState:function(Kr){Qr.splice(lu(Qr,{target:Kr}),1)},animateAll:function(Kr){var Yr=this;if(!this.options.animation){clearTimeout(Gr),typeof Kr=="function"&&Kr();return}var Zr=!1,tn=0;Qr.forEach(function(ln){var dn=0,yn=ln.target,wn=yn.fromRect,kn=yi(yn),An=yn.prevFromRect,Gn=yn.prevToRect,jn=ln.rect,Vn=Na(yn,!0);Vn&&(kn.top-=Vn.f,kn.left-=Vn.e),yn.toRect=kn,yn.thisAnimationDuration&&is(An,kn)&&!is(wn,kn)&&(jn.top-kn.top)/(jn.left-kn.left)===(wn.top-kn.top)/(wn.left-kn.left)&&(dn=du(jn,An,Gn,Yr.options)),is(kn,wn)||(yn.prevFromRect=wn,yn.prevToRect=kn,dn||(dn=Yr.options.animation),Yr.animate(yn,jn,kn,dn)),dn&&(Zr=!0,tn=Math.max(tn,dn),clearTimeout(yn.animationResetTimer),yn.animationResetTimer=setTimeout(function(){yn.animationTime=0,yn.prevFromRect=null,yn.fromRect=null,yn.prevToRect=null,yn.thisAnimationDuration=null},dn),yn.thisAnimationDuration=dn)}),clearTimeout(Gr),Zr?Gr=setTimeout(function(){typeof Kr=="function"&&Kr()},tn):typeof Kr=="function"&&Kr(),Qr=[]},animate:function(Kr,Yr,Zr,tn){if(tn){Xn(Kr,"transition",""),Xn(Kr,"transform","");var ln=Na(this.el),dn=ln&&ln.a,yn=ln&&ln.d,wn=(Yr.left-Zr.left)/(dn||1),kn=(Yr.top-Zr.top)/(yn||1);Kr.animatingX=!!wn,Kr.animatingY=!!kn,Xn(Kr,"transform","translate3d("+wn+"px,"+kn+"px,0)"),this.forRepaintDummy=hu(Kr),Xn(Kr,"transition","transform "+tn+"ms"+(this.options.easing?" "+this.options.easing:"")),Xn(Kr,"transform","translate3d(0,0,0)"),typeof Kr.animated=="number"&&clearTimeout(Kr.animated),Kr.animated=setTimeout(function(){Xn(Kr,"transition",""),Xn(Kr,"transform",""),Kr.animated=!1,Kr.animatingX=!1,Kr.animatingY=!1},tn)}}}}function hu(Qr){return Qr.offsetWidth}function du(Qr,Gr,Wr,Kr){return Math.sqrt(Math.pow(Gr.top-Qr.top,2)+Math.pow(Gr.left-Qr.left,2))/Math.sqrt(Math.pow(Gr.top-Wr.top,2)+Math.pow(Gr.left-Wr.left,2))*Kr.animation}var Aa=[],as={initializeByDefault:!0},Za={mount:function(Gr){for(var Wr in as)as.hasOwnProperty(Wr)&&!(Wr in Gr)&&(Gr[Wr]=as[Wr]);Aa.forEach(function(Kr){if(Kr.pluginName===Gr.pluginName)throw"Sortable: Cannot mount plugin ".concat(Gr.pluginName," more than once")}),Aa.push(Gr)},pluginEvent:function(Gr,Wr,Kr){var Yr=this;this.eventCanceled=!1,Kr.cancel=function(){Yr.eventCanceled=!0};var Zr=Gr+"Global";Aa.forEach(function(tn){Wr[tn.pluginName]&&(Wr[tn.pluginName][Zr]&&Wr[tn.pluginName][Zr](Qi({sortable:Wr},Kr)),Wr.options[tn.pluginName]&&Wr[tn.pluginName][Gr]&&Wr[tn.pluginName][Gr](Qi({sortable:Wr},Kr)))})},initializePlugins:function(Gr,Wr,Kr,Yr){Aa.forEach(function(ln){var dn=ln.pluginName;if(!(!Gr.options[dn]&&!ln.initializeByDefault)){var yn=new ln(Gr,Wr,Gr.options);yn.sortable=Gr,yn.options=Gr.options,Gr[dn]=yn,ta(Kr,yn.defaults)}});for(var Zr in Gr.options)if(Gr.options.hasOwnProperty(Zr)){var tn=this.modifyOption(Gr,Zr,Gr.options[Zr]);typeof tn<"u"&&(Gr.options[Zr]=tn)}},getEventProperties:function(Gr,Wr){var Kr={};return Aa.forEach(function(Yr){typeof Yr.eventProperties=="function"&&ta(Kr,Yr.eventProperties.call(Wr[Yr.pluginName],Gr))}),Kr},modifyOption:function(Gr,Wr,Kr){var Yr;return Aa.forEach(function(Zr){Gr[Zr.pluginName]&&Zr.optionListeners&&typeof Zr.optionListeners[Wr]=="function"&&(Yr=Zr.optionListeners[Wr].call(Gr[Zr.pluginName],Kr))}),Yr}};function pu(Qr){var Gr=Qr.sortable,Wr=Qr.rootEl,Kr=Qr.name,Yr=Qr.targetEl,Zr=Qr.cloneEl,tn=Qr.toEl,ln=Qr.fromEl,dn=Qr.oldIndex,yn=Qr.newIndex,wn=Qr.oldDraggableIndex,kn=Qr.newDraggableIndex,An=Qr.originalEvent,Gn=Qr.putSortable,jn=Qr.extraEventProperties;if(Gr=Gr||Wr&&Wr[Pi],!!Gr){var Vn,ti=Gr.options,Ti="on"+Kr.charAt(0).toUpperCase()+Kr.substr(1);window.CustomEvent&&!ra&&!Ja?Vn=new CustomEvent(Kr,{bubbles:!0,cancelable:!0}):(Vn=document.createEvent("Event"),Vn.initEvent(Kr,!0,!0)),Vn.to=tn||Wr,Vn.from=ln||Wr,Vn.item=Yr||Wr,Vn.clone=Zr,Vn.oldIndex=dn,Vn.newIndex=yn,Vn.oldDraggableIndex=wn,Vn.newDraggableIndex=kn,Vn.originalEvent=An,Vn.pullMode=Gn?Gn.lastPutMode:void 0;var fi=Qi(Qi({},jn),Za.getEventProperties(Kr,Gr));for(var oi in fi)Vn[oi]=fi[oi];Wr&&Wr.dispatchEvent(Vn),ti[Ti]&&ti[Ti].call(Gr,Vn)}}var mu=["evt"],Di=function(Gr,Wr){var Kr=arguments.length>2&&arguments[2]!==void 0?arguments[2]:{},Yr=Kr.evt,Zr=ou(Kr,mu);Za.pluginEvent.bind(Qn)(Gr,Wr,Qi({dragEl:qn,parentEl:hi,ghostEl:Yn,rootEl:ui,nextEl:va,lastDownEl:xo,cloneEl:ci,cloneHidden:sa,dragStarted:za,putSortable:wi,activeSortable:Qn.active,originalEvent:Yr,oldIndex:Ra,oldDraggableIndex:Ka,newIndex:Fi,newDraggableIndex:oa,hideGhostForTarget:vl,unhideGhostForTarget:yl,cloneNowHidden:function(){sa=!0},cloneNowShown:function(){sa=!1},dispatchSortableEvent:function(ln){Oi({sortable:Wr,name:ln,originalEvent:Yr})}},Zr))};function Oi(Qr){pu(Qi({putSortable:wi,cloneEl:ci,targetEl:qn,rootEl:ui,oldIndex:Ra,oldDraggableIndex:Ka,newIndex:Fi,newDraggableIndex:oa},Qr))}var qn,hi,Yn,ui,va,xo,ci,sa,Ra,Fi,Ka,oa,vo,wi,Pa=!1,Io=!1,Oo=[],ma,zi,os,ss,rl,nl,za,Da,Ya,$a=!1,yo=!1,So,Ii,ls=[],ds=!1,Ao=[],Po=typeof document<"u",bo=vs,il=Ja||ra?"cssFloat":"float",gu=Po&&!ol&&!vs&&"draggable"in document.createElement("div"),pl=function(){if(Po){if(ra)return!1;var Qr=document.createElement("x");return Qr.style.cssText="pointer-events:auto",Qr.style.pointerEvents==="auto"}}(),ml=function(Gr,Wr){var Kr=Xn(Gr),Yr=parseInt(Kr.width)-parseInt(Kr.paddingLeft)-parseInt(Kr.paddingRight)-parseInt(Kr.borderLeftWidth)-parseInt(Kr.borderRightWidth),Zr=La(Gr,0,Wr),tn=La(Gr,1,Wr),ln=Zr&&Xn(Zr),dn=tn&&Xn(tn),yn=ln&&parseInt(ln.marginLeft)+parseInt(ln.marginRight)+yi(Zr).width,wn=dn&&parseInt(dn.marginLeft)+parseInt(dn.marginRight)+yi(tn).width;if(Kr.display==="flex")return Kr.flexDirection==="column"||Kr.flexDirection==="column-reverse"?"vertical":"horizontal";if(Kr.display==="grid")return Kr.gridTemplateColumns.split(" ").length<=1?"vertical":"horizontal";if(Zr&&ln.float&&ln.float!=="none"){var kn=ln.float==="left"?"left":"right";return tn&&(dn.clear==="both"||dn.clear===kn)?"vertical":"horizontal"}return Zr&&(ln.display==="block"||ln.display==="flex"||ln.display==="table"||ln.display==="grid"||yn>=Yr&&Kr[il]==="none"||tn&&Kr[il]==="none"&&yn+wn>Yr)?"vertical":"horizontal"},vu=function(Gr,Wr,Kr){var Yr=Kr?Gr.left:Gr.top,Zr=Kr?Gr.right:Gr.bottom,tn=Kr?Gr.width:Gr.height,ln=Kr?Wr.left:Wr.top,dn=Kr?Wr.right:Wr.bottom,yn=Kr?Wr.width:Wr.height;return Yr===ln||Zr===dn||Yr+tn/2===ln+yn/2},yu=function(Gr,Wr){var Kr;return Oo.some(function(Yr){var Zr=Yr[Pi].options.emptyInsertThreshold;if(!(!Zr||ys(Yr))){var tn=yi(Yr),ln=Gr>=tn.left-Zr&&Gr<=tn.right+Zr,dn=Wr>=tn.top-Zr&&Wr<=tn.bottom+Zr;if(ln&&dn)return Kr=Yr}}),Kr},gl=function(Gr){function Wr(Zr,tn){return function(ln,dn,yn,wn){var kn=ln.options.group.name&&dn.options.group.name&&ln.options.group.name===dn.options.group.name;if(Zr==null&&(tn||kn))return!0;if(Zr==null||Zr===!1)return!1;if(tn&&Zr==="clone")return Zr;if(typeof Zr=="function")return Wr(Zr(ln,dn,yn,wn),tn)(ln,dn,yn,wn);var An=(tn?ln:dn).options.group.name;return Zr===!0||typeof Zr=="string"&&Zr===An||Zr.join&&Zr.indexOf(An)>-1}}var Kr={},Yr=Gr.group;(!Yr||To(Yr)!="object")&&(Yr={name:Yr}),Kr.name=Yr.name,Kr.checkPull=Wr(Yr.pull,!0),Kr.checkPut=Wr(Yr.put),Kr.revertClone=Yr.revertClone,Gr.group=Kr},vl=function(){!pl&&Yn&&Xn(Yn,"display","none")},yl=function(){!pl&&Yn&&Xn(Yn,"display","")};Po&&!ol&&document.addEventListener("click",function(Qr){if(Io)return Qr.preventDefault(),Qr.stopPropagation&&Qr.stopPropagation(),Qr.stopImmediatePropagation&&Qr.stopImmediatePropagation(),Io=!1,!1},!0);var ga=function(Gr){if(qn){Gr=Gr.touches?Gr.touches[0]:Gr;var Wr=yu(Gr.clientX,Gr.clientY);if(Wr){var Kr={};for(var Yr in Gr)Gr.hasOwnProperty(Yr)&&(Kr[Yr]=Gr[Yr]);Kr.target=Kr.rootEl=Wr,Kr.preventDefault=void 0,Kr.stopPropagation=void 0,Wr[Pi]._onDragOver(Kr)}}},bu=function(Gr){qn&&qn.parentNode[Pi]._isOutsideThisEl(Gr.target)};function Qn(Qr,Gr){if(!(Qr&&Qr.nodeType&&Qr.nodeType===1))throw"Sortable: `el` must be an HTMLElement, not ".concat({}.toString.call(Qr));this.el=Qr,this.options=Gr=ta({},Gr),Qr[Pi]=this;var Wr={group:null,sort:!0,disabled:!1,store:null,handle:null,draggable:/^[uo]l$/i.test(Qr.nodeName)?">li":">*",swapThreshold:1,invertSwap:!1,invertedSwapThreshold:null,removeCloneOnHide:!0,direction:function(){return ml(Qr,this.options)},ghostClass:"sortable-ghost",chosenClass:"sortable-chosen",dragClass:"sortable-drag",ignore:"a, img",filter:null,preventOnFilter:!0,animation:0,easing:null,setData:function(tn,ln){tn.setData("Text",ln.textContent)},dropBubble:!1,dragoverBubble:!1,dataIdAttr:"data-id",delay:0,delayOnTouchOnly:!1,touchStartThreshold:(Number.parseInt?Number:window).parseInt(window.devicePixelRatio,10)||1,forceFallback:!1,fallbackClass:"sortable-fallback",fallbackOnBody:!1,fallbackTolerance:0,fallbackOffset:{x:0,y:0},supportPointer:Qn.supportPointer!==!1&&"PointerEvent"in window&&(!Xa||vs),emptyInsertThreshold:5};Za.initializePlugins(this,Qr,Wr);for(var Kr in Wr)!(Kr in Gr)&&(Gr[Kr]=Wr[Kr]);gl(Gr);for(var Yr in this)Yr.charAt(0)==="_"&&typeof this[Yr]=="function"&&(this[Yr]=this[Yr].bind(this));this.nativeDraggable=Gr.forceFallback?!1:gu,this.nativeDraggable&&(this.options.touchStartThreshold=1),Gr.supportPointer?ei(Qr,"pointerdown",this._onTapStart):(ei(Qr,"mousedown",this._onTapStart),ei(Qr,"touchstart",this._onTapStart)),this.nativeDraggable&&(ei(Qr,"dragover",this),ei(Qr,"dragenter",this)),Oo.push(this.el),Gr.store&&Gr.store.get&&this.sort(Gr.store.get(this)||[]),ta(this,fu())}Qn.prototype={constructor:Qn,_isOutsideThisEl:function(Gr){!this.el.contains(Gr)&&Gr!==this.el&&(Da=null)},_getDirection:function(Gr,Wr){return typeof this.options.direction=="function"?this.options.direction.call(this,Gr,Wr,qn):this.options.direction},_onTapStart:function(Gr){if(Gr.cancelable){var Wr=this,Kr=this.el,Yr=this.options,Zr=Yr.preventOnFilter,tn=Gr.type,ln=Gr.touches&&Gr.touches[0]||Gr.pointerType&&Gr.pointerType==="touch"&&Gr,dn=(ln||Gr).target,yn=Gr.target.shadowRoot&&(Gr.path&&Gr.path[0]||Gr.composedPath&&Gr.composedPath()[0])||dn,wn=Yr.filter;if(Iu(Kr),!qn&&!(/mousedown|pointerdown/.test(tn)&&Gr.button!==0||Yr.disabled)&&!yn.isContentEditable&&!(!this.nativeDraggable&&Xa&&dn&&dn.tagName.toUpperCase()==="SELECT")&&(dn=Vi(dn,Yr.draggable,Kr,!1),!(dn&&dn.animated)&&xo!==dn)){if(Ra=Gi(dn),Ka=Gi(dn,Yr.draggable),typeof wn=="function"){if(wn.call(this,Gr,dn,this)){Oi({sortable:Wr,rootEl:yn,name:"filter",targetEl:dn,toEl:Kr,fromEl:Kr}),Di("filter",Wr,{evt:Gr}),Zr&&Gr.preventDefault();return}}else if(wn&&(wn=wn.split(",").some(function(kn){if(kn=Vi(yn,kn.trim(),Kr,!1),kn)return Oi({sortable:Wr,rootEl:kn,name:"filter",targetEl:dn,fromEl:Kr,toEl:Kr}),Di("filter",Wr,{evt:Gr}),!0}),wn)){Zr&&Gr.preventDefault();return}Yr.handle&&!Vi(yn,Yr.handle,Kr,!1)||this._prepareDragStart(Gr,ln,dn)}}},_prepareDragStart:function(Gr,Wr,Kr){var Yr=this,Zr=Yr.el,tn=Yr.options,ln=Zr.ownerDocument,dn;if(Kr&&!qn&&Kr.parentNode===Zr){var yn=yi(Kr);if(ui=Zr,qn=Kr,hi=qn.parentNode,va=qn.nextSibling,xo=Kr,vo=tn.group,Qn.dragged=qn,ma={target:qn,clientX:(Wr||Gr).clientX,clientY:(Wr||Gr).clientY},rl=ma.clientX-yn.left,nl=ma.clientY-yn.top,this._lastX=(Wr||Gr).clientX,this._lastY=(Wr||Gr).clientY,qn.style["will-change"]="all",dn=function(){if(Di("delayEnded",Yr,{evt:Gr}),Qn.eventCanceled){Yr._onDrop();return}Yr._disableDelayedDragEvents(),!Js&&Yr.nativeDraggable&&(qn.draggable=!0),Yr._triggerDragStart(Gr,Wr),Oi({sortable:Yr,name:"choose",originalEvent:Gr}),Mi(qn,tn.chosenClass,!0)},tn.ignore.split(",").forEach(function(wn){ul(qn,wn.trim(),us)}),ei(ln,"dragover",ga),ei(ln,"mousemove",ga),ei(ln,"touchmove",ga),tn.supportPointer?(ei(ln,"pointerup",Yr._onDrop),!this.nativeDraggable&&ei(ln,"pointercancel",Yr._onDrop)):(ei(ln,"mouseup",Yr._onDrop),ei(ln,"touchend",Yr._onDrop),ei(ln,"touchcancel",Yr._onDrop)),Js&&this.nativeDraggable&&(this.options.touchStartThreshold=4,qn.draggable=!0),Di("delayStart",this,{evt:Gr}),tn.delay&&(!tn.delayOnTouchOnly||Wr)&&(!this.nativeDraggable||!(Ja||ra))){if(Qn.eventCanceled){this._onDrop();return}tn.supportPointer?(ei(ln,"pointerup",Yr._disableDelayedDrag),ei(ln,"pointercancel",Yr._disableDelayedDrag)):(ei(ln,"mouseup",Yr._disableDelayedDrag),ei(ln,"touchend",Yr._disableDelayedDrag),ei(ln,"touchcancel",Yr._disableDelayedDrag)),ei(ln,"mousemove",Yr._delayedDragTouchMoveHandler),ei(ln,"touchmove",Yr._delayedDragTouchMoveHandler),tn.supportPointer&&ei(ln,"pointermove",Yr._delayedDragTouchMoveHandler),Yr._dragStartTimer=setTimeout(dn,tn.delay)}else dn()}},_delayedDragTouchMoveHandler:function(Gr){var Wr=Gr.touches?Gr.touches[0]:Gr;Math.max(Math.abs(Wr.clientX-this._lastX),Math.abs(Wr.clientY-this._lastY))>=Math.floor(this.options.touchStartThreshold/(this.nativeDraggable&&window.devicePixelRatio||1))&&this._disableDelayedDrag()},_disableDelayedDrag:function(){qn&&us(qn),clearTimeout(this._dragStartTimer),this._disableDelayedDragEvents()},_disableDelayedDragEvents:function(){var Gr=this.el.ownerDocument;Zn(Gr,"mouseup",this._disableDelayedDrag),Zn(Gr,"touchend",this._disableDelayedDrag),Zn(Gr,"touchcancel",this._disableDelayedDrag),Zn(Gr,"pointerup",this._disableDelayedDrag),Zn(Gr,"pointercancel",this._disableDelayedDrag),Zn(Gr,"mousemove",this._delayedDragTouchMoveHandler),Zn(Gr,"touchmove",this._delayedDragTouchMoveHandler),Zn(Gr,"pointermove",this._delayedDragTouchMoveHandler)},_triggerDragStart:function(Gr,Wr){Wr=Wr||Gr.pointerType=="touch"&&Gr,!this.nativeDraggable||Wr?this.options.supportPointer?ei(document,"pointermove",this._onTouchMove):Wr?ei(document,"touchmove",this._onTouchMove):ei(document,"mousemove",this._onTouchMove):(ei(qn,"dragend",this),ei(ui,"dragstart",this._onDragStart));try{document.selection?wo(function(){document.selection.empty()}):window.getSelection().removeAllRanges()}catch{}},_dragStarted:function(Gr,Wr){if(Pa=!1,ui&&qn){Di("dragStarted",this,{evt:Wr}),this.nativeDraggable&&ei(document,"dragover",bu);var Kr=this.options;!Gr&&Mi(qn,Kr.dragClass,!1),Mi(qn,Kr.ghostClass,!0),Qn.active=this,Gr&&this._appendGhost(),Oi({sortable:this,name:"start",originalEvent:Wr})}else this._nulling()},_emulateDragOver:function(){if(zi){this._lastX=zi.clientX,this._lastY=zi.clientY,vl();for(var Gr=document.elementFromPoint(zi.clientX,zi.clientY),Wr=Gr;Gr&&Gr.shadowRoot&&(Gr=Gr.shadowRoot.elementFromPoint(zi.clientX,zi.clientY),Gr!==Wr);)Wr=Gr;if(qn.parentNode[Pi]._isOutsideThisEl(Gr),Wr)do{if(Wr[Pi]){var Kr=void 0;if(Kr=Wr[Pi]._onDragOver({clientX:zi.clientX,clientY:zi.clientY,target:Gr,rootEl:Wr}),Kr&&!this.options.dragoverBubble)break}Gr=Wr}while(Wr=ll(Wr));yl()}},_onTouchMove:function(Gr){if(ma){var Wr=this.options,Kr=Wr.fallbackTolerance,Yr=Wr.fallbackOffset,Zr=Gr.touches?Gr.touches[0]:Gr,tn=Yn&&Na(Yn,!0),ln=Yn&&tn&&tn.a,dn=Yn&&tn&&tn.d,yn=bo&&Ii&&tl(Ii),wn=(Zr.clientX-ma.clientX+Yr.x)/(ln||1)+(yn?yn[0]-ls[0]:0)/(ln||1),kn=(Zr.clientY-ma.clientY+Yr.y)/(dn||1)+(yn?yn[1]-ls[1]:0)/(dn||1);if(!Qn.active&&!Pa){if(Kr&&Math.max(Math.abs(Zr.clientX-this._lastX),Math.abs(Zr.clientY-this._lastY))<Kr)return;this._onDragStart(Gr,!0)}if(Yn){tn?(tn.e+=wn-(os||0),tn.f+=kn-(ss||0)):tn={a:1,b:0,c:0,d:1,e:wn,f:kn};var An="matrix(".concat(tn.a,",").concat(tn.b,",").concat(tn.c,",").concat(tn.d,",").concat(tn.e,",").concat(tn.f,")");Xn(Yn,"webkitTransform",An),Xn(Yn,"mozTransform",An),Xn(Yn,"msTransform",An),Xn(Yn,"transform",An),os=wn,ss=kn,zi=Zr}Gr.cancelable&&Gr.preventDefault()}},_appendGhost:function(){if(!Yn){var Gr=this.options.fallbackOnBody?document.body:ui,Wr=yi(qn,!0,bo,!0,Gr),Kr=this.options;if(bo){for(Ii=Gr;Xn(Ii,"position")==="static"&&Xn(Ii,"transform")==="none"&&Ii!==document;)Ii=Ii.parentNode;Ii!==document.body&&Ii!==document.documentElement?(Ii===document&&(Ii=Xi()),Wr.top+=Ii.scrollTop,Wr.left+=Ii.scrollLeft):Ii=Xi(),ls=tl(Ii)}Yn=qn.cloneNode(!0),Mi(Yn,Kr.ghostClass,!1),Mi(Yn,Kr.fallbackClass,!0),Mi(Yn,Kr.dragClass,!0),Xn(Yn,"transition",""),Xn(Yn,"transform",""),Xn(Yn,"box-sizing","border-box"),Xn(Yn,"margin",0),Xn(Yn,"top",Wr.top),Xn(Yn,"left",Wr.left),Xn(Yn,"width",Wr.width),Xn(Yn,"height",Wr.height),Xn(Yn,"opacity","0.8"),Xn(Yn,"position",bo?"absolute":"fixed"),Xn(Yn,"zIndex","100000"),Xn(Yn,"pointerEvents","none"),Qn.ghost=Yn,Gr.appendChild(Yn),Xn(Yn,"transform-origin",rl/parseInt(Yn.style.width)*100+"% "+nl/parseInt(Yn.style.height)*100+"%")}},_onDragStart:function(Gr,Wr){var Kr=this,Yr=Gr.dataTransfer,Zr=Kr.options;if(Di("dragStart",this,{evt:Gr}),Qn.eventCanceled){this._onDrop();return}Di("setupClone",this),Qn.eventCanceled||(ci=hl(qn),ci.removeAttribute("id"),ci.draggable=!1,ci.style["will-change"]="",this._hideClone(),Mi(ci,this.options.chosenClass,!1),Qn.clone=ci),Kr.cloneId=wo(function(){Di("clone",Kr),!Qn.eventCanceled&&(Kr.options.removeCloneOnHide||ui.insertBefore(ci,qn),Kr._hideClone(),Oi({sortable:Kr,name:"clone"}))}),!Wr&&Mi(qn,Zr.dragClass,!0),Wr?(Io=!0,Kr._loopId=setInterval(Kr._emulateDragOver,50)):(Zn(document,"mouseup",Kr._onDrop),Zn(document,"touchend",Kr._onDrop),Zn(document,"touchcancel",Kr._onDrop),Yr&&(Yr.effectAllowed="move",Zr.setData&&Zr.setData.call(Kr,Yr,qn)),ei(document,"drop",Kr),Xn(qn,"transform","translateZ(0)")),Pa=!0,Kr._dragStartId=wo(Kr._dragStarted.bind(Kr,Wr,Gr)),ei(document,"selectstart",Kr),za=!0,window.getSelection().removeAllRanges(),Xa&&Xn(document.body,"user-select","none")},_onDragOver:function(Gr){var Wr=this.el,Kr=Gr.target,Yr,Zr,tn,ln=this.options,dn=ln.group,yn=Qn.active,wn=vo===dn,kn=ln.sort,An=wi||yn,Gn,jn=this,Vn=!1;if(ds)return;function ti(hn,On){Di(hn,jn,Qi({evt:Gr,isOwner:wn,axis:Gn?"vertical":"horizontal",revert:tn,dragRect:Yr,targetRect:Zr,canSort:kn,fromSortable:An,target:Kr,completed:fi,onMove:function(Nn,Dn){return Eo(ui,Wr,qn,Yr,Nn,yi(Nn),Gr,Dn)},changed:oi},On))}function Ti(){ti("dragOverAnimationCapture"),jn.captureAnimationState(),jn!==An&&An.captureAnimationState()}function fi(hn){return ti("dragOverCompleted",{insertion:hn}),hn&&(wn?yn._hideClone():yn._showClone(jn),jn!==An&&(Mi(qn,wi?wi.options.ghostClass:yn.options.ghostClass,!1),Mi(qn,ln.ghostClass,!0)),wi!==jn&&jn!==Qn.active?wi=jn:jn===Qn.active&&wi&&(wi=null),An===jn&&(jn._ignoreWhileAnimating=Kr),jn.animateAll(function(){ti("dragOverAnimationComplete"),jn._ignoreWhileAnimating=null}),jn!==An&&(An.animateAll(),An._ignoreWhileAnimating=null)),(Kr===qn&&!qn.animated||Kr===Wr&&!Kr.animated)&&(Da=null),!ln.dragoverBubble&&!Gr.rootEl&&Kr!==document&&(qn.parentNode[Pi]._isOutsideThisEl(Gr.target),!hn&&ga(Gr)),!ln.dragoverBubble&&Gr.stopPropagation&&Gr.stopPropagation(),Vn=!0}function oi(){Fi=Gi(qn),oa=Gi(qn,ln.draggable),Oi({sortable:jn,name:"change",toEl:Wr,newIndex:Fi,newDraggableIndex:oa,originalEvent:Gr})}if(Gr.preventDefault!==void 0&&Gr.cancelable&&Gr.preventDefault(),Kr=Vi(Kr,ln.draggable,Wr,!0),ti("dragOver"),Qn.eventCanceled)return Vn;if(qn.contains(Gr.target)||Kr.animated&&Kr.animatingX&&Kr.animatingY||jn._ignoreWhileAnimating===Kr)return fi(!1);if(Io=!1,yn&&!ln.disabled&&(wn?kn||(tn=hi!==ui):wi===this||(this.lastPutMode=vo.checkPull(this,yn,qn,Gr))&&dn.checkPut(this,yn,qn,Gr))){if(Gn=this._getDirection(Gr,Kr)==="vertical",Yr=yi(qn),ti("dragOverValid"),Qn.eventCanceled)return Vn;if(tn)return hi=ui,Ti(),this._hideClone(),ti("revert"),Qn.eventCanceled||(va?ui.insertBefore(qn,va):ui.appendChild(qn)),fi(!0);var Jn=ys(Wr,ln.draggable);if(!Jn||Su(Gr,Gn,this)&&!Jn.animated){if(Jn===qn)return fi(!1);if(Jn&&Wr===Gr.target&&(Kr=Jn),Kr&&(Zr=yi(Kr)),Eo(ui,Wr,qn,Yr,Kr,Zr,Gr,!!Kr)!==!1)return Ti(),Jn&&Jn.nextSibling?Wr.insertBefore(qn,Jn.nextSibling):Wr.appendChild(qn),hi=Wr,oi(),fi(!0)}else if(Jn&&xu(Gr,Gn,this)){var mi=La(Wr,0,ln,!0);if(mi===qn)return fi(!1);if(Kr=mi,Zr=yi(Kr),Eo(ui,Wr,qn,Yr,Kr,Zr,Gr,!1)!==!1)return Ti(),Wr.insertBefore(qn,mi),hi=Wr,oi(),fi(!0)}else if(Kr.parentNode===Wr){Zr=yi(Kr);var ai=0,Ui,gi=qn.parentNode!==Wr,ri=!vu(qn.animated&&qn.toRect||Yr,Kr.animated&&Kr.toRect||Zr,Gn),xn=Gn?"top":"left",en=el(Kr,"top","top")||el(qn,"top","top"),an=en?en.scrollTop:void 0;Da!==Kr&&(Ui=Zr[xn],$a=!1,yo=!ri&&ln.invertSwap||gi),ai=wu(Gr,Kr,Zr,Gn,ri?1:ln.swapThreshold,ln.invertedSwapThreshold==null?ln.swapThreshold:ln.invertedSwapThreshold,yo,Da===Kr);var pn;if(ai!==0){var mn=Gi(qn);do mn-=ai,pn=hi.children[mn];while(pn&&(Xn(pn,"display")==="none"||pn===Yn))}if(ai===0||pn===Kr)return fi(!1);Da=Kr,Ya=ai;var Sn=Kr.nextElementSibling,vn=!1;vn=ai===1;var bn=Eo(ui,Wr,qn,Yr,Kr,Zr,Gr,vn);if(bn!==!1)return(bn===1||bn===-1)&&(vn=bn===1),ds=!0,setTimeout(Tu,30),Ti(),vn&&!Sn?Wr.appendChild(qn):Kr.parentNode.insertBefore(qn,vn?Sn:Kr),en&&fl(en,0,an-en.scrollTop),hi=qn.parentNode,Ui!==void 0&&!yo&&(So=Math.abs(Ui-yi(Kr)[xn])),oi(),fi(!0)}if(Wr.contains(qn))return fi(!1)}return!1},_ignoreWhileAnimating:null,_offMoveEvents:function(){Zn(document,"mousemove",this._onTouchMove),Zn(document,"touchmove",this._onTouchMove),Zn(document,"pointermove",this._onTouchMove),Zn(document,"dragover",ga),Zn(document,"mousemove",ga),Zn(document,"touchmove",ga)},_offUpEvents:function(){var Gr=this.el.ownerDocument;Zn(Gr,"mouseup",this._onDrop),Zn(Gr,"touchend",this._onDrop),Zn(Gr,"pointerup",this._onDrop),Zn(Gr,"pointercancel",this._onDrop),Zn(Gr,"touchcancel",this._onDrop),Zn(document,"selectstart",this)},_onDrop:function(Gr){var Wr=this.el,Kr=this.options;if(Fi=Gi(qn),oa=Gi(qn,Kr.draggable),Di("drop",this,{evt:Gr}),hi=qn&&qn.parentNode,Fi=Gi(qn),oa=Gi(qn,Kr.draggable),Qn.eventCanceled){this._nulling();return}Pa=!1,yo=!1,$a=!1,clearInterval(this._loopId),clearTimeout(this._dragStartTimer),ps(this.cloneId),ps(this._dragStartId),this.nativeDraggable&&(Zn(document,"drop",this),Zn(Wr,"dragstart",this._onDragStart)),this._offMoveEvents(),this._offUpEvents(),Xa&&Xn(document.body,"user-select",""),Xn(qn,"transform",""),Gr&&(za&&(Gr.cancelable&&Gr.preventDefault(),!Kr.dropBubble&&Gr.stopPropagation()),Yn&&Yn.parentNode&&Yn.parentNode.removeChild(Yn),(ui===hi||wi&&wi.lastPutMode!=="clone")&&ci&&ci.parentNode&&ci.parentNode.removeChild(ci),qn&&(this.nativeDraggable&&Zn(qn,"dragend",this),us(qn),qn.style["will-change"]="",za&&!Pa&&Mi(qn,wi?wi.options.ghostClass:this.options.ghostClass,!1),Mi(qn,this.options.chosenClass,!1),Oi({sortable:this,name:"unchoose",toEl:hi,newIndex:null,newDraggableIndex:null,originalEvent:Gr}),ui!==hi?(Fi>=0&&(Oi({rootEl:hi,name:"add",toEl:hi,fromEl:ui,originalEvent:Gr}),Oi({sortable:this,name:"remove",toEl:hi,originalEvent:Gr}),Oi({rootEl:hi,name:"sort",toEl:hi,fromEl:ui,originalEvent:Gr}),Oi({sortable:this,name:"sort",toEl:hi,originalEvent:Gr})),wi&&wi.save()):Fi!==Ra&&Fi>=0&&(Oi({sortable:this,name:"update",toEl:hi,originalEvent:Gr}),Oi({sortable:this,name:"sort",toEl:hi,originalEvent:Gr})),Qn.active&&((Fi==null||Fi===-1)&&(Fi=Ra,oa=Ka),Oi({sortable:this,name:"end",toEl:hi,originalEvent:Gr}),this.save()))),this._nulling()},_nulling:function(){Di("nulling",this),ui=qn=hi=Yn=va=ci=xo=sa=ma=zi=za=Fi=oa=Ra=Ka=Da=Ya=wi=vo=Qn.dragged=Qn.ghost=Qn.clone=Qn.active=null,Ao.forEach(function(Gr){Gr.checked=!0}),Ao.length=os=ss=0},handleEvent:function(Gr){switch(Gr.type){case"drop":case"dragend":this._onDrop(Gr);break;case"dragenter":case"dragover":qn&&(this._onDragOver(Gr),Eu(Gr));break;case"selectstart":Gr.preventDefault();break}},toArray:function(){for(var Gr=[],Wr,Kr=this.el.children,Yr=0,Zr=Kr.length,tn=this.options;Yr<Zr;Yr++)Wr=Kr[Yr],Vi(Wr,tn.draggable,this.el,!1)&&Gr.push(Wr.getAttribute(tn.dataIdAttr)||Cu(Wr));return Gr},sort:function(Gr,Wr){var Kr={},Yr=this.el;this.toArray().forEach(function(Zr,tn){var ln=Yr.children[tn];Vi(ln,this.options.draggable,Yr,!1)&&(Kr[Zr]=ln)},this),Wr&&this.captureAnimationState(),Gr.forEach(function(Zr){Kr[Zr]&&(Yr.removeChild(Kr[Zr]),Yr.appendChild(Kr[Zr]))}),Wr&&this.animateAll()},save:function(){var Gr=this.options.store;Gr&&Gr.set&&Gr.set(this)},closest:function(Gr,Wr){return Vi(Gr,Wr||this.options.draggable,this.el,!1)},option:function(Gr,Wr){var Kr=this.options;if(Wr===void 0)return Kr[Gr];var Yr=Za.modifyOption(this,Gr,Wr);typeof Yr<"u"?Kr[Gr]=Yr:Kr[Gr]=Wr,Gr==="group"&&gl(Kr)},destroy:function(){Di("destroy",this);var Gr=this.el;Gr[Pi]=null,Zn(Gr,"mousedown",this._onTapStart),Zn(Gr,"touchstart",this._onTapStart),Zn(Gr,"pointerdown",this._onTapStart),this.nativeDraggable&&(Zn(Gr,"dragover",this),Zn(Gr,"dragenter",this)),Array.prototype.forEach.call(Gr.querySelectorAll("[draggable]"),function(Wr){Wr.removeAttribute("draggable")}),this._onDrop(),this._disableDelayedDragEvents(),Oo.splice(Oo.indexOf(this.el),1),this.el=Gr=null},_hideClone:function(){if(!sa){if(Di("hideClone",this),Qn.eventCanceled)return;Xn(ci,"display","none"),this.options.removeCloneOnHide&&ci.parentNode&&ci.parentNode.removeChild(ci),sa=!0}},_showClone:function(Gr){if(Gr.lastPutMode!=="clone"){this._hideClone();return}if(sa){if(Di("showClone",this),Qn.eventCanceled)return;qn.parentNode==ui&&!this.options.group.revertClone?ui.insertBefore(ci,qn):va?ui.insertBefore(ci,va):ui.appendChild(ci),this.options.group.revertClone&&this.animate(qn,ci),Xn(ci,"display",""),sa=!1}}};function Eu(Qr){Qr.dataTransfer&&(Qr.dataTransfer.dropEffect="move"),Qr.cancelable&&Qr.preventDefault()}function Eo(Qr,Gr,Wr,Kr,Yr,Zr,tn,ln){var dn,yn=Qr[Pi],wn=yn.options.onMove,kn;return window.CustomEvent&&!ra&&!Ja?dn=new CustomEvent("move",{bubbles:!0,cancelable:!0}):(dn=document.createEvent("Event"),dn.initEvent("move",!0,!0)),dn.to=Gr,dn.from=Qr,dn.dragged=Wr,dn.draggedRect=Kr,dn.related=Yr||Gr,dn.relatedRect=Zr||yi(Gr),dn.willInsertAfter=ln,dn.originalEvent=tn,Qr.dispatchEvent(dn),wn&&(kn=wn.call(yn,dn,tn)),kn}function us(Qr){Qr.draggable=!1}function Tu(){ds=!1}function xu(Qr,Gr,Wr){var Kr=yi(La(Wr.el,0,Wr.options,!0)),Yr=dl(Wr.el,Wr.options,Yn),Zr=10;return Gr?Qr.clientX<Yr.left-Zr||Qr.clientY<Kr.top&&Qr.clientX<Kr.right:Qr.clientY<Yr.top-Zr||Qr.clientY<Kr.bottom&&Qr.clientX<Kr.left}function Su(Qr,Gr,Wr){var Kr=yi(ys(Wr.el,Wr.options.draggable)),Yr=dl(Wr.el,Wr.options,Yn),Zr=10;return Gr?Qr.clientX>Yr.right+Zr||Qr.clientY>Kr.bottom&&Qr.clientX>Kr.left:Qr.clientY>Yr.bottom+Zr||Qr.clientX>Kr.right&&Qr.clientY>Kr.top}function wu(Qr,Gr,Wr,Kr,Yr,Zr,tn,ln){var dn=Kr?Qr.clientY:Qr.clientX,yn=Kr?Wr.height:Wr.width,wn=Kr?Wr.top:Wr.left,kn=Kr?Wr.bottom:Wr.right,An=!1;if(!tn){if(ln&&So<yn*Yr){if(!$a&&(Ya===1?dn>wn+yn*Zr/2:dn<kn-yn*Zr/2)&&($a=!0),$a)An=!0;else if(Ya===1?dn<wn+So:dn>kn-So)return-Ya}else if(dn>wn+yn*(1-Yr)/2&&dn<kn-yn*(1-Yr)/2)return ku(Gr)}return An=An||tn,An&&(dn<wn+yn*Zr/2||dn>kn-yn*Zr/2)?dn>wn+yn/2?1:-1:0}function ku(Qr){return Gi(qn)<Gi(Qr)?1:-1}function Cu(Qr){for(var Gr=Qr.tagName+Qr.className+Qr.src+Qr.href+Qr.textContent,Wr=Gr.length,Kr=0;Wr--;)Kr+=Gr.charCodeAt(Wr);return Kr.toString(36)}function Iu(Qr){Ao.length=0;for(var Gr=Qr.getElementsByTagName("input"),Wr=Gr.length;Wr--;){var Kr=Gr[Wr];Kr.checked&&Ao.push(Kr)}}function wo(Qr){return setTimeout(Qr,0)}function ps(Qr){return clearTimeout(Qr)}Po&&ei(document,"touchmove",function(Qr){(Qn.active||Pa)&&Qr.cancelable&&Qr.preventDefault()});Qn.utils={on:ei,off:Zn,css:Xn,find:ul,is:function(Gr,Wr){return!!Vi(Gr,Wr,Gr,!1)},extend:uu,throttle:cl,closest:Vi,toggleClass:Mi,clone:hl,index:Gi,nextTick:wo,cancelNextTick:ps,detectDirection:ml,getChild:La,expando:Pi};Qn.get=function(Qr){return Qr[Pi]};Qn.mount=function(){for(var Qr=arguments.length,Gr=new Array(Qr),Wr=0;Wr<Qr;Wr++)Gr[Wr]=arguments[Wr];Gr[0].constructor===Array&&(Gr=Gr[0]),Gr.forEach(function(Kr){if(!Kr.prototype||!Kr.prototype.constructor)throw"Sortable: Mounted plugin must be a constructor function, not ".concat({}.toString.call(Kr));Kr.utils&&(Qn.utils=Qi(Qi({},Qn.utils),Kr.utils)),Za.mount(Kr)})};Qn.create=function(Qr,Gr){return new Qn(Qr,Gr)};Qn.version=su;var vi=[],Va,ms,gs=!1,cs,fs,Do,Wa;function Ou(){function Qr(){this.defaults={scroll:!0,forceAutoScrollFallback:!1,scrollSensitivity:30,scrollSpeed:10,bubbleScroll:!0};for(var Gr in this)Gr.charAt(0)==="_"&&typeof this[Gr]=="function"&&(this[Gr]=this[Gr].bind(this))}return Qr.prototype={dragStarted:function(Wr){var Kr=Wr.originalEvent;this.sortable.nativeDraggable?ei(document,"dragover",this._handleAutoScroll):this.options.supportPointer?ei(document,"pointermove",this._handleFallbackAutoScroll):Kr.touches?ei(document,"touchmove",this._handleFallbackAutoScroll):ei(document,"mousemove",this._handleFallbackAutoScroll)},dragOverCompleted:function(Wr){var Kr=Wr.originalEvent;!this.options.dragOverBubble&&!Kr.rootEl&&this._handleAutoScroll(Kr)},drop:function(){this.sortable.nativeDraggable?Zn(document,"dragover",this._handleAutoScroll):(Zn(document,"pointermove",this._handleFallbackAutoScroll),Zn(document,"touchmove",this._handleFallbackAutoScroll),Zn(document,"mousemove",this._handleFallbackAutoScroll)),al(),ko(),cu()},nulling:function(){Do=ms=Va=gs=Wa=cs=fs=null,vi.length=0},_handleFallbackAutoScroll:function(Wr){this._handleAutoScroll(Wr,!0)},_handleAutoScroll:function(Wr,Kr){var Yr=this,Zr=(Wr.touches?Wr.touches[0]:Wr).clientX,tn=(Wr.touches?Wr.touches[0]:Wr).clientY,ln=document.elementFromPoint(Zr,tn);if(Do=Wr,Kr||this.options.forceAutoScrollFallback||Ja||ra||Xa){hs(Wr,this.options,ln,Kr);var dn=la(ln,!0);gs&&(!Wa||Zr!==cs||tn!==fs)&&(Wa&&al(),Wa=setInterval(function(){var yn=la(document.elementFromPoint(Zr,tn),!0);yn!==dn&&(dn=yn,ko()),hs(Wr,Yr.options,yn,Kr)},10),cs=Zr,fs=tn)}else{if(!this.options.bubbleScroll||la(ln,!0)===Xi()){ko();return}hs(Wr,this.options,la(ln,!1),!1)}}},ta(Qr,{pluginName:"scroll",initializeByDefault:!0})}function ko(){vi.forEach(function(Qr){clearInterval(Qr.pid)}),vi=[]}function al(){clearInterval(Wa)}var hs=cl(function(Qr,Gr,Wr,Kr){if(Gr.scroll){var Yr=(Qr.touches?Qr.touches[0]:Qr).clientX,Zr=(Qr.touches?Qr.touches[0]:Qr).clientY,tn=Gr.scrollSensitivity,ln=Gr.scrollSpeed,dn=Xi(),yn=!1,wn;ms!==Wr&&(ms=Wr,ko(),Va=Gr.scroll,wn=Gr.scrollFn,Va===!0&&(Va=la(Wr,!0)));var kn=0,An=Va;do{var Gn=An,jn=yi(Gn),Vn=jn.top,ti=jn.bottom,Ti=jn.left,fi=jn.right,oi=jn.width,Jn=jn.height,mi=void 0,ai=void 0,Ui=Gn.scrollWidth,gi=Gn.scrollHeight,ri=Xn(Gn),xn=Gn.scrollLeft,en=Gn.scrollTop;Gn===dn?(mi=oi<Ui&&(ri.overflowX==="auto"||ri.overflowX==="scroll"||ri.overflowX==="visible"),ai=Jn<gi&&(ri.overflowY==="auto"||ri.overflowY==="scroll"||ri.overflowY==="visible")):(mi=oi<Ui&&(ri.overflowX==="auto"||ri.overflowX==="scroll"),ai=Jn<gi&&(ri.overflowY==="auto"||ri.overflowY==="scroll"));var an=mi&&(Math.abs(fi-Yr)<=tn&&xn+oi<Ui)-(Math.abs(Ti-Yr)<=tn&&!!xn),pn=ai&&(Math.abs(ti-Zr)<=tn&&en+Jn<gi)-(Math.abs(Vn-Zr)<=tn&&!!en);if(!vi[kn])for(var mn=0;mn<=kn;mn++)vi[mn]||(vi[mn]={});(vi[kn].vx!=an||vi[kn].vy!=pn||vi[kn].el!==Gn)&&(vi[kn].el=Gn,vi[kn].vx=an,vi[kn].vy=pn,clearInterval(vi[kn].pid),(an!=0||pn!=0)&&(yn=!0,vi[kn].pid=setInterval(function(){Kr&&this.layer===0&&Qn.active._onTouchMove(Do);var Sn=vi[this.layer].vy?vi[this.layer].vy*ln:0,vn=vi[this.layer].vx?vi[this.layer].vx*ln:0;typeof wn=="function"&&wn.call(Qn.dragged.parentNode[Pi],vn,Sn,Qr,Do,vi[this.layer].el)!=="continue"||fl(vi[this.layer].el,vn,Sn)}.bind({layer:kn}),24))),kn++}while(Gr.bubbleScroll&&An!==dn&&(An=la(An,!1)));gs=yn}},30),bl=function(Gr){var Wr=Gr.originalEvent,Kr=Gr.putSortable,Yr=Gr.dragEl,Zr=Gr.activeSortable,tn=Gr.dispatchSortableEvent,ln=Gr.hideGhostForTarget,dn=Gr.unhideGhostForTarget;if(Wr){var yn=Kr||Zr;ln();var wn=Wr.changedTouches&&Wr.changedTouches.length?Wr.changedTouches[0]:Wr,kn=document.elementFromPoint(wn.clientX,wn.clientY);dn(),yn&&!yn.el.contains(kn)&&(tn("spill"),this.onSpill({dragEl:Yr,putSortable:Kr}))}};function bs(){}bs.prototype={startIndex:null,dragStart:function(Gr){var Wr=Gr.oldDraggableIndex;this.startIndex=Wr},onSpill:function(Gr){var Wr=Gr.dragEl,Kr=Gr.putSortable;this.sortable.captureAnimationState(),Kr&&Kr.captureAnimationState();var Yr=La(this.sortable.el,this.startIndex,this.options);Yr?this.sortable.el.insertBefore(Wr,Yr):this.sortable.el.appendChild(Wr),this.sortable.animateAll(),Kr&&Kr.animateAll()},drop:bl};ta(bs,{pluginName:"revertOnSpill"});function Es(){}Es.prototype={onSpill:function(Gr){var Wr=Gr.dragEl,Kr=Gr.putSortable,Yr=Kr||this.sortable;Yr.captureAnimationState(),Wr.parentNode&&Wr.parentNode.removeChild(Wr),Yr.animateAll()},drop:bl};ta(Es,{pluginName:"removeOnSpill"});Qn.mount(new Ou);Qn.mount(Es,bs);var El=Qn;var Ro=class{observerInitialized=!1;layoutMode;containerSelector;spacing;masonryBreakpoint;constructor(Gr={}){this.layoutMode=Gr.layoutMode??"justified",this.containerSelector=Gr.containerSelector??"#gallery-container",this.spacing=Gr.spacing??8,this.masonryBreakpoint=Gr.masonryBreakpoint??640}setLayoutMode(Gr){this.layoutMode=Gr,this.computeLayout()}computeLayout(){this.layoutMode==="masonry"?this.computeMasonry():this.computeJustified()}computeMasonry(){let Gr=document.querySelector(this.containerSelector);if(!Gr)return;let Wr=this.spacing,Kr=Gr.offsetWidth;if(Kr===0){requestAnimationFrame(()=>this.computeMasonry());return}let Yr=Kr<this.masonryBreakpoint?1:3,Zr=(Kr+Wr)/Yr-Wr,tn=new Array(Yr).fill(0);Gr.querySelectorAll(".gallery-item").forEach(dn=>{let yn=parseFloat(dn.dataset.width),wn=parseFloat(dn.dataset.height);if(!yn||!wn)return;let An=wn/yn*Zr,Gn=0;for(let ti=1;ti<Yr;ti++)tn[ti]<tn[Gn]&&(Gn=ti);let jn=(Zr+Wr)*Gn,Vn=tn[Gn];Object.assign(dn.style,{position:"absolute",width:`${Zr}px`,height:`${An}px`,left:`${jn}px`,top:`${Vn}px`}),tn[Gn]=Vn+An+Wr}),Gr.style.height=`${Math.max(...tn)}px`}computeJustified(){let Gr=document.querySelector(this.containerSelector);if(!Gr)return;let Wr=this.spacing,Kr=Gr.offsetWidth;if(Kr===0){requestAnimationFrame(()=>this.computeJustified());return}let Yr=Array.from(Gr.querySelectorAll(".gallery-item")),Zr=[],tn=0,ln=0;Yr.forEach(dn=>{Object.assign(dn.style,{position:"absolute",left:"0px",top:"0px",width:"auto",height:"auto"})});for(let dn=0;dn<Yr.length;dn++){let yn=Yr[dn],wn=parseFloat(yn.dataset.width),kn=parseFloat(yn.dataset.height);if(!wn||!kn)continue;let An=wn/kn;Zr.push({tile:yn,aspectRatio:An,imgW:wn,imgH:kn}),tn+=An;let Gn=(Kr-(Zr.length-1)*Wr)/tn;if(Gn<300||dn===Yr.length-1){let jn=0;for(let Vn of Zr){let ti=Gn*Vn.aspectRatio;Object.assign(Vn.tile.style,{position:"absolute",top:`${ln}px`,left:`${jn}px`,width:`${ti}px`,height:`${Gn}px`}),jn+=ti+Wr}ln+=Gn+Wr,Zr=[],tn=0}}Gr.style.position="relative",Gr.style.height=`${ln}px`}observe(){if(this.observerInitialized)return;this.observerInitialized=!0;let Gr=document.querySelector(this.containerSelector);if(!Gr)return;if(typeof ResizeObserver<"u"){let Kr=new ResizeObserver(()=>this.computeLayout());Gr.parentElement&&Kr.observe(Gr.parentElement)}new MutationObserver(()=>{this.computeLayout()}).observe(Gr,{childList:!0,subtree:!0})}init(Gr={}){document.addEventListener("DOMContentLoaded",()=>{document.querySelector(Gr.containerSelector??"#gallery-container")&&(this.computeLayout(),this.observe())})}};var No=class Qr{startX=0;threshold=50;observer;static initialized=!1;static id="photo-dialog";constructor(){this.observer=new MutationObserver(this.handleMutation.bind(this))}connect(){this.onTouchStart=this.onTouchStart.bind(this),this.onTouchEnd=this.onTouchEnd.bind(this),console.log("PhotoDialog: Connected touch event handlers.")}static maybeInitForElement(Gr){if(Gr.id!==Qr.id||Qr.initialized)return;let Wr=new Qr;Wr.connect(),document&&document.body?(document.body.addEventListener("touchstart",Wr.onTouchStart),document.body.addEventListener("touchend",Wr.onTouchEnd),Wr.observe(),Qr.initialized=!0):console.warn("document.body not available for PhotoDialog event listeners")}onTouchStart(Gr){this.startX=Gr.touches[0].clientX}onTouchEnd(Gr){let Kr=Gr.changedTouches[0].clientX-this.startX;if(Math.abs(Kr)>this.threshold){let Yr=Kr>0?"swiperight":"swipeleft";Gr.target.dispatchEvent(new CustomEvent(Yr,{bubbles:!0}))}}observe(){this.observer.observe(document.body,{childList:!0,subtree:!0})}disconnect(){this.observer.disconnect(),document.body.removeEventListener("touchstart",this.onTouchStart),document.body.removeEventListener("touchend",this.onTouchEnd),Qr.initialized=!1}handleMutation(){document.getElementById(Qr.id)||(console.log("PhotoDialog not found, removing event listeners"),this.disconnect())}};var Ts={};ru(Ts,{dataURLToBlob:()=>eo,doResize:()=>to,readFileAsDataURL:()=>Ma});function Ma(Qr){return new Promise((Gr,Wr)=>{let Kr=new FileReader;Kr.onload=()=>Gr(Kr.result),Kr.onerror=Wr,Kr.readAsDataURL(Qr)})}function eo(Qr){let[Gr,Wr]=Qr.split(","),Kr=/:(.*?);/.exec(Gr);if(!Kr)throw new Error("Invalid data URL");let Yr=Kr[1],Zr=atob(Wr),tn=new Uint8Array(Zr.length);for(let ln=0;ln<Zr.length;ln++)tn[ln]=Zr.charCodeAt(ln);return new Blob([tn],{type:Yr})}function Tl(Qr){let Gr=Qr.split(",")[1];return Math.ceil(Gr.length*3/4)}function Au(Qr,Gr){return new Promise((Wr,Kr)=>{let Yr=new Image;Yr.onload=()=>{let Zr;Gr.mode==="cover"?Zr=Math.max(Gr.width/Yr.width,Gr.height/Yr.height):Gr.mode==="contain"?Zr=Math.min(Gr.width/Yr.width,Gr.height/Yr.height):Zr=1;let tn=Math.round(Yr.width*Zr),ln=Math.round(Yr.height*Zr),dn=document.createElement("canvas");dn.width=tn,dn.height=ln;let yn=dn.getContext("2d");if(!yn)return Kr(new Error("Failed to get canvas context"));yn.fillStyle="#fff",yn.fillRect(0,0,tn,ln),yn.imageSmoothingEnabled=!0,yn.imageSmoothingQuality="high",yn.drawImage(Yr,0,0,tn,ln),Wr({dataUrl:dn.toDataURL("image/jpeg",Gr.quality),width:tn,height:ln})},Yr.onerror=Zr=>Kr(Zr),Yr.src=Qr})}async function to(Qr,Gr){let Wr=null,Kr=0,Yr=101;for(;Yr-Kr>1;){let Zr=Math.round((Kr+Yr)/2),tn=await Au(Qr,{width:Gr.width,height:Gr.height,quality:Zr/100,mode:Gr.mode});Tl(tn.dataUrl)<Gr.maxSize?(Kr=Zr,Wr=tn):Yr=Zr}if(!Wr)throw new Error("Failed to compress image");return{path:Wr.dataUrl,mime:"image/jpeg",size:Tl(Wr.dataUrl),width:Wr.width,height:Wr.height}}var xl=po(mo());var Lo=class{handleAvatarImageSelect(Gr){let Wr=Gr.files?.[0];Wr&&Ma(Wr).then(Kr=>{let Yr=document.createElement("img");typeof Kr=="string"?Yr.src=Kr:(console.error("Expected dataUrl to be a string, got:",typeof Kr),Yr.src=""),Yr.className="rounded-full w-full h-full object-cover",Yr.alt="Avatar preview";let Zr=Gr.closest("form")?.querySelector("#image-preview");Zr&&(Zr.innerHTML="",Zr.appendChild(Yr))})}async updateProfile(Gr){let Wr=new FormData(Gr),Kr=Wr.get("file");if(Kr instanceof File&&Kr?.type?.startsWith?.("image/"))try{let Yr=await Ma(Kr);if(!Yr||typeof Yr!="string"){console.error("Failed to read file as data URL");return}let Zr=await to(Yr,{width:2e3,height:2e3,maxSize:1e3*1e3,mode:"contain"}),tn=eo(Zr.path);Wr.set("file",tn,Kr.name)}catch(Yr){console.error("Error resizing image:",Yr),Wr.delete("file")}xl.default.ajax("PUT","/actions/profile",{swap:"none",values:Object.fromEntries(Wr),source:Gr})}};var Yo=typeof self<"u"?self:global,so=typeof navigator<"u",Du=so&&typeof HTMLImageElement>"u",qo=!(typeof global>"u"||typeof process>"u"||!process.versions||!process.versions.node),$o=Yo.Buffer,Mo=Yo.BigInt,Jo=!!$o,Pu=Qr=>Qr;function Uo(Qr,Gr=Pu){if(qo)try{return typeof ns=="function"?Promise.resolve(Gr(ns(Qr))):import(Qr).then(Gr)}catch{console.warn(`Couldn't load ${Qr}`)}}var Fs=Yo.fetch,Ru=Qr=>Fs=Qr;if(!Yo.fetch){let Qr=Uo("http",Kr=>Kr),Gr=Uo("https",Kr=>Kr),Wr=(Kr,{headers:Yr}={})=>new Promise(async(Zr,tn)=>{let{port:ln,hostname:dn,pathname:yn,protocol:wn,search:kn}=new URL(Kr),An={method:"GET",hostname:dn,path:encodeURI(yn)+kn,headers:Yr};ln!==""&&(An.port=Number(ln));let Gn=(wn==="https:"?await Gr:await Qr).request(An,jn=>{if(jn.statusCode===301||jn.statusCode===302){let Vn=new URL(jn.headers.location,Kr).toString();return Wr(Vn,{headers:Yr}).then(Zr).catch(tn)}Zr({status:jn.statusCode,arrayBuffer:()=>new Promise(Vn=>{let ti=[];jn.on("data",Ti=>ti.push(Ti)),jn.on("end",()=>Vn(Buffer.concat(ti)))})})});Gn.on("error",tn),Gn.end()});Ru(Wr)}function Kn(Qr,Gr,Wr){return Gr in Qr?Object.defineProperty(Qr,Gr,{value:Wr,enumerable:!0,configurable:!0,writable:!0}):Qr[Gr]=Wr,Qr}var Bo=Qr=>Fl(Qr)?void 0:Qr,Nu=Qr=>Qr!==void 0;function Fl(Qr){return Qr===void 0||(Qr instanceof Map?Qr.size===0:Object.values(Qr).filter(Nu).length===0)}function Ei(Qr){let Gr=new Error(Qr);throw delete Gr.stack,Gr}function ba(Qr){return(Qr=function(Gr){for(;Gr.endsWith("\0");)Gr=Gr.slice(0,-1);return Gr}(Qr).trim())===""?void 0:Qr}function ks(Qr){let Gr=function(Wr){let Kr=0;return Wr.ifd0.enabled&&(Kr+=1024),Wr.exif.enabled&&(Kr+=2048),Wr.makerNote&&(Kr+=2048),Wr.userComment&&(Kr+=1024),Wr.gps.enabled&&(Kr+=512),Wr.interop.enabled&&(Kr+=100),Wr.ifd1.enabled&&(Kr+=1024),Kr+2048}(Qr);return Qr.jfif.enabled&&(Gr+=50),Qr.xmp.enabled&&(Gr+=2e4),Qr.iptc.enabled&&(Gr+=14e3),Qr.icc.enabled&&(Gr+=6e3),Gr}var Cs=Qr=>String.fromCharCode.apply(null,Qr),Sl=typeof TextDecoder<"u"?new TextDecoder("utf-8"):void 0;function ql(Qr){return Sl?Sl.decode(Qr):Jo?Buffer.from(Qr).toString("utf8"):decodeURIComponent(escape(Cs(Qr)))}var Ki=class Qr{static from(Gr,Wr){return Gr instanceof this&&Gr.le===Wr?Gr:new Qr(Gr,void 0,void 0,Wr)}constructor(Gr,Wr=0,Kr,Yr){if(typeof Yr=="boolean"&&(this.le=Yr),Array.isArray(Gr)&&(Gr=new Uint8Array(Gr)),Gr===0)this.byteOffset=0,this.byteLength=0;else if(Gr instanceof ArrayBuffer){Kr===void 0&&(Kr=Gr.byteLength-Wr);let Zr=new DataView(Gr,Wr,Kr);this._swapDataView(Zr)}else if(Gr instanceof Uint8Array||Gr instanceof DataView||Gr instanceof Qr){Kr===void 0&&(Kr=Gr.byteLength-Wr),(Wr+=Gr.byteOffset)+Kr>Gr.byteOffset+Gr.byteLength&&Ei("Creating view outside of available memory in ArrayBuffer");let Zr=new DataView(Gr.buffer,Wr,Kr);this._swapDataView(Zr)}else if(typeof Gr=="number"){let Zr=new DataView(new ArrayBuffer(Gr));this._swapDataView(Zr)}else Ei("Invalid input argument for BufferView: "+Gr)}_swapArrayBuffer(Gr){this._swapDataView(new DataView(Gr))}_swapBuffer(Gr){this._swapDataView(new DataView(Gr.buffer,Gr.byteOffset,Gr.byteLength))}_swapDataView(Gr){this.dataView=Gr,this.buffer=Gr.buffer,this.byteOffset=Gr.byteOffset,this.byteLength=Gr.byteLength}_lengthToEnd(Gr){return this.byteLength-Gr}set(Gr,Wr,Kr=Qr){return Gr instanceof DataView||Gr instanceof Qr?Gr=new Uint8Array(Gr.buffer,Gr.byteOffset,Gr.byteLength):Gr instanceof ArrayBuffer&&(Gr=new Uint8Array(Gr)),Gr instanceof Uint8Array||Ei("BufferView.set(): Invalid data argument."),this.toUint8().set(Gr,Wr),new Kr(this,Wr,Gr.byteLength)}subarray(Gr,Wr){return Wr=Wr||this._lengthToEnd(Gr),new Qr(this,Gr,Wr)}toUint8(){return new Uint8Array(this.buffer,this.byteOffset,this.byteLength)}getUint8Array(Gr,Wr){return new Uint8Array(this.buffer,this.byteOffset+Gr,Wr)}getString(Gr=0,Wr=this.byteLength){return ql(this.getUint8Array(Gr,Wr))}getLatin1String(Gr=0,Wr=this.byteLength){let Kr=this.getUint8Array(Gr,Wr);return Cs(Kr)}getUnicodeString(Gr=0,Wr=this.byteLength){let Kr=[];for(let Yr=0;Yr<Wr&&Gr+Yr<this.byteLength;Yr+=2)Kr.push(this.getUint16(Gr+Yr));return Cs(Kr)}getInt8(Gr){return this.dataView.getInt8(Gr)}getUint8(Gr){return this.dataView.getUint8(Gr)}getInt16(Gr,Wr=this.le){return this.dataView.getInt16(Gr,Wr)}getInt32(Gr,Wr=this.le){return this.dataView.getInt32(Gr,Wr)}getUint16(Gr,Wr=this.le){return this.dataView.getUint16(Gr,Wr)}getUint32(Gr,Wr=this.le){return this.dataView.getUint32(Gr,Wr)}getFloat32(Gr,Wr=this.le){return this.dataView.getFloat32(Gr,Wr)}getFloat64(Gr,Wr=this.le){return this.dataView.getFloat64(Gr,Wr)}getFloat(Gr,Wr=this.le){return this.dataView.getFloat32(Gr,Wr)}getDouble(Gr,Wr=this.le){return this.dataView.getFloat64(Gr,Wr)}getUintBytes(Gr,Wr,Kr){switch(Wr){case 1:return this.getUint8(Gr,Kr);case 2:return this.getUint16(Gr,Kr);case 4:return this.getUint32(Gr,Kr);case 8:return this.getUint64&&this.getUint64(Gr,Kr)}}getUint(Gr,Wr,Kr){switch(Wr){case 8:return this.getUint8(Gr,Kr);case 16:return this.getUint16(Gr,Kr);case 32:return this.getUint32(Gr,Kr);case 64:return this.getUint64&&this.getUint64(Gr,Kr)}}toString(Gr){return this.dataView.toString(Gr,this.constructor.name)}ensureChunk(){}};function Is(Qr,Gr){Ei(`${Qr} '${Gr}' was not loaded, try using full build of exifr.`)}var lo=class extends Map{constructor(Gr){super(),this.kind=Gr}get(Gr,Wr){return this.has(Gr)||Is(this.kind,Gr),Wr&&(Gr in Wr||function(Kr,Yr){Ei(`Unknown ${Kr} '${Yr}'.`)}(this.kind,Gr),Wr[Gr].enabled||Is(this.kind,Gr)),super.get(Gr)}keyList(){return Array.from(this.keys())}},Yi=new lo("file parser"),bi=new lo("segment parser"),Ji=new lo("file reader");function Lu(Qr,Gr){return typeof Qr=="string"?wl(Qr,Gr):so&&!Du&&Qr instanceof HTMLImageElement?wl(Qr.src,Gr):Qr instanceof Uint8Array||Qr instanceof ArrayBuffer||Qr instanceof DataView?new Ki(Qr):so&&Qr instanceof Blob?Os(Qr,Gr,"blob",Sa):void Ei("Invalid input argument")}function wl(Qr,Gr){return(Wr=Qr).startsWith("data:")||Wr.length>1e4?As(Qr,Gr,"base64"):qo&&Qr.includes("://")?Os(Qr,Gr,"url",xa):qo?As(Qr,Gr,"fs"):so?Os(Qr,Gr,"url",xa):void Ei("Invalid input argument");var Wr}async function Os(Qr,Gr,Wr,Kr){return Ji.has(Wr)?As(Qr,Gr,Wr):Kr?async function(Yr,Zr){let tn=await Zr(Yr);return new Ki(tn)}(Qr,Kr):void Ei(`Parser ${Wr} is not loaded`)}async function As(Qr,Gr,Wr){let Kr=new(Ji.get(Wr))(Qr,Gr);return await Kr.read(),Kr}var xa=Qr=>Fs(Qr).then(Gr=>Gr.arrayBuffer()),Sa=Qr=>new Promise((Gr,Wr)=>{let Kr=new FileReader;Kr.onloadend=()=>Gr(Kr.result||new ArrayBuffer),Kr.onerror=Wr,Kr.readAsArrayBuffer(Qr)}),Ds=class extends Map{get tagKeys(){return this.allKeys||(this.allKeys=Array.from(this.keys())),this.allKeys}get tagValues(){return this.allValues||(this.allValues=Array.from(this.values())),this.allValues}};function pi(Qr,Gr,Wr){let Kr=new Ds;for(let[Yr,Zr]of Wr)Kr.set(Yr,Zr);if(Array.isArray(Gr))for(let Yr of Gr)Qr.set(Yr,Kr);else Qr.set(Gr,Kr);return Kr}function wa(Qr,Gr,Wr){let Kr,Yr=Qr.get(Gr);for(Kr of Wr)Yr.set(Kr[0],Kr[1])}var Si=new Map,Wi=new Map,da=new Map,ua=["chunked","firstChunkSize","firstChunkSizeNode","firstChunkSizeBrowser","chunkSize","chunkLimit"],Ha=["jfif","xmp","icc","iptc","ihdr"],ka=["tiff",...Ha],di=["ifd0","ifd1","exif","gps","interop"],ca=[...ka,...di],fa=["makerNote","userComment"],Ga=["translateKeys","translateValues","reviveValues","multiSegment"],ha=[...Ga,"sanitize","mergeOutput","silentErrors"],Ho=class{get translate(){return this.translateKeys||this.translateValues||this.reviveValues}},ya=class extends Ho{get needed(){return this.enabled||this.deps.size>0}constructor(Gr,Wr,Kr,Yr){if(super(),Kn(this,"enabled",!1),Kn(this,"skip",new Set),Kn(this,"pick",new Set),Kn(this,"deps",new Set),Kn(this,"translateKeys",!1),Kn(this,"translateValues",!1),Kn(this,"reviveValues",!1),this.key=Gr,this.enabled=Wr,this.parse=this.enabled,this.applyInheritables(Yr),this.canBeFiltered=di.includes(Gr),this.canBeFiltered&&(this.dict=Si.get(Gr)),Kr!==void 0)if(Array.isArray(Kr))this.parse=this.enabled=!0,this.canBeFiltered&&Kr.length>0&&this.translateTagSet(Kr,this.pick);else if(typeof Kr=="object"){if(this.enabled=!0,this.parse=Kr.parse!==!1,this.canBeFiltered){let{pick:Zr,skip:tn}=Kr;Zr&&Zr.length>0&&this.translateTagSet(Zr,this.pick),tn&&tn.length>0&&this.translateTagSet(tn,this.skip)}this.applyInheritables(Kr)}else Kr===!0||Kr===!1?this.parse=this.enabled=Kr:Ei(`Invalid options argument: ${Kr}`)}applyInheritables(Gr){let Wr,Kr;for(Wr of Ga)Kr=Gr[Wr],Kr!==void 0&&(this[Wr]=Kr)}translateTagSet(Gr,Wr){if(this.dict){let Kr,Yr,{tagKeys:Zr,tagValues:tn}=this.dict;for(Kr of Gr)typeof Kr=="string"?(Yr=tn.indexOf(Kr),Yr===-1&&(Yr=Zr.indexOf(Number(Kr))),Yr!==-1&&Wr.add(Number(Zr[Yr]))):Wr.add(Kr)}else for(let Kr of Gr)Wr.add(Kr)}finalizeFilters(){!this.enabled&&this.deps.size>0?(this.enabled=!0,Go(this.pick,this.deps)):this.enabled&&this.pick.size>0&&Go(this.pick,this.deps)}},Ai={jfif:!1,tiff:!0,xmp:!1,icc:!1,iptc:!1,ifd0:!0,ifd1:!1,exif:!0,gps:!0,interop:!1,ihdr:void 0,makerNote:!1,userComment:!1,multiSegment:!1,skip:[],pick:[],translateKeys:!0,translateValues:!0,reviveValues:!0,sanitize:!0,mergeOutput:!0,silentErrors:!0,chunked:!0,firstChunkSize:void 0,firstChunkSizeNode:512,firstChunkSizeBrowser:65536,chunkSize:65536,chunkLimit:5},kl=new Map,ia=class extends Ho{static useCached(Gr){let Wr=kl.get(Gr);return Wr!==void 0||(Wr=new this(Gr),kl.set(Gr,Wr)),Wr}constructor(Gr){super(),Gr===!0?this.setupFromTrue():Gr===void 0?this.setupFromUndefined():Array.isArray(Gr)?this.setupFromArray(Gr):typeof Gr=="object"?this.setupFromObject(Gr):Ei(`Invalid options argument ${Gr}`),this.firstChunkSize===void 0&&(this.firstChunkSize=so?this.firstChunkSizeBrowser:this.firstChunkSizeNode),this.mergeOutput&&(this.ifd1.enabled=!1),this.filterNestedSegmentTags(),this.traverseTiffDependencyTree(),this.checkLoadedPlugins()}setupFromUndefined(){let Gr;for(Gr of ua)this[Gr]=Ai[Gr];for(Gr of ha)this[Gr]=Ai[Gr];for(Gr of fa)this[Gr]=Ai[Gr];for(Gr of ca)this[Gr]=new ya(Gr,Ai[Gr],void 0,this)}setupFromTrue(){let Gr;for(Gr of ua)this[Gr]=Ai[Gr];for(Gr of ha)this[Gr]=Ai[Gr];for(Gr of fa)this[Gr]=!0;for(Gr of ca)this[Gr]=new ya(Gr,!0,void 0,this)}setupFromArray(Gr){let Wr;for(Wr of ua)this[Wr]=Ai[Wr];for(Wr of ha)this[Wr]=Ai[Wr];for(Wr of fa)this[Wr]=Ai[Wr];for(Wr of ca)this[Wr]=new ya(Wr,!1,void 0,this);this.setupGlobalFilters(Gr,void 0,di)}setupFromObject(Gr){let Wr;for(Wr of(di.ifd0=di.ifd0||di.image,di.ifd1=di.ifd1||di.thumbnail,Object.assign(this,Gr),ua))this[Wr]=xs(Gr[Wr],Ai[Wr]);for(Wr of ha)this[Wr]=xs(Gr[Wr],Ai[Wr]);for(Wr of fa)this[Wr]=xs(Gr[Wr],Ai[Wr]);for(Wr of ka)this[Wr]=new ya(Wr,Ai[Wr],Gr[Wr],this);for(Wr of di)this[Wr]=new ya(Wr,Ai[Wr],Gr[Wr],this.tiff);this.setupGlobalFilters(Gr.pick,Gr.skip,di,ca),Gr.tiff===!0?this.batchEnableWithBool(di,!0):Gr.tiff===!1?this.batchEnableWithUserValue(di,Gr):Array.isArray(Gr.tiff)?this.setupGlobalFilters(Gr.tiff,void 0,di):typeof Gr.tiff=="object"&&this.setupGlobalFilters(Gr.tiff.pick,Gr.tiff.skip,di)}batchEnableWithBool(Gr,Wr){for(let Kr of Gr)this[Kr].enabled=Wr}batchEnableWithUserValue(Gr,Wr){for(let Kr of Gr){let Yr=Wr[Kr];this[Kr].enabled=Yr!==!1&&Yr!==void 0}}setupGlobalFilters(Gr,Wr,Kr,Yr=Kr){if(Gr&&Gr.length){for(let tn of Yr)this[tn].enabled=!1;let Zr=Cl(Gr,Kr);for(let[tn,ln]of Zr)Go(this[tn].pick,ln),this[tn].enabled=!0}else if(Wr&&Wr.length){let Zr=Cl(Wr,Kr);for(let[tn,ln]of Zr)Go(this[tn].skip,ln)}}filterNestedSegmentTags(){let{ifd0:Gr,exif:Wr,xmp:Kr,iptc:Yr,icc:Zr}=this;this.makerNote?Wr.deps.add(37500):Wr.skip.add(37500),this.userComment?Wr.deps.add(37510):Wr.skip.add(37510),Kr.enabled||Gr.skip.add(700),Yr.enabled||Gr.skip.add(33723),Zr.enabled||Gr.skip.add(34675)}traverseTiffDependencyTree(){let{ifd0:Gr,exif:Wr,gps:Kr,interop:Yr}=this;Yr.needed&&(Wr.deps.add(40965),Gr.deps.add(40965)),Wr.needed&&Gr.deps.add(34665),Kr.needed&&Gr.deps.add(34853),this.tiff.enabled=di.some(Zr=>this[Zr].enabled===!0)||this.makerNote||this.userComment;for(let Zr of di)this[Zr].finalizeFilters()}get onlyTiff(){return!Ha.map(Gr=>this[Gr].enabled).some(Gr=>Gr===!0)&&this.tiff.enabled}checkLoadedPlugins(){for(let Gr of ka)this[Gr].enabled&&!bi.has(Gr)&&Is("segment parser",Gr)}};function Cl(Qr,Gr){let Wr,Kr,Yr,Zr,tn=[];for(Yr of Gr){for(Zr of(Wr=Si.get(Yr),Kr=[],Wr))(Qr.includes(Zr[0])||Qr.includes(Zr[1]))&&Kr.push(Zr[0]);Kr.length&&tn.push([Yr,Kr])}return tn}function xs(Qr,Gr){return Qr!==void 0?Qr:Gr!==void 0?Gr:void 0}function Go(Qr,Gr){for(let Wr of Gr)Qr.add(Wr)}Kn(ia,"default",Ai);var $i=class{constructor(Gr){Kn(this,"parsers",{}),Kn(this,"output",{}),Kn(this,"errors",[]),Kn(this,"pushToErrors",Wr=>this.errors.push(Wr)),this.options=ia.useCached(Gr)}async read(Gr){this.file=await Lu(Gr,this.options)}setup(){if(this.fileParser)return;let{file:Gr}=this,Wr=Gr.getUint16(0);for(let[Kr,Yr]of Yi)if(Yr.canHandle(Gr,Wr))return this.fileParser=new Yr(this.options,this.file,this.parsers),Gr[Kr]=!0;this.file.close&&this.file.close(),Ei("Unknown file format")}async parse(){let{output:Gr,errors:Wr}=this;return this.setup(),this.options.silentErrors?(await this.executeParsers().catch(this.pushToErrors),Wr.push(...this.fileParser.errors)):await this.executeParsers(),this.file.close&&this.file.close(),this.options.silentErrors&&Wr.length>0&&(Gr.errors=Wr),Bo(Gr)}async executeParsers(){let{output:Gr}=this;await this.fileParser.parse();let Wr=Object.values(this.parsers).map(async Kr=>{let Yr=await Kr.parse();Kr.assignToOutput(Gr,Yr)});this.options.silentErrors&&(Wr=Wr.map(Kr=>Kr.catch(this.pushToErrors))),await Promise.all(Wr)}async extractThumbnail(){this.setup();let{options:Gr,file:Wr}=this,Kr=bi.get("tiff",Gr);var Yr;if(Wr.tiff?Yr={start:0,type:"tiff"}:Wr.jpeg&&(Yr=await this.fileParser.getOrFindSegment("tiff")),Yr===void 0)return;let Zr=await this.fileParser.ensureSegmentChunk(Yr),tn=this.parsers.tiff=new Kr(Zr,Gr,Wr),ln=await tn.extractThumbnail();return Wr.close&&Wr.close(),ln}};async function Zo(Qr,Gr){let Wr=new $i(Gr);return await Wr.read(Qr),Wr.parse()}var Mu=Object.freeze({__proto__:null,parse:Zo,Exifr:$i,fileParsers:Yi,segmentParsers:bi,fileReaders:Ji,tagKeys:Si,tagValues:Wi,tagRevivers:da,createDictionary:pi,extendDictionary:wa,fetchUrlAsArrayBuffer:xa,readBlobAsArrayBuffer:Sa,chunkedProps:ua,otherSegments:Ha,segments:ka,tiffBlocks:di,segmentsAndBlocks:ca,tiffExtractables:fa,inheritables:Ga,allFormatters:ha,Options:ia}),Ua=class{constructor(Gr,Wr,Kr){Kn(this,"errors",[]),Kn(this,"ensureSegmentChunk",async Yr=>{let Zr=Yr.start,tn=Yr.size||65536;if(this.file.chunked)if(this.file.available(Zr,tn))Yr.chunk=this.file.subarray(Zr,tn);else try{Yr.chunk=await this.file.readChunk(Zr,tn)}catch(ln){Ei(`Couldn't read segment: ${JSON.stringify(Yr)}. ${ln.message}`)}else this.file.byteLength>Zr+tn?Yr.chunk=this.file.subarray(Zr,tn):Yr.size===void 0?Yr.chunk=this.file.subarray(Zr):Ei("Segment unreachable: "+JSON.stringify(Yr));return Yr.chunk}),this.extendOptions&&this.extendOptions(Gr),this.options=Gr,this.file=Wr,this.parsers=Kr}injectSegment(Gr,Wr){this.options[Gr].enabled&&this.createParser(Gr,Wr)}createParser(Gr,Wr){let Kr=new(bi.get(Gr))(Wr,this.options,this.file);return this.parsers[Gr]=Kr}createParsers(Gr){for(let Wr of Gr){let{type:Kr,chunk:Yr}=Wr,Zr=this.options[Kr];if(Zr&&Zr.enabled){let tn=this.parsers[Kr];tn&&tn.append||tn||this.createParser(Kr,Yr)}}}async readSegments(Gr){let Wr=Gr.map(this.ensureSegmentChunk);await Promise.all(Wr)}},qi=class{static findPosition(Gr,Wr){let Kr=Gr.getUint16(Wr+2)+2,Yr=typeof this.headerLength=="function"?this.headerLength(Gr,Wr,Kr):this.headerLength,Zr=Wr+Yr,tn=Kr-Yr;return{offset:Wr,length:Kr,headerLength:Yr,start:Zr,size:tn,end:Zr+tn}}static parse(Gr,Wr={}){return new this(Gr,new ia({[this.type]:Wr}),Gr).parse()}normalizeInput(Gr){return Gr instanceof Ki?Gr:new Ki(Gr)}constructor(Gr,Wr={},Kr){Kn(this,"errors",[]),Kn(this,"raw",new Map),Kn(this,"handleError",Yr=>{if(!this.options.silentErrors)throw Yr;this.errors.push(Yr.message)}),this.chunk=this.normalizeInput(Gr),this.file=Kr,this.type=this.constructor.type,this.globalOptions=this.options=Wr,this.localOptions=Wr[this.type],this.canTranslate=this.localOptions&&this.localOptions.translate}translate(){this.canTranslate&&(this.translated=this.translateBlock(this.raw,this.type))}get output(){return this.translated?this.translated:this.raw?Object.fromEntries(this.raw):void 0}translateBlock(Gr,Wr){let Kr=da.get(Wr),Yr=Wi.get(Wr),Zr=Si.get(Wr),tn=this.options[Wr],ln=tn.reviveValues&&!!Kr,dn=tn.translateValues&&!!Yr,yn=tn.translateKeys&&!!Zr,wn={};for(let[kn,An]of Gr)ln&&Kr.has(kn)?An=Kr.get(kn)(An):dn&&Yr.has(kn)&&(An=this.translateValue(An,Yr.get(kn))),yn&&Zr.has(kn)&&(kn=Zr.get(kn)||kn),wn[kn]=An;return wn}translateValue(Gr,Wr){return Wr[Gr]||Wr.DEFAULT||Gr}assignToOutput(Gr,Wr){this.assignObjectToOutput(Gr,this.constructor.type,Wr)}assignObjectToOutput(Gr,Wr,Kr){if(this.globalOptions.mergeOutput)return Object.assign(Gr,Kr);Gr[Wr]?Object.assign(Gr[Wr],Kr):Gr[Wr]=Kr}};Kn(qi,"headerLength",4),Kn(qi,"type",void 0),Kn(qi,"multiSegment",!1),Kn(qi,"canHandle",()=>!1);function Fu(Qr){return Qr===192||Qr===194||Qr===196||Qr===219||Qr===221||Qr===218||Qr===254}function qu(Qr){return Qr>=224&&Qr<=239}function Uu(Qr,Gr,Wr){for(let[Kr,Yr]of bi)if(Yr.canHandle(Qr,Gr,Wr))return Kr}var _o=class extends Ua{constructor(...Gr){super(...Gr),Kn(this,"appSegments",[]),Kn(this,"jpegSegments",[]),Kn(this,"unknownSegments",[])}static canHandle(Gr,Wr){return Wr===65496}async parse(){await this.findAppSegments(),await this.readSegments(this.appSegments),this.mergeMultiSegments(),this.createParsers(this.mergedAppSegments||this.appSegments)}setupSegmentFinderArgs(Gr){Gr===!0?(this.findAll=!0,this.wanted=new Set(bi.keyList())):(Gr=Gr===void 0?bi.keyList().filter(Wr=>this.options[Wr].enabled):Gr.filter(Wr=>this.options[Wr].enabled&&bi.has(Wr)),this.findAll=!1,this.remaining=new Set(Gr),this.wanted=new Set(Gr)),this.unfinishedMultiSegment=!1}async findAppSegments(Gr=0,Wr){this.setupSegmentFinderArgs(Wr);let{file:Kr,findAll:Yr,wanted:Zr,remaining:tn}=this;if(!Yr&&this.file.chunked&&(Yr=Array.from(Zr).some(ln=>{let dn=bi.get(ln),yn=this.options[ln];return dn.multiSegment&&yn.multiSegment}),Yr&&await this.file.readWhole()),Gr=this.findAppSegmentsInRange(Gr,Kr.byteLength),!this.options.onlyTiff&&Kr.chunked){let ln=!1;for(;tn.size>0&&!ln&&(Kr.canReadNextChunk||this.unfinishedMultiSegment);){let{nextChunkOffset:dn}=Kr,yn=this.appSegments.some(wn=>!this.file.available(wn.offset||wn.start,wn.length||wn.size));if(ln=Gr>dn&&!yn?!await Kr.readNextChunk(Gr):!await Kr.readNextChunk(dn),(Gr=this.findAppSegmentsInRange(Gr,Kr.byteLength))===void 0)return}}}findAppSegmentsInRange(Gr,Wr){Wr-=2;let Kr,Yr,Zr,tn,ln,dn,{file:yn,findAll:wn,wanted:kn,remaining:An,options:Gn}=this;for(;Gr<Wr;Gr++)if(yn.getUint8(Gr)===255){if(Kr=yn.getUint8(Gr+1),qu(Kr)){if(Yr=yn.getUint16(Gr+2),Zr=Uu(yn,Gr,Yr),Zr&&kn.has(Zr)&&(tn=bi.get(Zr),ln=tn.findPosition(yn,Gr),dn=Gn[Zr],ln.type=Zr,this.appSegments.push(ln),!wn&&(tn.multiSegment&&dn.multiSegment?(this.unfinishedMultiSegment=ln.chunkNumber<ln.chunkCount,this.unfinishedMultiSegment||An.delete(Zr)):An.delete(Zr),An.size===0)))break;Gn.recordUnknownSegments&&(ln=qi.findPosition(yn,Gr),ln.marker=Kr,this.unknownSegments.push(ln)),Gr+=Yr+1}else if(Fu(Kr)){if(Yr=yn.getUint16(Gr+2),Kr===218&&Gn.stopAfterSos!==!1)return;Gn.recordJpegSegments&&this.jpegSegments.push({offset:Gr,length:Yr,marker:Kr}),Gr+=Yr+1}}return Gr}mergeMultiSegments(){if(!this.appSegments.some(Wr=>Wr.multiSegment))return;let Gr=function(Wr,Kr){let Yr,Zr,tn,ln=new Map;for(let dn=0;dn<Wr.length;dn++)Yr=Wr[dn],Zr=Yr[Kr],ln.has(Zr)?tn=ln.get(Zr):ln.set(Zr,tn=[]),tn.push(Yr);return Array.from(ln)}(this.appSegments,"type");this.mergedAppSegments=Gr.map(([Wr,Kr])=>{let Yr=bi.get(Wr,this.options);return Yr.handleMultiSegments?{type:Wr,chunk:Yr.handleMultiSegments(Kr)}:Kr[0]})}getSegment(Gr){return this.appSegments.find(Wr=>Wr.type===Gr)}async getOrFindSegment(Gr){let Wr=this.getSegment(Gr);return Wr===void 0&&(await this.findAppSegments(0,[Gr]),Wr=this.getSegment(Gr)),Wr}};Kn(_o,"type","jpeg"),Yi.set("jpeg",_o);var Bu=[void 0,1,1,2,4,8,1,1,2,4,8,4,8,4],Ps=class extends qi{parseHeader(){var Gr=this.chunk.getUint16();Gr===18761?this.le=!0:Gr===19789&&(this.le=!1),this.chunk.le=this.le,this.headerParsed=!0}parseTags(Gr,Wr,Kr=new Map){let{pick:Yr,skip:Zr}=this.options[Wr];Yr=new Set(Yr);let tn=Yr.size>0,ln=Zr.size===0,dn=this.chunk.getUint16(Gr);Gr+=2;for(let yn=0;yn<dn;yn++){let wn=this.chunk.getUint16(Gr);if(tn){if(Yr.has(wn)&&(Kr.set(wn,this.parseTag(Gr,wn,Wr)),Yr.delete(wn),Yr.size===0))break}else!ln&&Zr.has(wn)||Kr.set(wn,this.parseTag(Gr,wn,Wr));Gr+=12}return Kr}parseTag(Gr,Wr,Kr){let{chunk:Yr}=this,Zr=Yr.getUint16(Gr+2),tn=Yr.getUint32(Gr+4),ln=Bu[Zr];if(ln*tn<=4?Gr+=8:Gr=Yr.getUint32(Gr+8),(Zr<1||Zr>13)&&Ei(`Invalid TIFF value type. block: ${Kr.toUpperCase()}, tag: ${Wr.toString(16)}, type: ${Zr}, offset ${Gr}`),Gr>Yr.byteLength&&Ei(`Invalid TIFF value offset. block: ${Kr.toUpperCase()}, tag: ${Wr.toString(16)}, type: ${Zr}, offset ${Gr} is outside of chunk size ${Yr.byteLength}`),Zr===1)return Yr.getUint8Array(Gr,tn);if(Zr===2)return ba(Yr.getString(Gr,tn));if(Zr===7)return Yr.getUint8Array(Gr,tn);if(tn===1)return this.parseTagValue(Zr,Gr);{let dn=new(function(wn){switch(wn){case 1:return Uint8Array;case 3:return Uint16Array;case 4:return Uint32Array;case 5:return Array;case 6:return Int8Array;case 8:return Int16Array;case 9:return Int32Array;case 10:return Array;case 11:return Float32Array;case 12:return Float64Array;default:return Array}}(Zr))(tn),yn=ln;for(let wn=0;wn<tn;wn++)dn[wn]=this.parseTagValue(Zr,Gr),Gr+=yn;return dn}}parseTagValue(Gr,Wr){let{chunk:Kr}=this;switch(Gr){case 1:return Kr.getUint8(Wr);case 3:return Kr.getUint16(Wr);case 4:return Kr.getUint32(Wr);case 5:return Kr.getUint32(Wr)/Kr.getUint32(Wr+4);case 6:return Kr.getInt8(Wr);case 8:return Kr.getInt16(Wr);case 9:return Kr.getInt32(Wr);case 10:return Kr.getInt32(Wr)/Kr.getInt32(Wr+4);case 11:return Kr.getFloat(Wr);case 12:return Kr.getDouble(Wr);case 13:return Kr.getUint32(Wr);default:Ei(`Invalid tiff type ${Gr}`)}}},no=class extends Ps{static canHandle(Gr,Wr){return Gr.getUint8(Wr+1)===225&&Gr.getUint32(Wr+4)===1165519206&&Gr.getUint16(Wr+8)===0}async parse(){this.parseHeader();let{options:Gr}=this;return Gr.ifd0.enabled&&await this.parseIfd0Block(),Gr.exif.enabled&&await this.safeParse("parseExifBlock"),Gr.gps.enabled&&await this.safeParse("parseGpsBlock"),Gr.interop.enabled&&await this.safeParse("parseInteropBlock"),Gr.ifd1.enabled&&await this.safeParse("parseThumbnailBlock"),this.createOutput()}safeParse(Gr){let Wr=this[Gr]();return Wr.catch!==void 0&&(Wr=Wr.catch(this.handleError)),Wr}findIfd0Offset(){this.ifd0Offset===void 0&&(this.ifd0Offset=this.chunk.getUint32(4))}findIfd1Offset(){if(this.ifd1Offset===void 0){this.findIfd0Offset();let Gr=this.chunk.getUint16(this.ifd0Offset),Wr=this.ifd0Offset+2+12*Gr;this.ifd1Offset=this.chunk.getUint32(Wr)}}parseBlock(Gr,Wr){let Kr=new Map;return this[Wr]=Kr,this.parseTags(Gr,Wr,Kr),Kr}async parseIfd0Block(){if(this.ifd0)return;let{file:Gr}=this;this.findIfd0Offset(),this.ifd0Offset<8&&Ei("Malformed EXIF data"),!Gr.chunked&&this.ifd0Offset>Gr.byteLength&&Ei(`IFD0 offset points to outside of file. 15 + this.ifd0Offset: ${this.ifd0Offset}, file.byteLength: ${Gr.byteLength}`),Gr.tiff&&await Gr.ensureChunk(this.ifd0Offset,ks(this.options));let Wr=this.parseBlock(this.ifd0Offset,"ifd0");return Wr.size!==0?(this.exifOffset=Wr.get(34665),this.interopOffset=Wr.get(40965),this.gpsOffset=Wr.get(34853),this.xmp=Wr.get(700),this.iptc=Wr.get(33723),this.icc=Wr.get(34675),this.options.sanitize&&(Wr.delete(34665),Wr.delete(40965),Wr.delete(34853),Wr.delete(700),Wr.delete(33723),Wr.delete(34675)),Wr):void 0}async parseExifBlock(){if(this.exif||(this.ifd0||await this.parseIfd0Block(),this.exifOffset===void 0))return;this.file.tiff&&await this.file.ensureChunk(this.exifOffset,ks(this.options));let Gr=this.parseBlock(this.exifOffset,"exif");return this.interopOffset||(this.interopOffset=Gr.get(40965)),this.makerNote=Gr.get(37500),this.userComment=Gr.get(37510),this.options.sanitize&&(Gr.delete(40965),Gr.delete(37500),Gr.delete(37510)),this.unpack(Gr,41728),this.unpack(Gr,41729),Gr}unpack(Gr,Wr){let Kr=Gr.get(Wr);Kr&&Kr.length===1&&Gr.set(Wr,Kr[0])}async parseGpsBlock(){if(this.gps||(this.ifd0||await this.parseIfd0Block(),this.gpsOffset===void 0))return;let Gr=this.parseBlock(this.gpsOffset,"gps");return Gr&&Gr.has(2)&&Gr.has(4)&&(Gr.set("latitude",Il(...Gr.get(2),Gr.get(1))),Gr.set("longitude",Il(...Gr.get(4),Gr.get(3)))),Gr}async parseInteropBlock(){if(!this.interop&&(this.ifd0||await this.parseIfd0Block(),this.interopOffset!==void 0||this.exif||await this.parseExifBlock(),this.interopOffset!==void 0))return this.parseBlock(this.interopOffset,"interop")}async parseThumbnailBlock(Gr=!1){if(!this.ifd1&&!this.ifd1Parsed&&(!this.options.mergeOutput||Gr))return this.findIfd1Offset(),this.ifd1Offset>0&&(this.parseBlock(this.ifd1Offset,"ifd1"),this.ifd1Parsed=!0),this.ifd1}async extractThumbnail(){if(this.headerParsed||this.parseHeader(),this.ifd1Parsed||await this.parseThumbnailBlock(!0),this.ifd1===void 0)return;let Gr=this.ifd1.get(513),Wr=this.ifd1.get(514);return this.chunk.getUint8Array(Gr,Wr)}get image(){return this.ifd0}get thumbnail(){return this.ifd1}createOutput(){let Gr,Wr,Kr,Yr={};for(Wr of di)if(Gr=this[Wr],!Fl(Gr))if(Kr=this.canTranslate?this.translateBlock(Gr,Wr):Object.fromEntries(Gr),this.options.mergeOutput){if(Wr==="ifd1")continue;Object.assign(Yr,Kr)}else Yr[Wr]=Kr;return this.makerNote&&(Yr.makerNote=this.makerNote),this.userComment&&(Yr.userComment=this.userComment),Yr}assignToOutput(Gr,Wr){if(this.globalOptions.mergeOutput)Object.assign(Gr,Wr);else for(let[Kr,Yr]of Object.entries(Wr))this.assignObjectToOutput(Gr,Kr,Yr)}};function Il(Qr,Gr,Wr,Kr){var Yr=Qr+Gr/60+Wr/3600;return Kr!=="S"&&Kr!=="W"||(Yr*=-1),Yr}Kn(no,"type","tiff"),Kn(no,"headerLength",10),bi.set("tiff",no);var Hu=Object.freeze({__proto__:null,default:Mu,Exifr:$i,fileParsers:Yi,segmentParsers:bi,fileReaders:Ji,tagKeys:Si,tagValues:Wi,tagRevivers:da,createDictionary:pi,extendDictionary:wa,fetchUrlAsArrayBuffer:xa,readBlobAsArrayBuffer:Sa,chunkedProps:ua,otherSegments:Ha,segments:ka,tiffBlocks:di,segmentsAndBlocks:ca,tiffExtractables:fa,inheritables:Ga,allFormatters:ha,Options:ia,parse:Zo}),qs={ifd0:!1,ifd1:!1,exif:!1,gps:!1,interop:!1,sanitize:!1,reviveValues:!0,translateKeys:!1,translateValues:!1,mergeOutput:!1},Us=Object.assign({},qs,{firstChunkSize:4e4,gps:[1,2,3,4]});async function Ul(Qr){let Gr=new $i(Us);await Gr.read(Qr);let Wr=await Gr.parse();if(Wr&&Wr.gps){let{latitude:Kr,longitude:Yr}=Wr.gps;return{latitude:Kr,longitude:Yr}}}var Bs=Object.assign({},qs,{tiff:!1,ifd1:!0,mergeOutput:!1});async function Bl(Qr){let Gr=new $i(Bs);await Gr.read(Qr);let Wr=await Gr.extractThumbnail();return Wr&&Jo?$o.from(Wr):Wr}async function Hl(Qr){let Gr=await this.thumbnail(Qr);if(Gr!==void 0){let Wr=new Blob([Gr]);return URL.createObjectURL(Wr)}}var Hs=Object.assign({},qs,{firstChunkSize:4e4,ifd0:[274]});async function Gs(Qr){let Gr=new $i(Hs);await Gr.read(Qr);let Wr=await Gr.parse();if(Wr&&Wr.ifd0)return Wr.ifd0[274]}var _s=Object.freeze({1:{dimensionSwapped:!1,scaleX:1,scaleY:1,deg:0,rad:0},2:{dimensionSwapped:!1,scaleX:-1,scaleY:1,deg:0,rad:0},3:{dimensionSwapped:!1,scaleX:1,scaleY:1,deg:180,rad:180*Math.PI/180},4:{dimensionSwapped:!1,scaleX:-1,scaleY:1,deg:180,rad:180*Math.PI/180},5:{dimensionSwapped:!0,scaleX:1,scaleY:-1,deg:90,rad:90*Math.PI/180},6:{dimensionSwapped:!0,scaleX:1,scaleY:1,deg:90,rad:90*Math.PI/180},7:{dimensionSwapped:!0,scaleX:1,scaleY:-1,deg:270,rad:270*Math.PI/180},8:{dimensionSwapped:!0,scaleX:1,scaleY:1,deg:270,rad:270*Math.PI/180}}),Ea=!0,Ta=!0;if(typeof navigator=="object"){let Qr=navigator.userAgent;if(Qr.includes("iPad")||Qr.includes("iPhone")){let Gr=Qr.match(/OS (\d+)_(\d+)/);if(Gr){let[,Wr,Kr]=Gr;Ea=Number(Wr)+.1*Number(Kr)<13.4,Ta=!1}}else if(Qr.includes("OS X 10")){let[,Gr]=Qr.match(/OS X 10[_.](\d+)/);Ea=Ta=Number(Gr)<15}if(Qr.includes("Chrome/")){let[,Gr]=Qr.match(/Chrome\/(\d+)/);Ea=Ta=Number(Gr)<81}else if(Qr.includes("Firefox/")){let[,Gr]=Qr.match(/Firefox\/(\d+)/);Ea=Ta=Number(Gr)<77}}async function Gl(Qr){let Gr=await Gs(Qr);return Object.assign({canvas:Ea,css:Ta},_s[Gr])}var Rs=class extends Ki{constructor(...Gr){super(...Gr),Kn(this,"ranges",new Ns),this.byteLength!==0&&this.ranges.add(0,this.byteLength)}_tryExtend(Gr,Wr,Kr){if(Gr===0&&this.byteLength===0&&Kr){let Yr=new DataView(Kr.buffer||Kr,Kr.byteOffset,Kr.byteLength);this._swapDataView(Yr)}else{let Yr=Gr+Wr;if(Yr>this.byteLength){let{dataView:Zr}=this._extend(Yr);this._swapDataView(Zr)}}}_extend(Gr){let Wr;Wr=Jo?$o.allocUnsafe(Gr):new Uint8Array(Gr);let Kr=new DataView(Wr.buffer,Wr.byteOffset,Wr.byteLength);return Wr.set(new Uint8Array(this.buffer,this.byteOffset,this.byteLength),0),{uintView:Wr,dataView:Kr}}subarray(Gr,Wr,Kr=!1){return Wr=Wr||this._lengthToEnd(Gr),Kr&&this._tryExtend(Gr,Wr),this.ranges.add(Gr,Wr),super.subarray(Gr,Wr)}set(Gr,Wr,Kr=!1){Kr&&this._tryExtend(Wr,Gr.byteLength,Gr);let Yr=super.set(Gr,Wr);return this.ranges.add(Wr,Yr.byteLength),Yr}async ensureChunk(Gr,Wr){this.chunked&&(this.ranges.available(Gr,Wr)||await this.readChunk(Gr,Wr))}available(Gr,Wr){return this.ranges.available(Gr,Wr)}},Ns=class{constructor(){Kn(this,"list",[])}get length(){return this.list.length}add(Gr,Wr,Kr=0){let Yr=Gr+Wr,Zr=this.list.filter(tn=>Ol(Gr,tn.offset,Yr)||Ol(Gr,tn.end,Yr));if(Zr.length>0){Gr=Math.min(Gr,...Zr.map(ln=>ln.offset)),Yr=Math.max(Yr,...Zr.map(ln=>ln.end)),Wr=Yr-Gr;let tn=Zr.shift();tn.offset=Gr,tn.length=Wr,tn.end=Yr,this.list=this.list.filter(ln=>!Zr.includes(ln))}else this.list.push({offset:Gr,length:Wr,end:Yr})}available(Gr,Wr){let Kr=Gr+Wr;return this.list.some(Yr=>Yr.offset<=Gr&&Kr<=Yr.end)}};function Ol(Qr,Gr,Wr){return Qr<=Gr&&Gr<=Wr}var Ba=class extends Rs{constructor(Gr,Wr){super(0),Kn(this,"chunksRead",0),this.input=Gr,this.options=Wr}async readWhole(){this.chunked=!1,await this.readChunk(this.nextChunkOffset)}async readChunked(){this.chunked=!0,await this.readChunk(0,this.options.firstChunkSize)}async readNextChunk(Gr=this.nextChunkOffset){if(this.fullyRead)return this.chunksRead++,!1;let Wr=this.options.chunkSize,Kr=await this.readChunk(Gr,Wr);return!!Kr&&Kr.byteLength===Wr}async readChunk(Gr,Wr){if(this.chunksRead++,(Wr=this.safeWrapAddress(Gr,Wr))!==0)return this._readChunk(Gr,Wr)}safeWrapAddress(Gr,Wr){return this.size!==void 0&&Gr+Wr>this.size?Math.max(0,this.size-Gr):Wr}get nextChunkOffset(){if(this.ranges.list.length!==0)return this.ranges.list[0].length}get canReadNextChunk(){return this.chunksRead<this.options.chunkLimit}get fullyRead(){return this.size!==void 0&&this.nextChunkOffset===this.size}read(){return this.options.chunked?this.readChunked():this.readWhole()}close(){}};Ji.set("blob",class extends Ba{async readWhole(){this.chunked=!1;let Qr=await Sa(this.input);this._swapArrayBuffer(Qr)}readChunked(){return this.chunked=!0,this.size=this.input.size,super.readChunked()}async _readChunk(Qr,Gr){let Wr=Gr?Qr+Gr:void 0,Kr=this.input.slice(Qr,Wr),Yr=await Sa(Kr);return this.set(Yr,Qr,!0)}});var Gu=Object.freeze({__proto__:null,default:Hu,Exifr:$i,fileParsers:Yi,segmentParsers:bi,fileReaders:Ji,tagKeys:Si,tagValues:Wi,tagRevivers:da,createDictionary:pi,extendDictionary:wa,fetchUrlAsArrayBuffer:xa,readBlobAsArrayBuffer:Sa,chunkedProps:ua,otherSegments:Ha,segments:ka,tiffBlocks:di,segmentsAndBlocks:ca,tiffExtractables:fa,inheritables:Ga,allFormatters:ha,Options:ia,parse:Zo,gpsOnlyOptions:Us,gps:Ul,thumbnailOnlyOptions:Bs,thumbnail:Bl,thumbnailUrl:Hl,orientationOnlyOptions:Hs,orientation:Gs,rotations:_s,get rotateCanvas(){return Ea},get rotateCss(){return Ta},rotation:Gl});Ji.set("url",class extends Ba{async readWhole(){this.chunked=!1;let Qr=await xa(this.input);Qr instanceof ArrayBuffer?this._swapArrayBuffer(Qr):Qr instanceof Uint8Array&&this._swapBuffer(Qr)}async _readChunk(Qr,Gr){let Wr=Gr?Qr+Gr-1:void 0,Kr=this.options.httpHeaders||{};(Qr||Wr)&&(Kr.range=`bytes=${[Qr,Wr].join("-")}`);let Yr=await Fs(this.input,{headers:Kr}),Zr=await Yr.arrayBuffer(),tn=Zr.byteLength;if(Yr.status!==416)return tn!==Gr&&(this.size=Qr+tn),this.set(Zr,Qr,!0)}});Ki.prototype.getUint64=function(Qr){let Gr=this.getUint32(Qr),Wr=this.getUint32(Qr+4);return Gr<1048575?Gr<<32|Wr:typeof Mo!==void 0?(console.warn("Using BigInt because of type 64uint but JS can only handle 53b numbers."),Mo(Gr)<<Mo(32)|Mo(Wr)):void Ei("Trying to read 64b value but JS can only handle 53b numbers.")};var Ls=class extends Ua{parseBoxes(Gr=0){let Wr=[];for(;Gr<this.file.byteLength-4;){let Kr=this.parseBoxHead(Gr);if(Wr.push(Kr),Kr.length===0)break;Gr+=Kr.length}return Wr}parseSubBoxes(Gr){Gr.boxes=this.parseBoxes(Gr.start)}findBox(Gr,Wr){return Gr.boxes===void 0&&this.parseSubBoxes(Gr),Gr.boxes.find(Kr=>Kr.kind===Wr)}parseBoxHead(Gr){let Wr=this.file.getUint32(Gr),Kr=this.file.getString(Gr+4,4),Yr=Gr+8;return Wr===1&&(Wr=this.file.getUint64(Gr+8),Yr+=8),{offset:Gr,length:Wr,kind:Kr,start:Yr}}parseBoxFullHead(Gr){if(Gr.version!==void 0)return;let Wr=this.file.getUint32(Gr.start);Gr.version=Wr>>24,Gr.start+=4}},jo=class extends Ls{static canHandle(Gr,Wr){if(Wr!==0)return!1;let Kr=Gr.getUint16(2);if(Kr>50)return!1;let Yr=16,Zr=[];for(;Yr<Kr;)Zr.push(Gr.getString(Yr,4)),Yr+=4;return Zr.includes(this.type)}async parse(){let Gr=this.file.getUint32(0),Wr=this.parseBoxHead(Gr);for(;Wr.kind!=="meta";)Gr+=Wr.length,await this.file.ensureChunk(Gr,16),Wr=this.parseBoxHead(Gr);await this.file.ensureChunk(Wr.offset,Wr.length),this.parseBoxFullHead(Wr),this.parseSubBoxes(Wr),this.options.icc.enabled&&await this.findIcc(Wr),this.options.tiff.enabled&&await this.findExif(Wr)}async registerSegment(Gr,Wr,Kr){await this.file.ensureChunk(Wr,Kr);let Yr=this.file.subarray(Wr,Kr);this.createParser(Gr,Yr)}async findIcc(Gr){let Wr=this.findBox(Gr,"iprp");if(Wr===void 0)return;let Kr=this.findBox(Wr,"ipco");if(Kr===void 0)return;let Yr=this.findBox(Kr,"colr");Yr!==void 0&&await this.registerSegment("icc",Yr.offset+12,Yr.length)}async findExif(Gr){let Wr=this.findBox(Gr,"iinf");if(Wr===void 0)return;let Kr=this.findBox(Gr,"iloc");if(Kr===void 0)return;let Yr=this.findExifLocIdInIinf(Wr),Zr=this.findExtentInIloc(Kr,Yr);if(Zr===void 0)return;let[tn,ln]=Zr;await this.file.ensureChunk(tn,ln);let dn=4+this.file.getUint32(tn);tn+=dn,ln-=dn,await this.registerSegment("tiff",tn,ln)}findExifLocIdInIinf(Gr){this.parseBoxFullHead(Gr);let Wr,Kr,Yr,Zr,tn=Gr.start,ln=this.file.getUint16(tn);for(tn+=2;ln--;){if(Wr=this.parseBoxHead(tn),this.parseBoxFullHead(Wr),Kr=Wr.start,Wr.version>=2&&(Yr=Wr.version===3?4:2,Zr=this.file.getString(Kr+Yr+2,4),Zr==="Exif"))return this.file.getUintBytes(Kr,Yr);tn+=Wr.length}}get8bits(Gr){let Wr=this.file.getUint8(Gr);return[Wr>>4,15&Wr]}findExtentInIloc(Gr,Wr){this.parseBoxFullHead(Gr);let Kr=Gr.start,[Yr,Zr]=this.get8bits(Kr++),[tn,ln]=this.get8bits(Kr++),dn=Gr.version===2?4:2,yn=Gr.version===1||Gr.version===2?2:0,wn=ln+Yr+Zr,kn=Gr.version===2?4:2,An=this.file.getUintBytes(Kr,kn);for(Kr+=kn;An--;){let Gn=this.file.getUintBytes(Kr,dn);Kr+=dn+yn+2+tn;let jn=this.file.getUint16(Kr);if(Kr+=2,Gn===Wr)return jn>1&&console.warn(`ILOC box has more than one extent but we're only processing one 16 + Please create an issue at https://github.com/MikeKovarik/exifr with this file`),[this.file.getUintBytes(Kr+ln,Yr),this.file.getUintBytes(Kr+ln+Yr,Zr)];Kr+=jn*wn}}},zo=class extends jo{};Kn(zo,"type","heic");var Vo=class extends jo{};Kn(Vo,"type","avif"),Yi.set("heic",zo),Yi.set("avif",Vo),pi(Si,["ifd0","ifd1"],[[256,"ImageWidth"],[257,"ImageHeight"],[258,"BitsPerSample"],[259,"Compression"],[262,"PhotometricInterpretation"],[270,"ImageDescription"],[271,"Make"],[272,"Model"],[273,"StripOffsets"],[274,"Orientation"],[277,"SamplesPerPixel"],[278,"RowsPerStrip"],[279,"StripByteCounts"],[282,"XResolution"],[283,"YResolution"],[284,"PlanarConfiguration"],[296,"ResolutionUnit"],[301,"TransferFunction"],[305,"Software"],[306,"ModifyDate"],[315,"Artist"],[316,"HostComputer"],[317,"Predictor"],[318,"WhitePoint"],[319,"PrimaryChromaticities"],[513,"ThumbnailOffset"],[514,"ThumbnailLength"],[529,"YCbCrCoefficients"],[530,"YCbCrSubSampling"],[531,"YCbCrPositioning"],[532,"ReferenceBlackWhite"],[700,"ApplicationNotes"],[33432,"Copyright"],[33723,"IPTC"],[34665,"ExifIFD"],[34675,"ICC"],[34853,"GpsIFD"],[330,"SubIFD"],[40965,"InteropIFD"],[40091,"XPTitle"],[40092,"XPComment"],[40093,"XPAuthor"],[40094,"XPKeywords"],[40095,"XPSubject"]]),pi(Si,"exif",[[33434,"ExposureTime"],[33437,"FNumber"],[34850,"ExposureProgram"],[34852,"SpectralSensitivity"],[34855,"ISO"],[34858,"TimeZoneOffset"],[34859,"SelfTimerMode"],[34864,"SensitivityType"],[34865,"StandardOutputSensitivity"],[34866,"RecommendedExposureIndex"],[34867,"ISOSpeed"],[34868,"ISOSpeedLatitudeyyy"],[34869,"ISOSpeedLatitudezzz"],[36864,"ExifVersion"],[36867,"DateTimeOriginal"],[36868,"CreateDate"],[36873,"GooglePlusUploadCode"],[36880,"OffsetTime"],[36881,"OffsetTimeOriginal"],[36882,"OffsetTimeDigitized"],[37121,"ComponentsConfiguration"],[37122,"CompressedBitsPerPixel"],[37377,"ShutterSpeedValue"],[37378,"ApertureValue"],[37379,"BrightnessValue"],[37380,"ExposureCompensation"],[37381,"MaxApertureValue"],[37382,"SubjectDistance"],[37383,"MeteringMode"],[37384,"LightSource"],[37385,"Flash"],[37386,"FocalLength"],[37393,"ImageNumber"],[37394,"SecurityClassification"],[37395,"ImageHistory"],[37396,"SubjectArea"],[37500,"MakerNote"],[37510,"UserComment"],[37520,"SubSecTime"],[37521,"SubSecTimeOriginal"],[37522,"SubSecTimeDigitized"],[37888,"AmbientTemperature"],[37889,"Humidity"],[37890,"Pressure"],[37891,"WaterDepth"],[37892,"Acceleration"],[37893,"CameraElevationAngle"],[40960,"FlashpixVersion"],[40961,"ColorSpace"],[40962,"ExifImageWidth"],[40963,"ExifImageHeight"],[40964,"RelatedSoundFile"],[41483,"FlashEnergy"],[41486,"FocalPlaneXResolution"],[41487,"FocalPlaneYResolution"],[41488,"FocalPlaneResolutionUnit"],[41492,"SubjectLocation"],[41493,"ExposureIndex"],[41495,"SensingMethod"],[41728,"FileSource"],[41729,"SceneType"],[41730,"CFAPattern"],[41985,"CustomRendered"],[41986,"ExposureMode"],[41987,"WhiteBalance"],[41988,"DigitalZoomRatio"],[41989,"FocalLengthIn35mmFormat"],[41990,"SceneCaptureType"],[41991,"GainControl"],[41992,"Contrast"],[41993,"Saturation"],[41994,"Sharpness"],[41996,"SubjectDistanceRange"],[42016,"ImageUniqueID"],[42032,"OwnerName"],[42033,"SerialNumber"],[42034,"LensInfo"],[42035,"LensMake"],[42036,"LensModel"],[42037,"LensSerialNumber"],[42080,"CompositeImage"],[42081,"CompositeImageCount"],[42082,"CompositeImageExposureTimes"],[42240,"Gamma"],[59932,"Padding"],[59933,"OffsetSchema"],[65e3,"OwnerName"],[65001,"SerialNumber"],[65002,"Lens"],[65100,"RawFile"],[65101,"Converter"],[65102,"WhiteBalance"],[65105,"Exposure"],[65106,"Shadows"],[65107,"Brightness"],[65108,"Contrast"],[65109,"Saturation"],[65110,"Sharpness"],[65111,"Smoothness"],[65112,"MoireFilter"],[40965,"InteropIFD"]]),pi(Si,"gps",[[0,"GPSVersionID"],[1,"GPSLatitudeRef"],[2,"GPSLatitude"],[3,"GPSLongitudeRef"],[4,"GPSLongitude"],[5,"GPSAltitudeRef"],[6,"GPSAltitude"],[7,"GPSTimeStamp"],[8,"GPSSatellites"],[9,"GPSStatus"],[10,"GPSMeasureMode"],[11,"GPSDOP"],[12,"GPSSpeedRef"],[13,"GPSSpeed"],[14,"GPSTrackRef"],[15,"GPSTrack"],[16,"GPSImgDirectionRef"],[17,"GPSImgDirection"],[18,"GPSMapDatum"],[19,"GPSDestLatitudeRef"],[20,"GPSDestLatitude"],[21,"GPSDestLongitudeRef"],[22,"GPSDestLongitude"],[23,"GPSDestBearingRef"],[24,"GPSDestBearing"],[25,"GPSDestDistanceRef"],[26,"GPSDestDistance"],[27,"GPSProcessingMethod"],[28,"GPSAreaInformation"],[29,"GPSDateStamp"],[30,"GPSDifferential"],[31,"GPSHPositioningError"]]),pi(Wi,["ifd0","ifd1"],[[274,{1:"Horizontal (normal)",2:"Mirror horizontal",3:"Rotate 180",4:"Mirror vertical",5:"Mirror horizontal and rotate 270 CW",6:"Rotate 90 CW",7:"Mirror horizontal and rotate 90 CW",8:"Rotate 270 CW"}],[296,{1:"None",2:"inches",3:"cm"}]]);var io=pi(Wi,"exif",[[34850,{0:"Not defined",1:"Manual",2:"Normal program",3:"Aperture priority",4:"Shutter priority",5:"Creative program",6:"Action program",7:"Portrait mode",8:"Landscape mode"}],[37121,{0:"-",1:"Y",2:"Cb",3:"Cr",4:"R",5:"G",6:"B"}],[37383,{0:"Unknown",1:"Average",2:"CenterWeightedAverage",3:"Spot",4:"MultiSpot",5:"Pattern",6:"Partial",255:"Other"}],[37384,{0:"Unknown",1:"Daylight",2:"Fluorescent",3:"Tungsten (incandescent light)",4:"Flash",9:"Fine weather",10:"Cloudy weather",11:"Shade",12:"Daylight fluorescent (D 5700 - 7100K)",13:"Day white fluorescent (N 4600 - 5400K)",14:"Cool white fluorescent (W 3900 - 4500K)",15:"White fluorescent (WW 3200 - 3700K)",17:"Standard light A",18:"Standard light B",19:"Standard light C",20:"D55",21:"D65",22:"D75",23:"D50",24:"ISO studio tungsten",255:"Other"}],[37385,{0:"Flash did not fire",1:"Flash fired",5:"Strobe return light not detected",7:"Strobe return light detected",9:"Flash fired, compulsory flash mode",13:"Flash fired, compulsory flash mode, return light not detected",15:"Flash fired, compulsory flash mode, return light detected",16:"Flash did not fire, compulsory flash mode",24:"Flash did not fire, auto mode",25:"Flash fired, auto mode",29:"Flash fired, auto mode, return light not detected",31:"Flash fired, auto mode, return light detected",32:"No flash function",65:"Flash fired, red-eye reduction mode",69:"Flash fired, red-eye reduction mode, return light not detected",71:"Flash fired, red-eye reduction mode, return light detected",73:"Flash fired, compulsory flash mode, red-eye reduction mode",77:"Flash fired, compulsory flash mode, red-eye reduction mode, return light not detected",79:"Flash fired, compulsory flash mode, red-eye reduction mode, return light detected",89:"Flash fired, auto mode, red-eye reduction mode",93:"Flash fired, auto mode, return light not detected, red-eye reduction mode",95:"Flash fired, auto mode, return light detected, red-eye reduction mode"}],[41495,{1:"Not defined",2:"One-chip color area sensor",3:"Two-chip color area sensor",4:"Three-chip color area sensor",5:"Color sequential area sensor",7:"Trilinear sensor",8:"Color sequential linear sensor"}],[41728,{1:"Film Scanner",2:"Reflection Print Scanner",3:"Digital Camera"}],[41729,{1:"Directly photographed"}],[41985,{0:"Normal",1:"Custom",2:"HDR (no original saved)",3:"HDR (original saved)",4:"Original (for HDR)",6:"Panorama",7:"Portrait HDR",8:"Portrait"}],[41986,{0:"Auto",1:"Manual",2:"Auto bracket"}],[41987,{0:"Auto",1:"Manual"}],[41990,{0:"Standard",1:"Landscape",2:"Portrait",3:"Night",4:"Other"}],[41991,{0:"None",1:"Low gain up",2:"High gain up",3:"Low gain down",4:"High gain down"}],[41996,{0:"Unknown",1:"Macro",2:"Close",3:"Distant"}],[42080,{0:"Unknown",1:"Not a Composite Image",2:"General Composite Image",3:"Composite Image Captured While Shooting"}]]),Al={1:"No absolute unit of measurement",2:"Inch",3:"Centimeter"};io.set(37392,Al),io.set(41488,Al);var Ss={0:"Normal",1:"Low",2:"High"};function Dl(Qr){return typeof Qr=="object"&&Qr.length!==void 0?Qr[0]:Qr}function Pl(Qr){let Gr=Array.from(Qr).slice(1);return Gr[1]>15&&(Gr=Gr.map(Wr=>String.fromCharCode(Wr))),Gr[2]!=="0"&&Gr[2]!==0||Gr.pop(),Gr.join(".")}function ws(Qr){if(typeof Qr=="string"){var[Gr,Wr,Kr,Yr,Zr,tn]=Qr.trim().split(/[-: ]/g).map(Number),ln=new Date(Gr,Wr-1,Kr);return Number.isNaN(Yr)||Number.isNaN(Zr)||Number.isNaN(tn)||(ln.setHours(Yr),ln.setMinutes(Zr),ln.setSeconds(tn)),Number.isNaN(+ln)?Qr:ln}}function ro(Qr){if(typeof Qr=="string")return Qr;let Gr=[];if(Qr[1]===0&&Qr[Qr.length-1]===0)for(let Wr=0;Wr<Qr.length;Wr+=2)Gr.push(Rl(Qr[Wr+1],Qr[Wr]));else for(let Wr=0;Wr<Qr.length;Wr+=2)Gr.push(Rl(Qr[Wr],Qr[Wr+1]));return ba(String.fromCodePoint(...Gr))}function Rl(Qr,Gr){return Qr<<8|Gr}io.set(41992,Ss),io.set(41993,Ss),io.set(41994,Ss),pi(da,["ifd0","ifd1"],[[50827,function(Qr){return typeof Qr!="string"?ql(Qr):Qr}],[306,ws],[40091,ro],[40092,ro],[40093,ro],[40094,ro],[40095,ro]]),pi(da,"exif",[[40960,Pl],[36864,Pl],[36867,ws],[36868,ws],[40962,Dl],[40963,Dl]]),pi(da,"gps",[[0,Qr=>Array.from(Qr).join(".")],[7,Qr=>Array.from(Qr).join(":")]]);var ao=class extends qi{static canHandle(Gr,Wr){return Gr.getUint8(Wr+1)===225&&Gr.getUint32(Wr+4)===1752462448&&Gr.getString(Wr+4,20)==="http://ns.adobe.com/"}static headerLength(Gr,Wr){return Gr.getString(Wr+4,34)==="http://ns.adobe.com/xmp/extension/"?79:33}static findPosition(Gr,Wr){let Kr=super.findPosition(Gr,Wr);return Kr.multiSegment=Kr.extended=Kr.headerLength===79,Kr.multiSegment?(Kr.chunkCount=Gr.getUint8(Wr+72),Kr.chunkNumber=Gr.getUint8(Wr+76),Gr.getUint8(Wr+77)!==0&&Kr.chunkNumber++):(Kr.chunkCount=1/0,Kr.chunkNumber=-1),Kr}static handleMultiSegments(Gr){return Gr.map(Wr=>Wr.chunk.getString()).join("")}normalizeInput(Gr){return typeof Gr=="string"?Gr:Ki.from(Gr).getString()}parse(Gr=this.chunk){if(!this.localOptions.parse)return Gr;Gr=function(Zr){let tn={},ln={};for(let dn of Vl)tn[dn]=[],ln[dn]=0;return Zr.replace(Vu,(dn,yn,wn)=>{if(yn==="<"){let kn=++ln[wn];return tn[wn].push(kn),`${dn}#${kn}`}return`${dn}#${tn[wn].pop()}`})}(Gr);let Wr=Wo.findAll(Gr,"rdf","Description");Wr.length===0&&Wr.push(new Wo("rdf","Description",void 0,Gr));let Kr,Yr={};for(let Zr of Wr)for(let tn of Zr.properties)Kr=zu(tn.ns,Yr),_l(tn,Kr);return function(Zr){let tn;for(let ln in Zr)tn=Zr[ln]=Bo(Zr[ln]),tn===void 0&&delete Zr[ln];return Bo(Zr)}(Yr)}assignToOutput(Gr,Wr){if(this.localOptions.parse)for(let[Kr,Yr]of Object.entries(Wr))switch(Kr){case"tiff":this.assignObjectToOutput(Gr,"ifd0",Yr);break;case"exif":this.assignObjectToOutput(Gr,"exif",Yr);break;case"xmlns":break;default:this.assignObjectToOutput(Gr,Kr,Yr)}else Gr.xmp=Wr}};Kn(ao,"type","xmp"),Kn(ao,"multiSegment",!0),bi.set("xmp",ao);var Ms=class Qr{static findAll(Gr){return jl(Gr,/([a-zA-Z0-9-]+):([a-zA-Z0-9-]+)=("[^"]*"|'[^']*')/gm).map(Qr.unpackMatch)}static unpackMatch(Gr){let Wr=Gr[1],Kr=Gr[2],Yr=Gr[3].slice(1,-1);return Yr=zl(Yr),new Qr(Wr,Kr,Yr)}constructor(Gr,Wr,Kr){this.ns=Gr,this.name=Wr,this.value=Kr}serialize(){return this.value}},Wo=class Qr{static findAll(Gr,Wr,Kr){if(Wr!==void 0||Kr!==void 0){Wr=Wr||"[\\w\\d-]+",Kr=Kr||"[\\w\\d-]+";var Yr=new RegExp(`<(${Wr}):(${Kr})(#\\d+)?((\\s+?[\\w\\d-:]+=("[^"]*"|'[^']*'))*\\s*)(\\/>|>([\\s\\S]*?)<\\/\\1:\\2\\3>)`,"gm")}else Yr=/<([\w\d-]+):([\w\d-]+)(#\d+)?((\s+?[\w\d-:]+=("[^"]*"|'[^']*'))*\s*)(\/>|>([\s\S]*?)<\/\1:\2\3>)/gm;return jl(Gr,Yr).map(Qr.unpackMatch)}static unpackMatch(Gr){let Wr=Gr[1],Kr=Gr[2],Yr=Gr[4],Zr=Gr[8];return new Qr(Wr,Kr,Yr,Zr)}constructor(Gr,Wr,Kr,Yr){this.ns=Gr,this.name=Wr,this.attrString=Kr,this.innerXml=Yr,this.attrs=Ms.findAll(Kr),this.children=Qr.findAll(Yr),this.value=this.children.length===0?zl(Yr):void 0,this.properties=[...this.attrs,...this.children]}get isPrimitive(){return this.value!==void 0&&this.attrs.length===0&&this.children.length===0}get isListContainer(){return this.children.length===1&&this.children[0].isList}get isList(){let{ns:Gr,name:Wr}=this;return Gr==="rdf"&&(Wr==="Seq"||Wr==="Bag"||Wr==="Alt")}get isListItem(){return this.ns==="rdf"&&this.name==="li"}serialize(){if(this.properties.length===0&&this.value===void 0)return;if(this.isPrimitive)return this.value;if(this.isListContainer)return this.children[0].serialize();if(this.isList)return ju(this.children.map(_u));if(this.isListItem&&this.children.length===1&&this.attrs.length===0)return this.children[0].serialize();let Gr={};for(let Wr of this.properties)_l(Wr,Gr);return this.value!==void 0&&(Gr.value=this.value),Bo(Gr)}};function _l(Qr,Gr){let Wr=Qr.serialize();Wr!==void 0&&(Gr[Qr.name]=Wr)}var _u=Qr=>Qr.serialize(),ju=Qr=>Qr.length===1?Qr[0]:Qr,zu=(Qr,Gr)=>Gr[Qr]?Gr[Qr]:Gr[Qr]={};function jl(Qr,Gr){let Wr,Kr=[];if(!Qr)return Kr;for(;(Wr=Gr.exec(Qr))!==null;)Kr.push(Wr);return Kr}function zl(Qr){if(function(Kr){return Kr==null||Kr==="null"||Kr==="undefined"||Kr===""||Kr.trim()===""}(Qr))return;let Gr=Number(Qr);if(!Number.isNaN(Gr))return Gr;let Wr=Qr.toLowerCase();return Wr==="true"||Wr!=="false"&&Qr.trim()}var Vl=["rdf:li","rdf:Seq","rdf:Bag","rdf:Alt","rdf:Description"],Vu=new RegExp(`(<|\\/)(${Vl.join("|")})`,"g"),Wu=Object.freeze({__proto__:null,default:Gu,Exifr:$i,fileParsers:Yi,segmentParsers:bi,fileReaders:Ji,tagKeys:Si,tagValues:Wi,tagRevivers:da,createDictionary:pi,extendDictionary:wa,fetchUrlAsArrayBuffer:xa,readBlobAsArrayBuffer:Sa,chunkedProps:ua,otherSegments:Ha,segments:ka,tiffBlocks:di,segmentsAndBlocks:ca,tiffExtractables:fa,inheritables:Ga,allFormatters:ha,Options:ia,parse:Zo,gpsOnlyOptions:Us,gps:Ul,thumbnailOnlyOptions:Bs,thumbnail:Bl,thumbnailUrl:Hl,orientationOnlyOptions:Hs,orientation:Gs,rotations:_s,get rotateCanvas(){return Ea},get rotateCss(){return Ta},rotation:Gl});var Nl=Uo("fs",Qr=>Qr.promises);Ji.set("fs",class extends Ba{async readWhole(){this.chunked=!1,this.fs=await Nl;let Qr=await this.fs.readFile(this.input);this._swapBuffer(Qr)}async readChunked(){this.chunked=!0,this.fs=await Nl,await this.open(),await this.readChunk(0,this.options.firstChunkSize)}async open(){this.fh===void 0&&(this.fh=await this.fs.open(this.input,"r"),this.size=(await this.fh.stat(this.input)).size)}async _readChunk(Qr,Gr){this.fh===void 0&&await this.open(),Qr+Gr>this.size&&(Gr=this.size-Qr);var Wr=this.subarray(Qr,Gr,!0);return await this.fh.read(Wr.dataView,0,Gr,Qr),Wr}async close(){if(this.fh){let Qr=this.fh;this.fh=void 0,await Qr.close()}}});Ji.set("base64",class extends Ba{constructor(...Qr){super(...Qr),this.input=this.input.replace(/^data:([^;]+);base64,/gim,""),this.size=this.input.length/4*3,this.input.endsWith("==")?this.size-=2:this.input.endsWith("=")&&(this.size-=1)}async _readChunk(Qr,Gr){let Wr,Kr,Yr=this.input;Qr===void 0?(Qr=0,Wr=0,Kr=0):(Wr=4*Math.floor(Qr/3),Kr=Qr-Wr/4*3),Gr===void 0&&(Gr=this.size);let Zr=Qr+Gr,tn=Wr+4*Math.ceil(Zr/3);Yr=Yr.slice(Wr,tn);let ln=Math.min(Gr,this.size-Qr);if(Jo){let dn=$o.from(Yr,"base64").slice(Kr,Kr+ln);return this.set(dn,Qr,!0)}{let dn=this.subarray(Qr,ln,!0),yn=atob(Yr),wn=dn.toUint8();for(let kn=0;kn<ln;kn++)wn[kn]=yn.charCodeAt(Kr+kn);return dn}}});var Xo=class extends Ua{static canHandle(Gr,Wr){return Wr===18761||Wr===19789}extendOptions(Gr){let{ifd0:Wr,xmp:Kr,iptc:Yr,icc:Zr}=Gr;Kr.enabled&&Wr.deps.add(700),Yr.enabled&&Wr.deps.add(33723),Zr.enabled&&Wr.deps.add(34675),Wr.finalizeFilters()}async parse(){let{tiff:Gr,xmp:Wr,iptc:Kr,icc:Yr}=this.options;if(Gr.enabled||Wr.enabled||Kr.enabled||Yr.enabled){let Zr=Math.max(ks(this.options),this.options.chunkSize);await this.file.ensureChunk(0,Zr),this.createParser("tiff",this.file),this.parsers.tiff.parseHeader(),await this.parsers.tiff.parseIfd0Block(),this.adaptTiffPropAsSegment("xmp"),this.adaptTiffPropAsSegment("iptc"),this.adaptTiffPropAsSegment("icc")}}adaptTiffPropAsSegment(Gr){if(this.parsers.tiff[Gr]){let Wr=this.parsers.tiff[Gr];this.injectSegment(Gr,Wr)}}};Kn(Xo,"type","tiff"),Yi.set("tiff",Xo);var Xu=Uo("zlib"),Qu=["ihdr","iccp","text","itxt","exif"],Qo=class extends Ua{constructor(...Gr){super(...Gr),Kn(this,"catchError",Wr=>this.errors.push(Wr)),Kn(this,"metaChunks",[]),Kn(this,"unknownChunks",[])}static canHandle(Gr,Wr){return Wr===35152&&Gr.getUint32(0)===2303741511&&Gr.getUint32(4)===218765834}async parse(){let{file:Gr}=this;await this.findPngChunksInRange(8,Gr.byteLength),await this.readSegments(this.metaChunks),this.findIhdr(),this.parseTextChunks(),await this.findExif().catch(this.catchError),await this.findXmp().catch(this.catchError),await this.findIcc().catch(this.catchError)}async findPngChunksInRange(Gr,Wr){let{file:Kr}=this;for(;Gr<Wr;){let Yr=Kr.getUint32(Gr),Zr=Kr.getUint32(Gr+4),tn=Kr.getString(Gr+4,4).toLowerCase(),ln=Yr+4+4+4,dn={type:tn,offset:Gr,length:ln,start:Gr+4+4,size:Yr,marker:Zr};Qu.includes(tn)?this.metaChunks.push(dn):this.unknownChunks.push(dn),Gr+=ln}}parseTextChunks(){let Gr=this.metaChunks.filter(Wr=>Wr.type==="text");for(let Wr of Gr){let[Kr,Yr]=this.file.getString(Wr.start,Wr.size).split("\0");this.injectKeyValToIhdr(Kr,Yr)}}injectKeyValToIhdr(Gr,Wr){let Kr=this.parsers.ihdr;Kr&&Kr.raw.set(Gr,Wr)}findIhdr(){let Gr=this.metaChunks.find(Wr=>Wr.type==="ihdr");Gr&&this.options.ihdr.enabled!==!1&&this.createParser("ihdr",Gr.chunk)}async findExif(){let Gr=this.metaChunks.find(Wr=>Wr.type==="exif");Gr&&this.injectSegment("tiff",Gr.chunk)}async findXmp(){let Gr=this.metaChunks.filter(Wr=>Wr.type==="itxt");for(let Wr of Gr)Wr.chunk.getString(0,17)==="XML:com.adobe.xmp"&&this.injectSegment("xmp",Wr.chunk)}async findIcc(){let Gr=this.metaChunks.find(ln=>ln.type==="iccp");if(!Gr)return;let{chunk:Wr}=Gr,Kr=Wr.getUint8Array(0,81),Yr=0;for(;Yr<80&&Kr[Yr]!==0;)Yr++;let Zr=Yr+2,tn=Wr.getString(0,Yr);if(this.injectKeyValToIhdr("ProfileName",tn),qo){let ln=await Xu,dn=Wr.getUint8Array(Zr);dn=ln.inflateSync(dn),this.injectSegment("icc",dn)}}};Kn(Qo,"type","png"),Yi.set("png",Qo),pi(Si,"interop",[[1,"InteropIndex"],[2,"InteropVersion"],[4096,"RelatedImageFileFormat"],[4097,"RelatedImageWidth"],[4098,"RelatedImageHeight"]]),wa(Si,"ifd0",[[11,"ProcessingSoftware"],[254,"SubfileType"],[255,"OldSubfileType"],[263,"Thresholding"],[264,"CellWidth"],[265,"CellLength"],[266,"FillOrder"],[269,"DocumentName"],[280,"MinSampleValue"],[281,"MaxSampleValue"],[285,"PageName"],[286,"XPosition"],[287,"YPosition"],[290,"GrayResponseUnit"],[297,"PageNumber"],[321,"HalftoneHints"],[322,"TileWidth"],[323,"TileLength"],[332,"InkSet"],[337,"TargetPrinter"],[18246,"Rating"],[18249,"RatingPercent"],[33550,"PixelScale"],[34264,"ModelTransform"],[34377,"PhotoshopSettings"],[50706,"DNGVersion"],[50707,"DNGBackwardVersion"],[50708,"UniqueCameraModel"],[50709,"LocalizedCameraModel"],[50736,"DNGLensInfo"],[50739,"ShadowScale"],[50740,"DNGPrivateData"],[33920,"IntergraphMatrix"],[33922,"ModelTiePoint"],[34118,"SEMInfo"],[34735,"GeoTiffDirectory"],[34736,"GeoTiffDoubleParams"],[34737,"GeoTiffAsciiParams"],[50341,"PrintIM"],[50721,"ColorMatrix1"],[50722,"ColorMatrix2"],[50723,"CameraCalibration1"],[50724,"CameraCalibration2"],[50725,"ReductionMatrix1"],[50726,"ReductionMatrix2"],[50727,"AnalogBalance"],[50728,"AsShotNeutral"],[50729,"AsShotWhiteXY"],[50730,"BaselineExposure"],[50731,"BaselineNoise"],[50732,"BaselineSharpness"],[50734,"LinearResponseLimit"],[50735,"CameraSerialNumber"],[50741,"MakerNoteSafety"],[50778,"CalibrationIlluminant1"],[50779,"CalibrationIlluminant2"],[50781,"RawDataUniqueID"],[50827,"OriginalRawFileName"],[50828,"OriginalRawFileData"],[50831,"AsShotICCProfile"],[50832,"AsShotPreProfileMatrix"],[50833,"CurrentICCProfile"],[50834,"CurrentPreProfileMatrix"],[50879,"ColorimetricReference"],[50885,"SRawType"],[50898,"PanasonicTitle"],[50899,"PanasonicTitle2"],[50931,"CameraCalibrationSig"],[50932,"ProfileCalibrationSig"],[50933,"ProfileIFD"],[50934,"AsShotProfileName"],[50936,"ProfileName"],[50937,"ProfileHueSatMapDims"],[50938,"ProfileHueSatMapData1"],[50939,"ProfileHueSatMapData2"],[50940,"ProfileToneCurve"],[50941,"ProfileEmbedPolicy"],[50942,"ProfileCopyright"],[50964,"ForwardMatrix1"],[50965,"ForwardMatrix2"],[50966,"PreviewApplicationName"],[50967,"PreviewApplicationVersion"],[50968,"PreviewSettingsName"],[50969,"PreviewSettingsDigest"],[50970,"PreviewColorSpace"],[50971,"PreviewDateTime"],[50972,"RawImageDigest"],[50973,"OriginalRawFileDigest"],[50981,"ProfileLookTableDims"],[50982,"ProfileLookTableData"],[51043,"TimeCodes"],[51044,"FrameRate"],[51058,"TStop"],[51081,"ReelName"],[51089,"OriginalDefaultFinalSize"],[51090,"OriginalBestQualitySize"],[51091,"OriginalDefaultCropSize"],[51105,"CameraLabel"],[51107,"ProfileHueSatMapEncoding"],[51108,"ProfileLookTableEncoding"],[51109,"BaselineExposureOffset"],[51110,"DefaultBlackRender"],[51111,"NewRawImageDigest"],[51112,"RawToPreviewGain"]]);var Ll=[[273,"StripOffsets"],[279,"StripByteCounts"],[288,"FreeOffsets"],[289,"FreeByteCounts"],[291,"GrayResponseCurve"],[292,"T4Options"],[293,"T6Options"],[300,"ColorResponseUnit"],[320,"ColorMap"],[324,"TileOffsets"],[325,"TileByteCounts"],[326,"BadFaxLines"],[327,"CleanFaxData"],[328,"ConsecutiveBadFaxLines"],[330,"SubIFD"],[333,"InkNames"],[334,"NumberofInks"],[336,"DotRange"],[338,"ExtraSamples"],[339,"SampleFormat"],[340,"SMinSampleValue"],[341,"SMaxSampleValue"],[342,"TransferRange"],[343,"ClipPath"],[344,"XClipPathUnits"],[345,"YClipPathUnits"],[346,"Indexed"],[347,"JPEGTables"],[351,"OPIProxy"],[400,"GlobalParametersIFD"],[401,"ProfileType"],[402,"FaxProfile"],[403,"CodingMethods"],[404,"VersionYear"],[405,"ModeNumber"],[433,"Decode"],[434,"DefaultImageColor"],[435,"T82Options"],[437,"JPEGTables"],[512,"JPEGProc"],[515,"JPEGRestartInterval"],[517,"JPEGLosslessPredictors"],[518,"JPEGPointTransforms"],[519,"JPEGQTables"],[520,"JPEGDCTables"],[521,"JPEGACTables"],[559,"StripRowCounts"],[999,"USPTOMiscellaneous"],[18247,"XP_DIP_XML"],[18248,"StitchInfo"],[28672,"SonyRawFileType"],[28688,"SonyToneCurve"],[28721,"VignettingCorrection"],[28722,"VignettingCorrParams"],[28724,"ChromaticAberrationCorrection"],[28725,"ChromaticAberrationCorrParams"],[28726,"DistortionCorrection"],[28727,"DistortionCorrParams"],[29895,"SonyCropTopLeft"],[29896,"SonyCropSize"],[32781,"ImageID"],[32931,"WangTag1"],[32932,"WangAnnotation"],[32933,"WangTag3"],[32934,"WangTag4"],[32953,"ImageReferencePoints"],[32954,"RegionXformTackPoint"],[32955,"WarpQuadrilateral"],[32956,"AffineTransformMat"],[32995,"Matteing"],[32996,"DataType"],[32997,"ImageDepth"],[32998,"TileDepth"],[33300,"ImageFullWidth"],[33301,"ImageFullHeight"],[33302,"TextureFormat"],[33303,"WrapModes"],[33304,"FovCot"],[33305,"MatrixWorldToScreen"],[33306,"MatrixWorldToCamera"],[33405,"Model2"],[33421,"CFARepeatPatternDim"],[33422,"CFAPattern2"],[33423,"BatteryLevel"],[33424,"KodakIFD"],[33445,"MDFileTag"],[33446,"MDScalePixel"],[33447,"MDColorTable"],[33448,"MDLabName"],[33449,"MDSampleInfo"],[33450,"MDPrepDate"],[33451,"MDPrepTime"],[33452,"MDFileUnits"],[33589,"AdventScale"],[33590,"AdventRevision"],[33628,"UIC1Tag"],[33629,"UIC2Tag"],[33630,"UIC3Tag"],[33631,"UIC4Tag"],[33918,"IntergraphPacketData"],[33919,"IntergraphFlagRegisters"],[33921,"INGRReserved"],[34016,"Site"],[34017,"ColorSequence"],[34018,"IT8Header"],[34019,"RasterPadding"],[34020,"BitsPerRunLength"],[34021,"BitsPerExtendedRunLength"],[34022,"ColorTable"],[34023,"ImageColorIndicator"],[34024,"BackgroundColorIndicator"],[34025,"ImageColorValue"],[34026,"BackgroundColorValue"],[34027,"PixelIntensityRange"],[34028,"TransparencyIndicator"],[34029,"ColorCharacterization"],[34030,"HCUsage"],[34031,"TrapIndicator"],[34032,"CMYKEquivalent"],[34152,"AFCP_IPTC"],[34232,"PixelMagicJBIGOptions"],[34263,"JPLCartoIFD"],[34306,"WB_GRGBLevels"],[34310,"LeafData"],[34687,"TIFF_FXExtensions"],[34688,"MultiProfiles"],[34689,"SharedData"],[34690,"T88Options"],[34732,"ImageLayer"],[34750,"JBIGOptions"],[34856,"Opto-ElectricConvFactor"],[34857,"Interlace"],[34908,"FaxRecvParams"],[34909,"FaxSubAddress"],[34910,"FaxRecvTime"],[34929,"FedexEDR"],[34954,"LeafSubIFD"],[37387,"FlashEnergy"],[37388,"SpatialFrequencyResponse"],[37389,"Noise"],[37390,"FocalPlaneXResolution"],[37391,"FocalPlaneYResolution"],[37392,"FocalPlaneResolutionUnit"],[37397,"ExposureIndex"],[37398,"TIFF-EPStandardID"],[37399,"SensingMethod"],[37434,"CIP3DataFile"],[37435,"CIP3Sheet"],[37436,"CIP3Side"],[37439,"StoNits"],[37679,"MSDocumentText"],[37680,"MSPropertySetStorage"],[37681,"MSDocumentTextPosition"],[37724,"ImageSourceData"],[40965,"InteropIFD"],[40976,"SamsungRawPointersOffset"],[40977,"SamsungRawPointersLength"],[41217,"SamsungRawByteOrder"],[41218,"SamsungRawUnknown"],[41484,"SpatialFrequencyResponse"],[41485,"Noise"],[41489,"ImageNumber"],[41490,"SecurityClassification"],[41491,"ImageHistory"],[41494,"TIFF-EPStandardID"],[41995,"DeviceSettingDescription"],[42112,"GDALMetadata"],[42113,"GDALNoData"],[44992,"ExpandSoftware"],[44993,"ExpandLens"],[44994,"ExpandFilm"],[44995,"ExpandFilterLens"],[44996,"ExpandScanner"],[44997,"ExpandFlashLamp"],[46275,"HasselbladRawImage"],[48129,"PixelFormat"],[48130,"Transformation"],[48131,"Uncompressed"],[48132,"ImageType"],[48256,"ImageWidth"],[48257,"ImageHeight"],[48258,"WidthResolution"],[48259,"HeightResolution"],[48320,"ImageOffset"],[48321,"ImageByteCount"],[48322,"AlphaOffset"],[48323,"AlphaByteCount"],[48324,"ImageDataDiscard"],[48325,"AlphaDataDiscard"],[50215,"OceScanjobDesc"],[50216,"OceApplicationSelector"],[50217,"OceIDNumber"],[50218,"OceImageLogic"],[50255,"Annotations"],[50459,"HasselbladExif"],[50547,"OriginalFileName"],[50560,"USPTOOriginalContentType"],[50656,"CR2CFAPattern"],[50710,"CFAPlaneColor"],[50711,"CFALayout"],[50712,"LinearizationTable"],[50713,"BlackLevelRepeatDim"],[50714,"BlackLevel"],[50715,"BlackLevelDeltaH"],[50716,"BlackLevelDeltaV"],[50717,"WhiteLevel"],[50718,"DefaultScale"],[50719,"DefaultCropOrigin"],[50720,"DefaultCropSize"],[50733,"BayerGreenSplit"],[50737,"ChromaBlurRadius"],[50738,"AntiAliasStrength"],[50752,"RawImageSegmentation"],[50780,"BestQualityScale"],[50784,"AliasLayerMetadata"],[50829,"ActiveArea"],[50830,"MaskedAreas"],[50935,"NoiseReductionApplied"],[50974,"SubTileBlockSize"],[50975,"RowInterleaveFactor"],[51008,"OpcodeList1"],[51009,"OpcodeList2"],[51022,"OpcodeList3"],[51041,"NoiseProfile"],[51114,"CacheVersion"],[51125,"DefaultUserCrop"],[51157,"NikonNEFInfo"],[65024,"KdcIFD"]];wa(Si,"ifd0",Ll),wa(Si,"exif",Ll),pi(Wi,"gps",[[23,{M:"Magnetic North",T:"True North"}],[25,{K:"Kilometers",M:"Miles",N:"Nautical Miles"}]]);var oo=class extends qi{static canHandle(Gr,Wr){return Gr.getUint8(Wr+1)===224&&Gr.getUint32(Wr+4)===1246120262&&Gr.getUint8(Wr+8)===0}parse(){return this.parseTags(),this.translate(),this.output}parseTags(){this.raw=new Map([[0,this.chunk.getUint16(0)],[2,this.chunk.getUint8(2)],[3,this.chunk.getUint16(3)],[5,this.chunk.getUint16(5)],[7,this.chunk.getUint8(7)],[8,this.chunk.getUint8(8)]])}};Kn(oo,"type","jfif"),Kn(oo,"headerLength",9),bi.set("jfif",oo),pi(Si,"jfif",[[0,"JFIFVersion"],[2,"ResolutionUnit"],[3,"XResolution"],[5,"YResolution"],[7,"ThumbnailWidth"],[8,"ThumbnailHeight"]]);var Ko=class extends qi{parse(){return this.parseTags(),this.translate(),this.output}parseTags(){this.raw=new Map([[0,this.chunk.getUint32(0)],[4,this.chunk.getUint32(4)],[8,this.chunk.getUint8(8)],[9,this.chunk.getUint8(9)],[10,this.chunk.getUint8(10)],[11,this.chunk.getUint8(11)],[12,this.chunk.getUint8(12)],...Array.from(this.raw)])}};Kn(Ko,"type","ihdr"),bi.set("ihdr",Ko),pi(Si,"ihdr",[[0,"ImageWidth"],[4,"ImageHeight"],[8,"BitDepth"],[9,"ColorType"],[10,"Compression"],[11,"Filter"],[12,"Interlace"]]),pi(Wi,"ihdr",[[9,{0:"Grayscale",2:"RGB",3:"Palette",4:"Grayscale with Alpha",6:"RGB with Alpha",DEFAULT:"Unknown"}],[10,{0:"Deflate/Inflate",DEFAULT:"Unknown"}],[11,{0:"Adaptive",DEFAULT:"Unknown"}],[12,{0:"Noninterlaced",1:"Adam7 Interlace",DEFAULT:"Unknown"}]]);var qa=class extends qi{static canHandle(Gr,Wr){return Gr.getUint8(Wr+1)===226&&Gr.getUint32(Wr+4)===1229144927}static findPosition(Gr,Wr){let Kr=super.findPosition(Gr,Wr);return Kr.chunkNumber=Gr.getUint8(Wr+16),Kr.chunkCount=Gr.getUint8(Wr+17),Kr.multiSegment=Kr.chunkCount>1,Kr}static handleMultiSegments(Gr){return function(Wr){let Kr=function(Yr){let Zr=Yr[0].constructor,tn=0;for(let yn of Yr)tn+=yn.length;let ln=new Zr(tn),dn=0;for(let yn of Yr)ln.set(yn,dn),dn+=yn.length;return ln}(Wr.map(Yr=>Yr.chunk.toUint8()));return new Ki(Kr)}(Gr)}parse(){return this.raw=new Map,this.parseHeader(),this.parseTags(),this.translate(),this.output}parseHeader(){let{raw:Gr}=this;this.chunk.byteLength<84&&Ei("ICC header is too short");for(let[Wr,Kr]of Object.entries(Ku)){Wr=parseInt(Wr,10);let Yr=Kr(this.chunk,Wr);Yr!=="\0\0\0\0"&&Gr.set(Wr,Yr)}}parseTags(){let Gr,Wr,Kr,Yr,Zr,{raw:tn}=this,ln=this.chunk.getUint32(128),dn=132,yn=this.chunk.byteLength;for(;ln--;){if(Gr=this.chunk.getString(dn,4),Wr=this.chunk.getUint32(dn+4),Kr=this.chunk.getUint32(dn+8),Yr=this.chunk.getString(Wr,4),Wr+Kr>yn)return void console.warn("reached the end of the first ICC chunk. Enable options.tiff.multiSegment to read all ICC segments.");Zr=this.parseTag(Yr,Wr,Kr),Zr!==void 0&&Zr!=="\0\0\0\0"&&tn.set(Gr,Zr),dn+=12}}parseTag(Gr,Wr,Kr){switch(Gr){case"desc":return this.parseDesc(Wr);case"mluc":return this.parseMluc(Wr);case"text":return this.parseText(Wr,Kr);case"sig ":return this.parseSig(Wr)}if(!(Wr+Kr>this.chunk.byteLength))return this.chunk.getUint8Array(Wr,Kr)}parseDesc(Gr){let Wr=this.chunk.getUint32(Gr+8)-1;return ba(this.chunk.getString(Gr+12,Wr))}parseText(Gr,Wr){return ba(this.chunk.getString(Gr+8,Wr-8))}parseSig(Gr){return ba(this.chunk.getString(Gr+8,4))}parseMluc(Gr){let{chunk:Wr}=this,Kr=Wr.getUint32(Gr+8),Yr=Wr.getUint32(Gr+12),Zr=Gr+16,tn=[];for(let ln=0;ln<Kr;ln++){let dn=Wr.getString(Zr+0,2),yn=Wr.getString(Zr+2,2),wn=Wr.getUint32(Zr+4),kn=Wr.getUint32(Zr+8)+Gr,An=ba(Wr.getUnicodeString(kn,wn));tn.push({lang:dn,country:yn,text:An}),Zr+=Yr}return Kr===1?tn[0].text:tn}translateValue(Gr,Wr){return typeof Gr=="string"?Wr[Gr]||Wr[Gr.toLowerCase()]||Gr:Wr[Gr]||Gr}};Kn(qa,"type","icc"),Kn(qa,"multiSegment",!0),Kn(qa,"headerLength",18);var Ku={4:na,8:function(Qr,Gr){return[Qr.getUint8(Gr),Qr.getUint8(Gr+1)>>4,Qr.getUint8(Gr+1)%16].map(Wr=>Wr.toString(10)).join(".")},12:na,16:na,20:na,24:function(Qr,Gr){let Wr=Qr.getUint16(Gr),Kr=Qr.getUint16(Gr+2)-1,Yr=Qr.getUint16(Gr+4),Zr=Qr.getUint16(Gr+6),tn=Qr.getUint16(Gr+8),ln=Qr.getUint16(Gr+10);return new Date(Date.UTC(Wr,Kr,Yr,Zr,tn,ln))},36:na,40:na,48:na,52:na,64:(Qr,Gr)=>Qr.getUint32(Gr),80:na};function na(Qr,Gr){return ba(Qr.getString(Gr,4))}bi.set("icc",qa),pi(Si,"icc",[[4,"ProfileCMMType"],[8,"ProfileVersion"],[12,"ProfileClass"],[16,"ColorSpaceData"],[20,"ProfileConnectionSpace"],[24,"ProfileDateTime"],[36,"ProfileFileSignature"],[40,"PrimaryPlatform"],[44,"CMMFlags"],[48,"DeviceManufacturer"],[52,"DeviceModel"],[56,"DeviceAttributes"],[64,"RenderingIntent"],[68,"ConnectionSpaceIlluminant"],[80,"ProfileCreator"],[84,"ProfileID"],["Header","ProfileHeader"],["MS00","WCSProfiles"],["bTRC","BlueTRC"],["bXYZ","BlueMatrixColumn"],["bfd","UCRBG"],["bkpt","MediaBlackPoint"],["calt","CalibrationDateTime"],["chad","ChromaticAdaptation"],["chrm","Chromaticity"],["ciis","ColorimetricIntentImageState"],["clot","ColorantTableOut"],["clro","ColorantOrder"],["clrt","ColorantTable"],["cprt","ProfileCopyright"],["crdi","CRDInfo"],["desc","ProfileDescription"],["devs","DeviceSettings"],["dmdd","DeviceModelDesc"],["dmnd","DeviceMfgDesc"],["dscm","ProfileDescriptionML"],["fpce","FocalPlaneColorimetryEstimates"],["gTRC","GreenTRC"],["gXYZ","GreenMatrixColumn"],["gamt","Gamut"],["kTRC","GrayTRC"],["lumi","Luminance"],["meas","Measurement"],["meta","Metadata"],["mmod","MakeAndModel"],["ncl2","NamedColor2"],["ncol","NamedColor"],["ndin","NativeDisplayInfo"],["pre0","Preview0"],["pre1","Preview1"],["pre2","Preview2"],["ps2i","PS2RenderingIntent"],["ps2s","PostScript2CSA"],["psd0","PostScript2CRD0"],["psd1","PostScript2CRD1"],["psd2","PostScript2CRD2"],["psd3","PostScript2CRD3"],["pseq","ProfileSequenceDesc"],["psid","ProfileSequenceIdentifier"],["psvm","PS2CRDVMSize"],["rTRC","RedTRC"],["rXYZ","RedMatrixColumn"],["resp","OutputResponse"],["rhoc","ReflectionHardcopyOrigColorimetry"],["rig0","PerceptualRenderingIntentGamut"],["rig2","SaturationRenderingIntentGamut"],["rpoc","ReflectionPrintOutputColorimetry"],["sape","SceneAppearanceEstimates"],["scoe","SceneColorimetryEstimates"],["scrd","ScreeningDesc"],["scrn","Screening"],["targ","CharTarget"],["tech","Technology"],["vcgt","VideoCardGamma"],["view","ViewingConditions"],["vued","ViewingCondDesc"],["wtpt","MediaWhitePoint"]]);var Fo={"4d2p":"Erdt Systems",AAMA:"Aamazing Technologies",ACER:"Acer",ACLT:"Acolyte Color Research",ACTI:"Actix Sytems",ADAR:"Adara Technology",ADBE:"Adobe",ADI:"ADI Systems",AGFA:"Agfa Graphics",ALMD:"Alps Electric",ALPS:"Alps Electric",ALWN:"Alwan Color Expertise",AMTI:"Amiable Technologies",AOC:"AOC International",APAG:"Apago",APPL:"Apple Computer",AST:"AST","AT&T":"AT&T",BAEL:"BARBIERI electronic",BRCO:"Barco NV",BRKP:"Breakpoint",BROT:"Brother",BULL:"Bull",BUS:"Bus Computer Systems","C-IT":"C-Itoh",CAMR:"Intel",CANO:"Canon",CARR:"Carroll Touch",CASI:"Casio",CBUS:"Colorbus PL",CEL:"Crossfield",CELx:"Crossfield",CGS:"CGS Publishing Technologies International",CHM:"Rochester Robotics",CIGL:"Colour Imaging Group, London",CITI:"Citizen",CL00:"Candela",CLIQ:"Color IQ",CMCO:"Chromaco",CMiX:"CHROMiX",COLO:"Colorgraphic Communications",COMP:"Compaq",COMp:"Compeq/Focus Technology",CONR:"Conrac Display Products",CORD:"Cordata Technologies",CPQ:"Compaq",CPRO:"ColorPro",CRN:"Cornerstone",CTX:"CTX International",CVIS:"ColorVision",CWC:"Fujitsu Laboratories",DARI:"Darius Technology",DATA:"Dataproducts",DCP:"Dry Creek Photo",DCRC:"Digital Contents Resource Center, Chung-Ang University",DELL:"Dell Computer",DIC:"Dainippon Ink and Chemicals",DICO:"Diconix",DIGI:"Digital","DL&C":"Digital Light & Color",DPLG:"Doppelganger",DS:"Dainippon Screen",DSOL:"DOOSOL",DUPN:"DuPont",EPSO:"Epson",ESKO:"Esko-Graphics",ETRI:"Electronics and Telecommunications Research Institute",EVER:"Everex Systems",EXAC:"ExactCODE",Eizo:"Eizo",FALC:"Falco Data Products",FF:"Fuji Photo Film",FFEI:"FujiFilm Electronic Imaging",FNRD:"Fnord Software",FORA:"Fora",FORE:"Forefront Technology",FP:"Fujitsu",FPA:"WayTech Development",FUJI:"Fujitsu",FX:"Fuji Xerox",GCC:"GCC Technologies",GGSL:"Global Graphics Software",GMB:"Gretagmacbeth",GMG:"GMG",GOLD:"GoldStar Technology",GOOG:"Google",GPRT:"Giantprint",GTMB:"Gretagmacbeth",GVC:"WayTech Development",GW2K:"Sony",HCI:"HCI",HDM:"Heidelberger Druckmaschinen",HERM:"Hermes",HITA:"Hitachi America",HP:"Hewlett-Packard",HTC:"Hitachi",HiTi:"HiTi Digital",IBM:"IBM",IDNT:"Scitex",IEC:"Hewlett-Packard",IIYA:"Iiyama North America",IKEG:"Ikegami Electronics",IMAG:"Image Systems",IMI:"Ingram Micro",INTC:"Intel",INTL:"N/A (INTL)",INTR:"Intra Electronics",IOCO:"Iocomm International Technology",IPS:"InfoPrint Solutions Company",IRIS:"Scitex",ISL:"Ichikawa Soft Laboratory",ITNL:"N/A (ITNL)",IVM:"IVM",IWAT:"Iwatsu Electric",Idnt:"Scitex",Inca:"Inca Digital Printers",Iris:"Scitex",JPEG:"Joint Photographic Experts Group",JSFT:"Jetsoft Development",JVC:"JVC Information Products",KART:"Scitex",KFC:"KFC Computek Components",KLH:"KLH Computers",KMHD:"Konica Minolta",KNCA:"Konica",KODA:"Kodak",KYOC:"Kyocera",Kart:"Scitex",LCAG:"Leica",LCCD:"Leeds Colour",LDAK:"Left Dakota",LEAD:"Leading Technology",LEXM:"Lexmark International",LINK:"Link Computer",LINO:"Linotronic",LITE:"Lite-On",Leaf:"Leaf",Lino:"Linotronic",MAGC:"Mag Computronic",MAGI:"MAG Innovision",MANN:"Mannesmann",MICN:"Micron Technology",MICR:"Microtek",MICV:"Microvitec",MINO:"Minolta",MITS:"Mitsubishi Electronics America",MITs:"Mitsuba",MNLT:"Minolta",MODG:"Modgraph",MONI:"Monitronix",MONS:"Monaco Systems",MORS:"Morse Technology",MOTI:"Motive Systems",MSFT:"Microsoft",MUTO:"MUTOH INDUSTRIES",Mits:"Mitsubishi Electric",NANA:"NANAO",NEC:"NEC",NEXP:"NexPress Solutions",NISS:"Nissei Sangyo America",NKON:"Nikon",NONE:"none",OCE:"Oce Technologies",OCEC:"OceColor",OKI:"Oki",OKID:"Okidata",OKIP:"Okidata",OLIV:"Olivetti",OLYM:"Olympus",ONYX:"Onyx Graphics",OPTI:"Optiquest",PACK:"Packard Bell",PANA:"Matsushita Electric Industrial",PANT:"Pantone",PBN:"Packard Bell",PFU:"PFU",PHIL:"Philips Consumer Electronics",PNTX:"HOYA",POne:"Phase One A/S",PREM:"Premier Computer Innovations",PRIN:"Princeton Graphic Systems",PRIP:"Princeton Publishing Labs",QLUX:"Hong Kong",QMS:"QMS",QPCD:"QPcard AB",QUAD:"QuadLaser",QUME:"Qume",RADI:"Radius",RDDx:"Integrated Color Solutions",RDG:"Roland DG",REDM:"REDMS Group",RELI:"Relisys",RGMS:"Rolf Gierling Multitools",RICO:"Ricoh",RNLD:"Edmund Ronald",ROYA:"Royal",RPC:"Ricoh Printing Systems",RTL:"Royal Information Electronics",SAMP:"Sampo",SAMS:"Samsung",SANT:"Jaime Santana Pomares",SCIT:"Scitex",SCRN:"Dainippon Screen",SDP:"Scitex",SEC:"Samsung",SEIK:"Seiko Instruments",SEIk:"Seikosha",SGUY:"ScanGuy.com",SHAR:"Sharp Laboratories",SICC:"International Color Consortium",SONY:"Sony",SPCL:"SpectraCal",STAR:"Star",STC:"Sampo Technology",Scit:"Scitex",Sdp:"Scitex",Sony:"Sony",TALO:"Talon Technology",TAND:"Tandy",TATU:"Tatung",TAXA:"TAXAN America",TDS:"Tokyo Denshi Sekei",TECO:"TECO Information Systems",TEGR:"Tegra",TEKT:"Tektronix",TI:"Texas Instruments",TMKR:"TypeMaker",TOSB:"Toshiba",TOSH:"Toshiba",TOTK:"TOTOKU ELECTRIC",TRIU:"Triumph",TSBT:"Toshiba",TTX:"TTX Computer Products",TVM:"TVM Professional Monitor",TW:"TW Casper",ULSX:"Ulead Systems",UNIS:"Unisys",UTZF:"Utz Fehlau & Sohn",VARI:"Varityper",VIEW:"Viewsonic",VISL:"Visual communication",VIVO:"Vivo Mobile Communication",WANG:"Wang",WLBR:"Wilbur Imaging",WTG2:"Ware To Go",WYSE:"WYSE Technology",XERX:"Xerox",XRIT:"X-Rite",ZRAN:"Zoran",Zebr:"Zebra Technologies",appl:"Apple Computer",bICC:"basICColor",berg:"bergdesign",ceyd:"Integrated Color Solutions",clsp:"MacDermid ColorSpan",ds:"Dainippon Screen",dupn:"DuPont",ffei:"FujiFilm Electronic Imaging",flux:"FluxData",iris:"Scitex",kart:"Scitex",lcms:"Little CMS",lino:"Linotronic",none:"none",ob4d:"Erdt Systems",obic:"Medigraph",quby:"Qubyx Sarl",scit:"Scitex",scrn:"Dainippon Screen",sdp:"Scitex",siwi:"SIWI GRAFIKA",yxym:"YxyMaster"},Ml={scnr:"Scanner",mntr:"Monitor",prtr:"Printer",link:"Device Link",abst:"Abstract",spac:"Color Space Conversion Profile",nmcl:"Named Color",cenc:"ColorEncodingSpace profile",mid:"MultiplexIdentification profile",mlnk:"MultiplexLink profile",mvis:"MultiplexVisualization profile",nkpf:"Nikon Input Device Profile (NON-STANDARD!)"};pi(Wi,"icc",[[4,Fo],[12,Ml],[40,Object.assign({},Fo,Ml)],[48,Fo],[80,Fo],[64,{0:"Perceptual",1:"Relative Colorimetric",2:"Saturation",3:"Absolute Colorimetric"}],["tech",{amd:"Active Matrix Display",crt:"Cathode Ray Tube Display",kpcd:"Photo CD",pmd:"Passive Matrix Display",dcam:"Digital Camera",dcpj:"Digital Cinema Projector",dmpc:"Digital Motion Picture Camera",dsub:"Dye Sublimation Printer",epho:"Electrophotographic Printer",esta:"Electrostatic Printer",flex:"Flexography",fprn:"Film Writer",fscn:"Film Scanner",grav:"Gravure",ijet:"Ink Jet Printer",imgs:"Photo Image Setter",mpfr:"Motion Picture Film Recorder",mpfs:"Motion Picture Film Scanner",offs:"Offset Lithography",pjtv:"Projection Television",rpho:"Photographic Paper Printer",rscn:"Reflective Scanner",silk:"Silkscreen",twax:"Thermal Wax Printer",vidc:"Video Camera",vidm:"Video Monitor"}]]);var Fa=class extends qi{static canHandle(Gr,Wr,Kr){return Gr.getUint8(Wr+1)===237&&Gr.getString(Wr+4,9)==="Photoshop"&&this.containsIptc8bim(Gr,Wr,Kr)!==void 0}static headerLength(Gr,Wr,Kr){let Yr,Zr=this.containsIptc8bim(Gr,Wr,Kr);if(Zr!==void 0)return Yr=Gr.getUint8(Wr+Zr+7),Yr%2!=0&&(Yr+=1),Yr===0&&(Yr=4),Zr+8+Yr}static containsIptc8bim(Gr,Wr,Kr){for(let Yr=0;Yr<Kr;Yr++)if(this.isIptcSegmentHead(Gr,Wr+Yr))return Yr}static isIptcSegmentHead(Gr,Wr){return Gr.getUint8(Wr)===56&&Gr.getUint32(Wr)===943868237&&Gr.getUint16(Wr+4)===1028}parse(){let{raw:Gr}=this,Wr=this.chunk.byteLength-1,Kr=!1;for(let Yr=0;Yr<Wr;Yr++)if(this.chunk.getUint8(Yr)===28&&this.chunk.getUint8(Yr+1)===2){Kr=!0;let Zr=this.chunk.getUint16(Yr+3),tn=this.chunk.getUint8(Yr+2),ln=this.chunk.getLatin1String(Yr+5,Zr);Gr.set(tn,this.pluralizeValue(Gr.get(tn),ln)),Yr+=4+Zr}else if(Kr)break;return this.translate(),this.output}pluralizeValue(Gr,Wr){return Gr!==void 0?Gr instanceof Array?(Gr.push(Wr),Gr):[Gr,Wr]:Wr}};Kn(Fa,"type","iptc"),Kn(Fa,"translateValues",!1),Kn(Fa,"reviveValues",!1),bi.set("iptc",Fa),pi(Si,"iptc",[[0,"ApplicationRecordVersion"],[3,"ObjectTypeReference"],[4,"ObjectAttributeReference"],[5,"ObjectName"],[7,"EditStatus"],[8,"EditorialUpdate"],[10,"Urgency"],[12,"SubjectReference"],[15,"Category"],[20,"SupplementalCategories"],[22,"FixtureIdentifier"],[25,"Keywords"],[26,"ContentLocationCode"],[27,"ContentLocationName"],[30,"ReleaseDate"],[35,"ReleaseTime"],[37,"ExpirationDate"],[38,"ExpirationTime"],[40,"SpecialInstructions"],[42,"ActionAdvised"],[45,"ReferenceService"],[47,"ReferenceDate"],[50,"ReferenceNumber"],[55,"DateCreated"],[60,"TimeCreated"],[62,"DigitalCreationDate"],[63,"DigitalCreationTime"],[65,"OriginatingProgram"],[70,"ProgramVersion"],[75,"ObjectCycle"],[80,"Byline"],[85,"BylineTitle"],[90,"City"],[92,"Sublocation"],[95,"State"],[100,"CountryCode"],[101,"Country"],[103,"OriginalTransmissionReference"],[105,"Headline"],[110,"Credit"],[115,"Source"],[116,"CopyrightNotice"],[118,"Contact"],[120,"Caption"],[121,"LocalCaption"],[122,"Writer"],[125,"RasterizedCaption"],[130,"ImageType"],[131,"ImageOrientation"],[135,"LanguageIdentifier"],[150,"AudioType"],[151,"AudioSamplingRate"],[152,"AudioSamplingResolution"],[153,"AudioDuration"],[154,"AudioOutcue"],[184,"JobID"],[185,"MasterDocumentID"],[186,"ShortDocumentID"],[187,"UniqueDocumentID"],[188,"OwnerID"],[200,"ObjectPreviewFileFormat"],[201,"ObjectPreviewFileVersion"],[202,"ObjectPreviewData"],[221,"Prefs"],[225,"ClassifyState"],[228,"SimilarityIndex"],[230,"DocumentNotes"],[231,"DocumentHistory"],[232,"ExifCameraInfo"],[255,"CatalogSets"]]),pi(Wi,"iptc",[[10,{0:"0 (reserved)",1:"1 (most urgent)",2:"2",3:"3",4:"4",5:"5 (normal urgency)",6:"6",7:"7",8:"8 (least urgent)",9:"9 (user-defined priority)"}],[75,{a:"Morning",b:"Both Morning and Evening",p:"Evening"}],[131,{L:"Landscape",P:"Portrait",S:"Square"}]]);var js=Wu;var Xl=po(mo());var Wl=["DateTimeOriginal","ExposureTime","FNumber","Flash","FocalLengthIn35mmFormat","ISO","LensMake","LensModel","Make","Model"];var es=class{async uploadPhotos(Gr){let Wr=new FormData(Gr),Kr=Wr.getAll("files")??[],Yr=Wr.get("parseExif")==="on";if(Kr.length>10){alert("You can only upload 10 photos at a time");return}let Zr=Kr.map(async tn=>{let ln,dn,yn;try{if(ln=await Ma(tn),ln===null||typeof ln!="string"){console.error("File data URL is not a string:",ln),alert("Error reading file.");return}}catch(An){console.error("Error reading file as Data URL:",An),alert("Error reading file.");return}if(Yr)try{let An=await js.parse(tn,{pick:Wl});console.log("EXIF tags:",await js.parse(tn)),dn=$u(An)}catch(An){console.error("Error reading EXIF data:",An)}try{yn=await to(ln,{width:2e3,height:2e3,maxSize:1e3*1e3,mode:"contain"})}catch(An){console.error("Error resizing image:",An),alert("Error resizing image.");return}let wn=eo(yn.path),kn=new FormData;kn.append("file",wn,tn.name),kn.append("width",String(yn.width)),kn.append("height",String(yn.height)),dn&&kn.append("exif",JSON.stringify(dn)),Xl.default.ajax("POST","/actions/photo",{swap:"afterbegin",target:"#image-preview",values:Object.fromEntries(kn),source:Gr})});await Promise.all(Zr),Gr.querySelector("input[type='file']").value=""}},Yu=1e6;function $u(Qr,Gr=Yu){let Wr={};for(let[Kr,Yr]of Object.entries(Qr)){let Zr=Kr[0].toLowerCase()+Kr.slice(1);typeof Yr=="number"?Wr[Zr]=Math.round(Yr*Gr):Array.isArray(Yr)?Wr[Zr]=Yr.map(tn=>typeof tn=="number"?Math.round(tn*Gr):tn):Yr instanceof Date?Wr[Zr]=Yr.toISOString():typeof Yr=="string"||typeof Yr=="boolean"?Wr[Zr]=Yr:Yr===void 0?Wr[Zr]=void 0:Wr[Zr]=String(Yr)}return Wr}var Ql=new Ro({layoutMode:"justified"});Ql.init();zs.default.onLoad(function(Qr){if(No.maybeInitForElement(Qr),Qr.id==="gallery-sort-dialog"){let Gr=Qr.querySelectorAll(".sortable");for(let Wr of Array.from(Gr))new El(Wr,{animation:150})}});Vs.default.browserInit();var Zi=globalThis;Zi.htmx=Zi.htmx??zs.default;Zi._hyperscript=Zi._hyperscript??Vs.default;Zi.Grain=Zi.Grain??{};Zi.Grain.uploadPage=new es;Zi.Grain.profileDialog=new Lo;Zi.Grain.galleryLayout=Ql;Zi.Grain.photoManip=Ts; 17 + /*! Bundled license information: 18 + 19 + sortablejs/modular/sortable.esm.js: 20 + (**! 21 + * Sortable 1.15.6 22 + * @author RubaXa <trash@rubaxa.org> 23 + * @author owenm <owen23355@gmail.com> 24 + * @license MIT 25 + *) 26 + */
-170
static/masonry.js
··· 1 - // deno-lint-ignore-file 2 - 3 - let masonryObserverInitialized = false; 4 - let layoutMode = "justified"; 5 - 6 - function computeLayout() { 7 - if (layoutMode === "masonry") { 8 - computeMasonry(); 9 - } else { 10 - computeJustified(); 11 - } 12 - } 13 - 14 - function toggleLayout(layout = "justified") { 15 - layoutMode = layout; 16 - computeLayout(); 17 - } 18 - 19 - function computeMasonry() { 20 - const container = document.getElementById("masonry-container"); 21 - if (!container) return; 22 - 23 - const spacing = 8; 24 - const containerWidth = container.offsetWidth; 25 - 26 - if (containerWidth === 0) { 27 - requestAnimationFrame(computeMasonry); 28 - return; 29 - } 30 - 31 - const columns = containerWidth < 640 ? 1 : 3; 32 - 33 - const columnWidth = (containerWidth + spacing) / columns - spacing; 34 - const columnHeights = new Array(columns).fill(0); 35 - const tiles = container.querySelectorAll(".masonry-tile"); 36 - 37 - tiles.forEach((tile) => { 38 - const imgW = parseFloat(tile.dataset.width); 39 - const imgH = parseFloat(tile.dataset.height); 40 - if (!imgW || !imgH) return; 41 - 42 - const aspectRatio = imgH / imgW; 43 - const renderedHeight = aspectRatio * columnWidth; 44 - 45 - let shortestIndex = 0; 46 - for (let i = 1; i < columns; i++) { 47 - if (columnHeights[i] < columnHeights[shortestIndex]) { 48 - shortestIndex = i; 49 - } 50 - } 51 - 52 - const left = (columnWidth + spacing) * shortestIndex; 53 - const top = columnHeights[shortestIndex]; 54 - 55 - Object.assign(tile.style, { 56 - position: "absolute", 57 - width: `${columnWidth}px`, 58 - height: `${renderedHeight}px`, 59 - left: `${left}px`, 60 - top: `${top}px`, 61 - }); 62 - 63 - columnHeights[shortestIndex] = top + renderedHeight + spacing; 64 - }); 65 - 66 - container.style.height = `${Math.max(...columnHeights)}px`; 67 - } 68 - 69 - function computeJustified() { 70 - const container = document.getElementById("masonry-container"); 71 - if (!container) return; 72 - 73 - const spacing = 8; 74 - const containerWidth = container.offsetWidth; 75 - 76 - if (containerWidth === 0) { 77 - requestAnimationFrame(computeJustified); 78 - return; 79 - } 80 - 81 - const tiles = Array.from(container.querySelectorAll(".masonry-tile")); 82 - let currentRow = []; 83 - let rowAspectRatioSum = 0; 84 - let yOffset = 0; 85 - 86 - // Clear all styles before layout 87 - tiles.forEach((tile) => { 88 - Object.assign(tile.style, { 89 - position: "absolute", 90 - left: "0px", 91 - top: "0px", 92 - width: "auto", 93 - height: "auto", 94 - }); 95 - }); 96 - 97 - for (let i = 0; i < tiles.length; i++) { 98 - const tile = tiles[i]; 99 - const imgW = parseFloat(tile.dataset.width); 100 - const imgH = parseFloat(tile.dataset.height); 101 - if (!imgW || !imgH) continue; 102 - 103 - const aspectRatio = imgW / imgH; 104 - currentRow.push({ tile, aspectRatio, imgW, imgH }); 105 - rowAspectRatioSum += aspectRatio; 106 - 107 - // Estimate if row is "full" enough 108 - const estimatedRowHeight = 109 - (containerWidth - (currentRow.length - 1) * spacing) / rowAspectRatioSum; 110 - 111 - // If height is reasonable or we're at the end, render the row 112 - if (estimatedRowHeight < 300 || i === tiles.length - 1) { 113 - let xOffset = 0; 114 - 115 - for (const item of currentRow) { 116 - const width = estimatedRowHeight * item.aspectRatio; 117 - Object.assign(item.tile.style, { 118 - position: "absolute", 119 - top: `${yOffset}px`, 120 - left: `${xOffset}px`, 121 - width: `${width}px`, 122 - height: `${estimatedRowHeight}px`, 123 - }); 124 - xOffset += width + spacing; 125 - } 126 - 127 - yOffset += estimatedRowHeight + spacing; 128 - currentRow = []; 129 - rowAspectRatioSum = 0; 130 - } 131 - } 132 - 133 - container.style.position = "relative"; 134 - container.style.height = `${yOffset}px`; 135 - } 136 - 137 - function observeMasonry() { 138 - if (masonryObserverInitialized) return; 139 - masonryObserverInitialized = true; 140 - 141 - const container = document.getElementById("masonry-container"); 142 - if (!container) return; 143 - 144 - // Observe parent resize 145 - if (typeof ResizeObserver !== "undefined") { 146 - const resizeObserver = new ResizeObserver(() => computeLayout()); 147 - if (container.parentElement) { 148 - resizeObserver.observe(container.parentElement); 149 - } 150 - } 151 - 152 - // Observe inner content changes (tiles being added/removed) 153 - const mutationObserver = new MutationObserver(() => { 154 - computeLayout(); 155 - }); 156 - 157 - mutationObserver.observe(container, { 158 - childList: true, 159 - subtree: true, 160 - }); 161 - } 162 - 163 - document.addEventListener("DOMContentLoaded", () => { 164 - computeMasonry(); 165 - observeMasonry(); 166 - }); 167 - 168 - window.Grain = window.Grain || {}; 169 - window.Grain.toggleLayout = toggleLayout; 170 - window.Grain.computeLayout = computeLayout;
-31
static/photo_dialog.js
··· 1 - let startX = 0; 2 - const threshold = 50; 3 - const onTouchStart = (e) => { 4 - startX = e.touches[0].clientX; 5 - }; 6 - const onTouchEnd = (e) => { 7 - const endX = e.changedTouches[0].clientX; 8 - const diffX = endX - startX; 9 - 10 - if (Math.abs(diffX) > threshold) { 11 - const direction = diffX > 0 ? "swiperight" : "swipeleft"; 12 - e.target.dispatchEvent(new CustomEvent(direction, { bubbles: true })); 13 - } 14 - }; 15 - const observer = new MutationObserver(() => { 16 - const modal = document.getElementById("photo-dialog"); 17 - if (!modal) { 18 - console.log("Photo Dialog not found, removing event listeners"); 19 - document.body.removeEventListener("touchstart", onTouchStart); 20 - document.body.removeEventListener("touchend", onTouchEnd); 21 - observer.disconnect(); 22 - } 23 - }); 24 - htmx.onLoad((evt) => { 25 - if (evt.id === "photo-dialog") { 26 - document.body.addEventListener("touchstart", onTouchStart); 27 - document.body.addEventListener("touchend", onTouchEnd); 28 - } 29 - const parent = document.body; 30 - observer.observe(parent, { childList: true, subtree: true }); 31 - });
+47 -14
static/photo_manip.js src/static/photo_manip.ts
··· 1 - // deno-lint-ignore-file no-window 2 - function readFileAsDataURL(file) { 1 + type ResizeOptions = { 2 + width: number; 3 + height: number; 4 + quality: number; 5 + mode: "cover" | "contain" | "stretch"; 6 + }; 7 + 8 + type ResizeResult = { 9 + dataUrl: string; 10 + width: number; 11 + height: number; 12 + }; 13 + 14 + type DoResizeOptions = { 15 + width: number; 16 + height: number; 17 + maxSize: number; 18 + mode: "cover" | "contain" | "stretch"; 19 + }; 20 + 21 + type DoResizeResult = { 22 + path: string; 23 + mime: string; 24 + size: number; 25 + width: number; 26 + height: number; 27 + }; 28 + 29 + export function readFileAsDataURL( 30 + file: File, 31 + ): Promise<string | ArrayBuffer | null> { 3 32 return new Promise((resolve, reject) => { 4 33 const reader = new FileReader(); 5 34 reader.onload = () => resolve(reader.result); ··· 8 37 }); 9 38 } 10 39 11 - function dataURLToBlob(dataUrl) { 40 + export function dataURLToBlob(dataUrl: string): Blob { 12 41 const [meta, base64] = dataUrl.split(","); 13 - const mime = meta.match(/:(.*?);/)[1]; 42 + // Use RegExp.exec instead of match for type safety 43 + const mimeMatch = /:(.*?);/.exec(meta); 44 + if (!mimeMatch) throw new Error("Invalid data URL"); 45 + const mime = mimeMatch[1]; 14 46 const binary = atob(base64); 15 47 const array = new Uint8Array(binary.length); 16 48 for (let i = 0; i < binary.length; i++) { ··· 19 51 return new Blob([array], { type: mime }); 20 52 } 21 53 22 - function getDataUriSize(dataUri) { 54 + function getDataUriSize(dataUri: string): number { 23 55 const base64 = dataUri.split(",")[1]; 24 56 return Math.ceil((base64.length * 3) / 4); 25 57 } 26 58 27 - function createResizedImage(dataUri, options) { 59 + function createResizedImage( 60 + dataUri: string, 61 + options: ResizeOptions, 62 + ): Promise<ResizeResult> { 28 63 return new Promise((resolve, reject) => { 29 64 const img = new Image(); 30 65 img.onload = () => { 31 - let scale = 1; 66 + let scale: number; 32 67 if (options.mode === "cover") { 33 68 scale = Math.max( 34 69 options.width / img.width, ··· 70 105 }); 71 106 } 72 107 73 - async function doResize(dataUri, opts) { 74 - let bestResult = null; 108 + export async function doResize( 109 + dataUri: string, 110 + opts: DoResizeOptions, 111 + ): Promise<DoResizeResult> { 112 + let bestResult: ResizeResult | null = null; 75 113 let minQuality = 0; 76 114 let maxQuality = 101; 77 115 ··· 106 144 height: bestResult.height, 107 145 }; 108 146 } 109 - 110 - window.Grain = window.Grain || {}; 111 - window.Grain.readFileAsDataURL = readFileAsDataURL; 112 - window.Grain.dataURLToBlob = dataURLToBlob; 113 - window.Grain.doResize = doResize;
-55
static/profile_dialog.js
··· 1 - // deno-lint-ignore-file no-window 2 - 3 - function handleAvatarImageSelect(fileInput) { 4 - if (fileInput.files.length > 0) { 5 - const file = fileInput.files[0]; 6 - Grain.readFileAsDataURL(file).then((dataUrl) => { 7 - const previewImg = document.createElement("img"); 8 - previewImg.src = dataUrl; 9 - previewImg.className = "rounded-full w-full h-full object-cover"; 10 - previewImg.alt = "Avatar preview"; 11 - 12 - const imagePreview = fileInput.closest("form").querySelector( 13 - "#image-preview", 14 - ); 15 - if (imagePreview) { 16 - imagePreview.innerHTML = ""; 17 - imagePreview.appendChild(previewImg); 18 - } 19 - }); 20 - } 21 - } 22 - 23 - async function updateProfile(formElement) { 24 - const formData = new FormData(formElement); 25 - 26 - const avatarFile = formData.get("file"); 27 - if (avatarFile && avatarFile.type.startsWith("image/")) { 28 - try { 29 - const dataUrl = await Grain.readFileAsDataURL(file); 30 - 31 - const resized = await Grain.doResize(dataUrl, { 32 - width: 2000, 33 - height: 2000, 34 - maxSize: 1000 * 1000, // 1MB 35 - mode: "contain", 36 - }); 37 - 38 - const blob = Grain.dataURLToBlob(resized.path); 39 - formData.set("file", blob, avatarFile.name); 40 - } catch (err) { 41 - console.error("Error resizing image:", err); 42 - formData.delete("file"); 43 - } 44 - } 45 - 46 - htmx.ajax("POST", "/actions/profile/update", { 47 - "swap": "none", 48 - "values": Object.fromEntries(formData), 49 - "source": formElement, 50 - }); 51 - } 52 - 53 - window.Grain = window.Grain || {}; 54 - window.Grain.handleAvatarImageSelect = handleAvatarImageSelect; 55 - window.Grain.updateProfile = updateProfile;
-8
static/sortable.js
··· 1 - htmx.onLoad(function (content) { 2 - const sortables = content.querySelectorAll(".sortable"); 3 - for (const sortable of sortables) { 4 - new Sortable(sortable, { 5 - animation: 150, 6 - }); 7 - } 8 - });
+277 -1
static/styles.css
··· 1 - /*! tailwindcss v4.1.7 | MIT License | https://tailwindcss.com */ 1 + /*! tailwindcss v4.1.8 | MIT License | https://tailwindcss.com */ 2 2 @layer properties; 3 3 @layer theme, base, components, utilities; 4 4 @layer theme { ··· 8 8 --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", 9 9 "Courier New", monospace; 10 10 --color-sky-500: oklch(68.5% 0.169 237.323); 11 + --color-sky-600: oklch(58.8% 0.158 241.966); 11 12 --color-zinc-50: oklch(98.5% 0 0); 12 13 --color-zinc-100: oklch(96.7% 0.001 286.375); 13 14 --color-zinc-200: oklch(92% 0.004 286.32); ··· 32 33 --text-xl--line-height: calc(1.75 / 1.25); 33 34 --text-2xl: 1.5rem; 34 35 --text-2xl--line-height: calc(2 / 1.5); 36 + --text-3xl: 1.875rem; 37 + --text-3xl--line-height: calc(2.25 / 1.875); 35 38 --text-4xl: 2.25rem; 36 39 --text-4xl--line-height: calc(2.5 / 2.25); 37 40 --font-weight-medium: 500; 38 41 --font-weight-semibold: 600; 39 42 --font-weight-bold: 700; 43 + --ease-in: cubic-bezier(0.4, 0, 1, 1); 44 + --default-transition-duration: 150ms; 45 + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); 40 46 --default-font-family: var(--font-sans); 41 47 --default-mono-font-family: var(--font-mono); 42 48 } ··· 187 193 } 188 194 } 189 195 @layer utilities { 196 + .invisible { 197 + visibility: hidden; 198 + } 199 + .visible { 200 + visibility: visible; 201 + } 190 202 .sr-only { 191 203 position: absolute; 192 204 width: 1px; ··· 261 273 .z-100 { 262 274 z-index: 100; 263 275 } 276 + .z-101 { 277 + z-index: 101; 278 + } 264 279 .container { 265 280 width: 100%; 266 281 @media (width >= 40rem) { ··· 279 294 max-width: 96rem; 280 295 } 281 296 } 297 + .-m-1 { 298 + margin: calc(var(--spacing) * -1); 299 + } 300 + .-m-2 { 301 + margin: calc(var(--spacing) * -2); 302 + } 303 + .mx-1 { 304 + margin-inline: calc(var(--spacing) * 1); 305 + } 306 + .mx-2 { 307 + margin-inline: calc(var(--spacing) * 2); 308 + } 282 309 .mx-auto { 283 310 margin-inline: auto; 284 311 } 312 + .my-1 { 313 + margin-block: calc(var(--spacing) * 1); 314 + } 285 315 .my-2 { 286 316 margin-block: calc(var(--spacing) * 2); 287 317 } 288 318 .my-4 { 289 319 margin-block: calc(var(--spacing) * 4); 320 + } 321 + .my-10 { 322 + margin-block: calc(var(--spacing) * 10); 290 323 } 291 324 .my-30 { 292 325 margin-block: calc(var(--spacing) * 30); 293 326 } 327 + .mt-1 { 328 + margin-top: calc(var(--spacing) * 1); 329 + } 294 330 .mt-2 { 295 331 margin-top: calc(var(--spacing) * 2); 296 332 } ··· 303 339 .mr-2 { 304 340 margin-right: calc(var(--spacing) * 2); 305 341 } 342 + .mb-1 { 343 + margin-bottom: calc(var(--spacing) * 1); 344 + } 306 345 .mb-2 { 307 346 margin-bottom: calc(var(--spacing) * 2); 308 347 } 309 348 .mb-4 { 310 349 margin-bottom: calc(var(--spacing) * 4); 311 350 } 351 + .mb-6 { 352 + margin-bottom: calc(var(--spacing) * 6); 353 + } 354 + .mb-8 { 355 + margin-bottom: calc(var(--spacing) * 8); 356 + } 357 + .ml-0 { 358 + margin-left: calc(var(--spacing) * 0); 359 + } 312 360 .block { 313 361 display: block; 314 362 } ··· 329 377 } 330 378 .inline-flex { 331 379 display: inline-flex; 380 + } 381 + .table { 382 + display: table; 332 383 } 333 384 .aspect-\[3\/2\] { 334 385 aspect-ratio: 3/2; ··· 370 421 .h-full { 371 422 height: 100%; 372 423 } 424 + .max-h-\[calc\(100vh-100px\)\] { 425 + max-height: calc(100vh - 100px); 426 + } 373 427 .min-h-screen { 374 428 min-height: 100vh; 375 429 } ··· 424 478 .min-w-0 { 425 479 min-width: calc(var(--spacing) * 0); 426 480 } 481 + .min-w-\[120px\] { 482 + min-width: 120px; 483 + } 427 484 .flex-1 { 428 485 flex: 1; 429 486 } ··· 453 510 } 454 511 .flex-col { 455 512 flex-direction: column; 513 + } 514 + .flex-row { 515 + flex-direction: row; 456 516 } 457 517 .flex-wrap { 458 518 flex-wrap: wrap; ··· 463 523 .items-center { 464 524 align-items: center; 465 525 } 526 + .items-start { 527 + align-items: flex-start; 528 + } 466 529 .justify-between { 467 530 justify-content: space-between; 468 531 } ··· 498 561 .gap-x-4 { 499 562 column-gap: calc(var(--spacing) * 4); 500 563 } 564 + .space-x-1 { 565 + :where(& > :not(:last-child)) { 566 + --tw-space-x-reverse: 0; 567 + margin-inline-start: calc(calc(var(--spacing) * 1) * var(--tw-space-x-reverse)); 568 + margin-inline-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-x-reverse))); 569 + } 570 + } 501 571 .space-x-2 { 502 572 :where(& > :not(:last-child)) { 503 573 --tw-space-x-reverse: 0; ··· 529 599 border-color: var(--color-zinc-200); 530 600 } 531 601 } 602 + .self-end { 603 + align-self: flex-end; 604 + } 532 605 .self-start { 533 606 align-self: flex-start; 534 607 } ··· 540 613 .overflow-hidden { 541 614 overflow: hidden; 542 615 } 616 + .overflow-x-auto { 617 + overflow-x: auto; 618 + } 619 + .rounded { 620 + border-radius: 0.25rem; 621 + } 543 622 .rounded-full { 544 623 border-radius: calc(infinity * 1px); 545 624 } ··· 572 651 .bg-sky-500 { 573 652 background-color: var(--color-sky-500); 574 653 } 654 + .bg-transparent { 655 + background-color: transparent; 656 + } 575 657 .bg-white { 576 658 background-color: var(--color-white); 577 659 } ··· 587 669 .bg-zinc-950 { 588 670 background-color: var(--color-zinc-950); 589 671 } 672 + .bg-zinc-950\/70 { 673 + background-color: color-mix(in srgb, oklch(14.1% 0.005 285.823) 70%, transparent); 674 + @supports (color: color-mix(in lab, red, red)) { 675 + background-color: color-mix(in oklab, var(--color-zinc-950) 70%, transparent); 676 + } 677 + } 590 678 .fill-zinc-950 { 591 679 fill: var(--color-zinc-950); 592 680 } ··· 602 690 .p-4 { 603 691 padding: calc(var(--spacing) * 4); 604 692 } 693 + .px-1 { 694 + padding-inline: calc(var(--spacing) * 1); 695 + } 696 + .px-2 { 697 + padding-inline: calc(var(--spacing) * 2); 698 + } 605 699 .px-4 { 606 700 padding-inline: calc(var(--spacing) * 4); 607 701 } 608 702 .px-\[3px\] { 609 703 padding-inline: 3px; 610 704 } 705 + .py-1 { 706 + padding-block: calc(var(--spacing) * 1); 707 + } 611 708 .py-2 { 612 709 padding-block: calc(var(--spacing) * 2); 613 710 } ··· 641 738 .font-\[\'Jersey_20\'\] { 642 739 font-family: 'Jersey 20'; 643 740 } 741 + .font-mono { 742 + font-family: var(--font-mono); 743 + } 644 744 .text-2xl { 645 745 font-size: var(--text-2xl); 646 746 line-height: var(--tw-leading, var(--text-2xl--line-height)); 647 747 } 748 + .text-3xl { 749 + font-size: var(--text-3xl); 750 + line-height: var(--tw-leading, var(--text-3xl--line-height)); 751 + } 648 752 .text-4xl { 649 753 font-size: var(--text-4xl); 650 754 line-height: var(--tw-leading, var(--text-4xl--line-height)); ··· 652 756 .text-sm { 653 757 font-size: var(--text-sm); 654 758 line-height: var(--tw-leading, var(--text-sm--line-height)); 759 + } 760 + .text-sm\! { 761 + font-size: var(--text-sm) !important; 762 + line-height: var(--tw-leading, var(--text-sm--line-height)) !important; 655 763 } 656 764 .text-xl { 657 765 font-size: var(--text-xl); ··· 691 799 .text-white { 692 800 color: var(--color-white); 693 801 } 802 + .text-zinc-50 { 803 + color: var(--color-zinc-50); 804 + } 805 + .text-zinc-500 { 806 + color: var(--color-zinc-500); 807 + } 694 808 .text-zinc-600 { 695 809 color: var(--color-zinc-600); 696 810 } 697 811 .text-zinc-700 { 698 812 color: var(--color-zinc-700); 699 813 } 814 + .text-zinc-800 { 815 + color: var(--color-zinc-800); 816 + } 700 817 .text-zinc-900 { 701 818 color: var(--color-zinc-900); 702 819 } ··· 706 823 .lowercase { 707 824 text-transform: lowercase; 708 825 } 826 + .underline { 827 + text-decoration-line: underline; 828 + } 829 + .accent-sky-600 { 830 + accent-color: var(--color-sky-600); 831 + } 832 + .shadow { 833 + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); 834 + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); 835 + } 709 836 .ring-sky-500 { 710 837 --tw-ring-color: var(--color-sky-500); 711 838 } 839 + .invert { 840 + --tw-invert: invert(100%); 841 + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); 842 + } 843 + .filter { 844 + filter: var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,); 845 + } 846 + .transition { 847 + transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, visibility, content-visibility, overlay, pointer-events; 848 + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); 849 + transition-duration: var(--tw-duration, var(--default-transition-duration)); 850 + } 851 + .ease-in { 852 + --tw-ease: var(--ease-in); 853 + transition-timing-function: var(--ease-in); 854 + } 712 855 .group-data-\[added\=true\]\:block { 713 856 &:is(:where(.group)[data-added="true"] *) { 714 857 display: block; 858 + } 859 + } 860 + .hover\:no-underline { 861 + &:hover { 862 + @media (hover: hover) { 863 + text-decoration-line: none; 864 + } 715 865 } 716 866 } 717 867 .hover\:underline { ··· 750 900 .sm\:fixed { 751 901 @media (width >= 40rem) { 752 902 position: fixed; 903 + } 904 + } 905 + .sm\:static { 906 + @media (width >= 40rem) { 907 + position: static; 753 908 } 754 909 } 755 910 .sm\:top-0 { ··· 777 932 left: calc(var(--spacing) * 0); 778 933 } 779 934 } 935 + .sm\:mt-0 { 936 + @media (width >= 40rem) { 937 + margin-top: calc(var(--spacing) * 0); 938 + } 939 + } 940 + .sm\:ml-1 { 941 + @media (width >= 40rem) { 942 + margin-left: calc(var(--spacing) * 1); 943 + } 944 + } 945 + .sm\:ml-2 { 946 + @media (width >= 40rem) { 947 + margin-left: calc(var(--spacing) * 2); 948 + } 949 + } 950 + .sm\:block { 951 + @media (width >= 40rem) { 952 + display: block; 953 + } 954 + } 955 + .sm\:hidden { 956 + @media (width >= 40rem) { 957 + display: none; 958 + } 959 + } 780 960 .sm\:h-screen { 781 961 @media (width >= 40rem) { 782 962 height: 100vh; ··· 802 982 max-width: 500px; 803 983 } 804 984 } 985 + .sm\:min-w-\[120px\] { 986 + @media (width >= 40rem) { 987 + min-width: 120px; 988 + } 989 + } 805 990 .sm\:grid-cols-3 { 806 991 @media (width >= 40rem) { 807 992 grid-template-columns: repeat(3, minmax(0, 1fr)); ··· 825 1010 .sm\:items-center { 826 1011 @media (width >= 40rem) { 827 1012 align-items: center; 1013 + } 1014 + } 1015 + .sm\:items-end { 1016 + @media (width >= 40rem) { 1017 + align-items: flex-end; 828 1018 } 829 1019 } 830 1020 .sm\:justify-between { ··· 837 1027 justify-content: flex-end; 838 1028 } 839 1029 } 1030 + .sm\:gap-0 { 1031 + @media (width >= 40rem) { 1032 + gap: calc(var(--spacing) * 0); 1033 + } 1034 + } 840 1035 .sm\:space-x-2 { 841 1036 @media (width >= 40rem) { 842 1037 :where(& > :not(:last-child)) { ··· 869 1064 } 870 1065 } 871 1066 } 1067 + .dark\:border-zinc-700 { 1068 + @media (prefers-color-scheme: dark) { 1069 + border-color: var(--color-zinc-700); 1070 + } 1071 + } 872 1072 .dark\:border-zinc-800 { 873 1073 @media (prefers-color-scheme: dark) { 874 1074 border-color: var(--color-zinc-800); ··· 902 1102 .dark\:text-zinc-50 { 903 1103 @media (prefers-color-scheme: dark) { 904 1104 color: var(--color-zinc-50); 1105 + } 1106 + } 1107 + .dark\:text-zinc-100 { 1108 + @media (prefers-color-scheme: dark) { 1109 + color: var(--color-zinc-100); 905 1110 } 906 1111 } 907 1112 .dark\:text-zinc-300 { ··· 1024 1229 inherits: false; 1025 1230 initial-value: 0 0 #0000; 1026 1231 } 1232 + @property --tw-blur { 1233 + syntax: "*"; 1234 + inherits: false; 1235 + } 1236 + @property --tw-brightness { 1237 + syntax: "*"; 1238 + inherits: false; 1239 + } 1240 + @property --tw-contrast { 1241 + syntax: "*"; 1242 + inherits: false; 1243 + } 1244 + @property --tw-grayscale { 1245 + syntax: "*"; 1246 + inherits: false; 1247 + } 1248 + @property --tw-hue-rotate { 1249 + syntax: "*"; 1250 + inherits: false; 1251 + } 1252 + @property --tw-invert { 1253 + syntax: "*"; 1254 + inherits: false; 1255 + } 1256 + @property --tw-opacity { 1257 + syntax: "*"; 1258 + inherits: false; 1259 + } 1260 + @property --tw-saturate { 1261 + syntax: "*"; 1262 + inherits: false; 1263 + } 1264 + @property --tw-sepia { 1265 + syntax: "*"; 1266 + inherits: false; 1267 + } 1268 + @property --tw-drop-shadow { 1269 + syntax: "*"; 1270 + inherits: false; 1271 + } 1272 + @property --tw-drop-shadow-color { 1273 + syntax: "*"; 1274 + inherits: false; 1275 + } 1276 + @property --tw-drop-shadow-alpha { 1277 + syntax: "<percentage>"; 1278 + inherits: false; 1279 + initial-value: 100%; 1280 + } 1281 + @property --tw-drop-shadow-size { 1282 + syntax: "*"; 1283 + inherits: false; 1284 + } 1285 + @property --tw-ease { 1286 + syntax: "*"; 1287 + inherits: false; 1288 + } 1027 1289 @layer properties { 1028 1290 @supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) { 1029 1291 *, ::before, ::after, ::backdrop { ··· 1051 1313 --tw-ring-offset-width: 0px; 1052 1314 --tw-ring-offset-color: #fff; 1053 1315 --tw-ring-offset-shadow: 0 0 #0000; 1316 + --tw-blur: initial; 1317 + --tw-brightness: initial; 1318 + --tw-contrast: initial; 1319 + --tw-grayscale: initial; 1320 + --tw-hue-rotate: initial; 1321 + --tw-invert: initial; 1322 + --tw-opacity: initial; 1323 + --tw-saturate: initial; 1324 + --tw-sepia: initial; 1325 + --tw-drop-shadow: initial; 1326 + --tw-drop-shadow-color: initial; 1327 + --tw-drop-shadow-alpha: 100%; 1328 + --tw-drop-shadow-size: initial; 1329 + --tw-ease: initial; 1054 1330 } 1055 1331 } 1056 1332 }
-106
static/upload_page.js
··· 1 - // deno-lint-ignore-file no-window 2 - 3 - async function uploadPhotos(inputElement) { 4 - const fileList = Array.from(inputElement.files); 5 - 6 - if (fileList.length > 10) { 7 - alert("You can only upload 10 photos at a time"); 8 - return; 9 - } 10 - 11 - const preview = document.querySelector("#image-preview"); 12 - 13 - const uploadPromises = fileList.map(async (file) => { 14 - let dataUrl = ""; 15 - let exif; 16 - let resized; 17 - 18 - try { 19 - dataUrl = await Grain.readFileAsDataURL(file); 20 - } catch (err) { 21 - console.error("Error reading file as Data URL:", err); 22 - alert("Error reading file."); 23 - return; 24 - } 25 - 26 - try { 27 - const rawExif = await window.exifr.parse(file, { gps: false }); 28 - exif = normalizeExif(rawExif); 29 - } catch (err) { 30 - console.error("Error reading EXIF data:", err); 31 - } 32 - 33 - try { 34 - resized = await Grain.doResize(dataUrl, { 35 - width: 2000, 36 - height: 2000, 37 - maxSize: 1000 * 1000, // 1MB 38 - mode: "contain", 39 - }); 40 - } catch (err) { 41 - console.error("Error resizing image:", err); 42 - alert("Error resizing image."); 43 - return; 44 - } 45 - 46 - const blob = Grain.dataURLToBlob(resized.path); 47 - 48 - const fd = new FormData(); 49 - fd.append("file", blob, file.name); 50 - fd.append("width", resized.width); 51 - fd.append("height", resized.height); 52 - fd.append("exif", JSON.stringify(exif)); 53 - 54 - try { 55 - const response = await fetch("/actions/photo/upload", { 56 - method: "POST", 57 - body: fd, 58 - }); 59 - 60 - if (!response.ok) { 61 - alert(await response.text()); 62 - return; 63 - } 64 - 65 - const html = await response.text(); 66 - const temp = document.createElement("div"); 67 - temp.innerHTML = html; 68 - preview.insertBefore(temp.firstElementChild, preview.firstChild); 69 - htmx.process(preview); 70 - } catch (err) { 71 - console.error("Error uploading photo:", err); 72 - alert("Error uploading photo"); 73 - } 74 - }); 75 - 76 - await Promise.all(uploadPromises); 77 - inputElement.value = ""; 78 - } 79 - 80 - const SCALE_FACTOR = 1000000; 81 - 82 - function normalizeExif( 83 - exif, 84 - scale = SCALE_FACTOR, 85 - ) { 86 - const normalized = {}; 87 - 88 - for (const [key, value] of Object.entries(exif)) { 89 - const camelKey = key[0].toLowerCase() + key.slice(1); 90 - 91 - if (typeof value === "number") { 92 - normalized[camelKey] = Math.round(value * scale); 93 - } else if (Array.isArray(value)) { 94 - normalized[camelKey] = value.map((v) => 95 - typeof v === "number" ? Math.round(v * scale) : v 96 - ); 97 - } else { 98 - normalized[camelKey] = value; 99 - } 100 - } 101 - 102 - return normalized; 103 - } 104 - 105 - window.Grain = window.Grain || {}; 106 - window.Grain.uploadPhotos = uploadPhotos;