Compare commits

..
51 Commits
Author SHA1 Message Date
raucao f0a19e30b8 Don't load actual map tiles in tests
CI / Lint (pull_request) Successful in 31s
CI / Test (pull_request) Failing after 1m6s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
We don't need remote tiles for testing our functionality
2026-06-06 12:34:59 +04:00
raucao 59bc5ca046 1.24.0
CI / Lint (push) Successful in 31s
CI / Test (push) Successful in 55s
2026-06-06 12:12:06 +04:00
raucao ef4bb8f51a Merge pull request 'Include saved places in search results' (#59) from feature/search_saved_places into master
CI / Lint (push) Successful in 33s
CI / Test (push) Successful in 56s
Reviewed-on: #59
2026-06-06 08:03:32 +00:00
raucao f82a797720 Include list names in search results for saved places
CI / Lint (pull_request) Successful in 32s
CI / Test (pull_request) Successful in 55s
Release Drafter / Update release notes draft (pull_request) Successful in 16s
2026-06-06 12:00:48 +04:00
raucao f9cb22ee0e Include saved places in search results 2026-06-06 11:47:27 +04:00
raucao a77ea0c97d 1.23.0
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 54s
2026-06-05 18:57:28 +04:00
raucao 208b77a294 Merge pull request 'Optionally add suggested tags to place photos' (#58) from feature/photo_tags into master
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 54s
Reviewed-on: #58
2026-06-05 14:51:15 +00:00
raucao ea3e4dd0dc Fix warning when running tests
CI / Lint (pull_request) Successful in 32s
CI / Test (pull_request) Successful in 52s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2026-06-05 18:46:10 +04:00
raucao 2c2a3e2a4c Fix flaky test
CI / Lint (pull_request) Successful in 32s
CI / Test (pull_request) Successful in 55s
Wait for specific actions/elements
2026-06-05 18:37:40 +04:00
raucao d266bb92bd Use data attribute to determine current gallery photo in test 2026-06-05 18:33:08 +04:00
raucao 200100686d Optionally add tag to place photo
CI / Test (pull_request) Failing after 56s
CI / Lint (pull_request) Successful in 52s
2026-06-05 18:24:36 +04:00
raucao 70d2fe1c6c 1.22.0
CI / Lint (push) Successful in 31s
CI / Test (push) Successful in 54s
2026-05-13 12:28:39 +02:00
raucao 6329ad986d Fix unnecessary browser console warnings 2026-05-13 12:27:16 +02:00
raucao bcfa81494e Merge pull request 'Delete own photos, fetch/sync remote deletions' (#55) from feature/delete_own_photos into master
CI / Lint (push) Successful in 31s
CI / Test (push) Successful in 1m0s
Reviewed-on: #55
2026-05-13 10:08:22 +00:00
raucao bc42694707 Move form group to different place
CI / Lint (pull_request) Successful in 30s
CI / Test (pull_request) Successful in 1m0s
Release Drafter / Update release notes draft (pull_request) Successful in 5s
2026-05-13 12:03:43 +02:00
raucao 4390b7d699 Add settings for experimental features
CI / Lint (pull_request) Successful in 31s
CI / Test (pull_request) Successful in 59s
2026-05-13 11:57:41 +02:00
raucao 7bab8dfa09 Fix arrow key scrolling in photo gallery 2026-05-13 11:02:19 +02:00
raucao 51c9555273 Fix flaky photo gallery carousel tests and refactor overlays
* Fixed a race condition in `photo-carousel` where programmatic scrolling
  (e.g., keyboard navigation) would conflict with `IntersectionObserver`
  callbacks, causing the current photo to revert mid-scroll. Added an
  `isProgrammaticScroll` flag to temporarily suppress observer updates
  during these scrolls.
* Added explicit timeouts in `photo-gallery-test.gjs` to allow the carousel
  animations to settle between keyboard events.
* Refactored `Modal` and `PhotoGallery` components to use `{{in-element}}`
  to render their contents into a top-level `#modal-portal` div. This prevents
  z-index and overflow clipping issues.
* Updated `index.html` to include the `#modal-portal` div.
2026-05-13 10:31:45 +02:00
raucao 632efeeab5 1.21.3
CI / Lint (push) Successful in 34s
CI / Test (push) Successful in 57s
2026-05-08 11:43:57 +02:00
raucao 14827fce3e Delete own photos
CI / Lint (pull_request) Successful in 30s
CI / Test (pull_request) Failing after 57s
2026-05-05 12:07:33 +02:00
raucao deeea9961f Merge pull request 'Add photo actions, fix portrait photos using thumb URLs' (#54) from feature/photo_actions into master
CI / Lint (push) Successful in 31s
CI / Test (push) Successful in 56s
Reviewed-on: #54
2026-05-05 09:49:25 +00:00
raucao 7a109c9ba5 Fix portrait photos using thumb for full size variant
CI / Lint (pull_request) Successful in 32s
CI / Test (pull_request) Successful in 55s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2026-05-05 10:16:12 +02:00
raucao 10aae3c9b3 Add photo action for copying event ID 2026-05-05 09:56:12 +02:00
raucao b492e2aa89 Add dropdown component, photo actions menu 2026-05-05 09:49:20 +02:00
raucao 4c4a53ae42 1.21.2
CI / Lint (push) Successful in 30s
CI / Test (push) Successful in 55s
2026-05-05 07:18:37 +02:00
raucao a240a5d199 Serve dev on all IPs 2026-05-05 07:17:30 +02:00
raucao 0332cf4c3c Merge pull request 'Hide quick-search pills on low zoom levels' (#53) from ui/pills_on_zoom into master
CI / Test (push) Successful in 54s
CI / Lint (push) Successful in 30s
Reviewed-on: #53
2026-05-05 05:14:54 +00:00
raucao 59c447fe1f Hide quick-search pills on low zoom levels
CI / Lint (pull_request) Successful in 30s
CI / Test (pull_request) Successful in 56s
Release Drafter / Update release notes draft (pull_request) Successful in 7s
2026-05-05 07:10:28 +02:00
raucao 1140ecfe41 Add buttons for opening signer app, copying connect link
CI / Lint (push) Successful in 49s
CI / Test (push) Successful in 59s
2026-05-05 07:02:08 +02:00
raucao 60936ed2f5 1.21.1
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 56s
2026-04-27 21:51:55 +01:00
raucao ca82a029bc Fix app menu section layout 2026-04-27 21:50:47 +01:00
raucao 0630aed73d Fix modals 2026-04-27 21:50:36 +01:00
raucao f27a636529 1.21.0
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 58s
2026-04-27 21:26:23 +01:00
raucao 995ae95b09 Merge pull request 'Add full-size photo gallery' (#52) from feature/photo-gallery into master
CI / Lint (push) Successful in 31s
CI / Test (push) Successful in 58s
Reviewed-on: #52
2026-04-27 20:24:12 +00:00
raucao 0fb320d996 Fix test and linter error
CI / Lint (pull_request) Successful in 31s
CI / Test (pull_request) Successful in 57s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2026-04-27 21:20:39 +01:00
raucao 4f4ca827b1 Refactor carousel and gallery to share the carousel component
And make the gallery awesome
2026-04-27 21:12:46 +01:00
raucao c1d3f25d50 WIP Add basic photo gallery 2026-04-27 16:45:49 +01:00
raucao 2087cfc4f7 Merge pull request 'Various search UI improvements' (#51) from ui/search into master
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 1m0s
Reviewed-on: #51
2026-04-27 14:50:02 +00:00
raucao 8572032481 Eliminate race condition in tests
CI / Lint (pull_request) Successful in 33s
CI / Test (pull_request) Successful in 1m0s
Release Drafter / Update release notes draft (pull_request) Successful in 4s
2026-04-27 15:47:23 +01:00
raucao b4c3f5c88d Improve map centering on mobile
CI / Lint (pull_request) Successful in 33s
CI / Test (pull_request) Failing after 1m1s
2026-04-27 15:37:13 +01:00
raucao cff19980d5 Refactor search route/loading
* Fetch results asynchronously after app launch
* Hide sidebar and search results when new search is issued
2026-04-27 15:18:17 +01:00
raucao cf251f702b Prevent zoom when opening place from search results 2026-04-27 14:44:35 +01:00
raucao d2eb888dcf Keep search results, only hide sidebar when closed 2026-04-27 14:37:19 +01:00
raucao a0b4a4b3f3 Show toast notification when adding RS account 2026-04-27 12:47:33 +01:00
raucao cb3ee48909 Update rs.js
CI / Lint (push) Successful in 32s
CI / Test (push) Successful in 1m0s
2026-04-27 10:32:51 +01:00
raucao 1d022b21bd 1.20.5
CI / Lint (push) Successful in 31s
CI / Test (push) Successful in 56s
2026-04-25 11:10:44 +01:00
raucao 3e831a7686 Enable relay pings
This should re-establish relay connections when PWAs are being woken up
after background sleep
2026-04-25 11:08:55 +01:00
raucao 2943125dbd 1.20.4
CI / Lint (push) Successful in 31s
CI / Test (push) Successful in 57s
2026-04-25 10:55:20 +01:00
raucao a32ad7572b Add more marker icons
CI / Lint (push) Successful in 32s
CI / Test (push) Has been cancelled
2026-04-25 10:53:53 +01:00
raucao a1b3957c83 Fix photo icon in map markers 2026-04-25 10:42:05 +01:00
raucao 9f2f233c22 Adjust JPEG quality for large photos 2026-04-25 10:41:39 +01:00
65 changed files with 2858 additions and 647 deletions

No files matched your search

+7 -1
View File
@@ -15,6 +15,7 @@ export default class AppHeaderComponent extends Component {
@service settings; @service settings;
@service nostrAuth; @service nostrAuth;
@service nostrData; @service nostrData;
@service mapUi;
@tracked isUserMenuOpen = false; @tracked isUserMenuOpen = false;
@tracked searchQuery = ''; @tracked searchQuery = '';
@@ -22,6 +23,11 @@ export default class AppHeaderComponent extends Component {
return !!this.searchQuery; return !!this.searchQuery;
} }
get showQuickSearch() {
const zoom = this.mapUi.currentZoom ?? 13;
return this.settings.showQuickSearchButtons && zoom >= 12;
}
@action @action
toggleUserMenu() { toggleUserMenu() {
this.isUserMenuOpen = !this.isUserMenuOpen; this.isUserMenuOpen = !this.isUserMenuOpen;
@@ -54,7 +60,7 @@ export default class AppHeaderComponent extends Component {
/> />
</div> </div>
{{#if this.settings.showQuickSearchButtons}} {{#if this.showQuickSearch}}
<div class="header-center {{if this.hasQuery 'searching'}}"> <div class="header-center {{if this.hasQuery 'searching'}}">
<CategoryChips @onSelect={{this.handleChipSelect}} /> <CategoryChips @onSelect={{this.handleChipSelect}} />
</div> </div>
+2
View File
@@ -6,6 +6,7 @@ import Icon from '#components/icon';
import AppMenuSettingsMapUi from './settings/map-ui'; import AppMenuSettingsMapUi from './settings/map-ui';
import AppMenuSettingsApis from './settings/apis'; import AppMenuSettingsApis from './settings/apis';
import AppMenuSettingsNostr from './settings/nostr'; import AppMenuSettingsNostr from './settings/nostr';
import AppMenuSettingsExperimental from './settings/experimental';
export default class AppMenuSettings extends Component { export default class AppMenuSettings extends Component {
@service settings; @service settings;
@@ -35,6 +36,7 @@ export default class AppMenuSettings extends Component {
<AppMenuSettingsMapUi @onChange={{this.updateSetting}} /> <AppMenuSettingsMapUi @onChange={{this.updateSetting}} />
<AppMenuSettingsApis @onChange={{this.updateSetting}} /> <AppMenuSettingsApis @onChange={{this.updateSetting}} />
<AppMenuSettingsNostr @onChange={{this.updateSetting}} /> <AppMenuSettingsNostr @onChange={{this.updateSetting}} />
<AppMenuSettingsExperimental @onChange={{this.updateSetting}} />
</section> </section>
</div> </div>
</template> </template>
+1 -1
View File
@@ -15,7 +15,7 @@ export default class AppMenuSettingsApis extends Component {
<Icon @name="server" @size={{20}} /> <Icon @name="server" @size={{20}} />
<span>API Providers</span> <span>API Providers</span>
</summary> </summary>
<div class="details-content"> <div class="details-content form-layout">
<div class="form-group"> <div class="form-group">
<label for="overpass-api">Overpass API Provider</label> <label for="overpass-api">Overpass API Provider</label>
<select <select
@@ -0,0 +1,49 @@
import Component from '@glimmer/component';
import { on } from '@ember/modifier';
import { service } from '@ember/service';
import { fn } from '@ember/helper';
import Icon from '#components/icon';
export default class AppMenuSettingsExperimental extends Component {
@service settings;
<template>
{{! template-lint-disable no-nested-interactive }}
<details>
<summary>
<Icon @name="alert-triangle" @size={{20}} />
<span>Experimental</span>
</summary>
<div class="details-content form-layout">
<div class="form-group">
<label for="experimental-enable-photo-deletion">Enable photo deletion
(own photos)</label>
<select
id="experimental-enable-photo-deletion"
class="form-control"
{{on "change" (fn @onChange "experimentalEnablePhotoDeletion")}}
>
<option
value="true"
selected={{if
this.settings.experimentalEnablePhotoDeletion
"selected"
}}
>
On
</option>
<option
value="false"
selected={{unless
this.settings.experimentalEnablePhotoDeletion
"selected"
}}
>
Off
</option>
</select>
</div>
</div>
</details>
</template>
}
+1 -1
View File
@@ -14,7 +14,7 @@ export default class AppMenuSettingsMapUi extends Component {
<Icon @name="map" @size={{20}} /> <Icon @name="map" @size={{20}} />
<span>Map & UI</span> <span>Map & UI</span>
</summary> </summary>
<div class="details-content"> <div class="details-content form-layout">
<div class="form-group"> <div class="form-group">
<label for="show-quick-search">Quick search buttons visible</label> <label for="show-quick-search">Quick search buttons visible</label>
<select <select
+27 -27
View File
@@ -108,33 +108,7 @@ export default class AppMenuSettingsNostr extends Component {
<Icon @name="zap" @size={{20}} /> <Icon @name="zap" @size={{20}} />
<span>Nostr</span> <span>Nostr</span>
</summary> </summary>
<div class="details-content"> <div class="details-content form-layout">
<div class="form-group">
<label for="nostr-photo-fallback-uploads">Upload photos to fallback
servers</label>
<select
id="nostr-photo-fallback-uploads"
class="form-control"
{{on "change" (fn @onChange "nostrPhotoFallbackUploads")}}
>
<option
value="true"
selected={{if this.settings.nostrPhotoFallbackUploads "selected"}}
>
Yes
</option>
<option
value="false"
selected={{unless
this.settings.nostrPhotoFallbackUploads
"selected"
}}
>
No
</option>
</select>
</div>
<div class="form-group"> <div class="form-group">
<label for="new-read-relay">Read Relays</label> <label for="new-read-relay">Read Relays</label>
<ul class="relay-list"> <ul class="relay-list">
@@ -225,6 +199,32 @@ export default class AppMenuSettingsNostr extends Component {
{{/if}} {{/if}}
</div> </div>
<div class="form-group">
<label for="nostr-photo-fallback-uploads">Upload photos to fallback
servers</label>
<select
id="nostr-photo-fallback-uploads"
class="form-control"
{{on "change" (fn @onChange "nostrPhotoFallbackUploads")}}
>
<option
value="true"
selected={{if this.settings.nostrPhotoFallbackUploads "selected"}}
>
Yes
</option>
<option
value="false"
selected={{unless
this.settings.nostrPhotoFallbackUploads
"selected"
}}
>
No
</option>
</select>
</div>
<div class="form-group"> <div class="form-group">
<label>Cached data</label> <label>Cached data</label>
<button <button
+53
View File
@@ -0,0 +1,53 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { on } from '@ember/modifier';
import Icon from '#components/icon';
export default class DropdownMenu extends Component {
@tracked isOpen = false;
@action
toggleMenu(e) {
e?.stopPropagation();
this.isOpen = !this.isOpen;
}
@action
closeMenu(e) {
e?.stopPropagation();
this.isOpen = false;
}
get triggerIcon() {
return this.args.triggerIcon || 'more-vertical';
}
<template>
<div class="dropdown-menu-container">
<button
class="dropdown-trigger-btn btn-press"
type="button"
title={{@triggerTitle}}
{{on "click" this.toggleMenu}}
>
<Icon
@name={{this.triggerIcon}}
@size={{@iconSize}}
@color={{@iconColor}}
/>
</button>
{{#if this.isOpen}}
<div class="dropdown-popover {{@popoverClass}}">
{{yield this.closeMenu}}
</div>
<div
class="menu-backdrop"
{{on "click" this.closeMenu}}
role="button"
></div>
{{/if}}
</div>
</template>
}
+42 -13
View File
@@ -20,6 +20,7 @@ import { Style, Circle, Fill, Stroke, Icon } from 'ol/style.js';
import { apply } from 'ol-mapbox-style'; import { apply } from 'ol-mapbox-style';
import { getIcon } from '../utils/icons'; import { getIcon } from '../utils/icons';
import { getIconNameForTags } from '../utils/osm-icons'; import { getIconNameForTags } from '../utils/osm-icons';
import config from 'marco/config/environment';
export default class MapComponent extends Component { export default class MapComponent extends Component {
@service osm; @service osm;
@@ -284,10 +285,28 @@ export default class MapComponent extends Component {
// Initialize the UI service with the map center // Initialize the UI service with the map center
const initialCenter = toLonLat(view.getCenter()); const initialCenter = toLonLat(view.getCenter());
this.mapUi.updateCenter(initialCenter[1], initialCenter[0]); this.mapUi.updateCenter(initialCenter[1], initialCenter[0]);
this.mapUi.updateZoom(view.getZoom());
apply(this.mapInstance, 'https://tiles.openfreemap.org/styles/liberty', { if (config.environment === 'test') {
webfonts: 'data:text/css,', apply(this.mapInstance, {
}); version: 8,
name: 'Test Style',
sources: {},
layers: [
{
id: 'background',
type: 'background',
paint: {
'background-color': '#f8f4f0',
},
},
],
});
} else {
apply(this.mapInstance, 'https://tiles.openfreemap.org/styles/liberty', {
webfonts: 'data:text/css,',
});
}
this.searchOverlayElement = document.createElement('div'); this.searchOverlayElement = document.createElement('div');
this.searchOverlayElement.className = 'search-pulse'; this.searchOverlayElement.className = 'search-pulse';
@@ -782,14 +801,19 @@ export default class MapComponent extends Component {
// Check if mobile (width <= 768px matches CSS) // Check if mobile (width <= 768px matches CSS)
if (size[0] <= 768) { if (size[0] <= 768) {
// On mobile, the bottom 50% is covered by the sheet. // On mobile, the bottom 50% is covered by the sheet.
// We want the pin to be in the center of the TOP 50% (visible area). // We want the pin to be in the center of the TOP 50% (visible area), minus the header.
// That means the pin should be at y = height * 0.25 (25% down from top).
// The map center is at y = height * 0.50.
// So the pin is "above" the center by 25% of the height in pixels.
// To put the pin there, the map center needs to be "below" the pin by that amount.
const height = size[1]; const height = size[1];
const offsetPixels = height * 0.25; // Distance from desired pin pos to map center const headerEl = document.querySelector('.app-header');
const headerHeight = headerEl ? headerEl.offsetHeight : 60;
// Visible area is from headerHeight to height / 2 (bottom sheet covers bottom 50%)
const visibleCenterY = headerHeight + (height / 2 - headerHeight) / 2;
// The map center is at y = height * 0.50.
// So the pin is "above" the center by (height/2 - visibleCenterY) pixels.
// To put the pin there, the map center needs to be "below" the pin by that amount.
const offsetPixels = height / 2 - visibleCenterY; // Distance from desired pin pos to map center
const offsetMapUnits = offsetPixels * resolution; const offsetMapUnits = offsetPixels * resolution;
// Shift center SOUTH (decrease Y). // Shift center SOUTH (decrease Y).
@@ -849,6 +873,9 @@ export default class MapComponent extends Component {
let targetPixelY = pixel[1]; let targetPixelY = pixel[1];
let needsPan = false; let needsPan = false;
const headerEl = document.querySelector('.app-header');
const headerHeight = headerEl ? headerEl.offsetHeight : 60;
// 1. Mobile Bottom Sheet Logic (Screen <= 768px) // 1. Mobile Bottom Sheet Logic (Screen <= 768px)
if (size[0] <= 768) { if (size[0] <= 768) {
const height = size[1]; const height = size[1];
@@ -856,7 +883,7 @@ export default class MapComponent extends Component {
// If in bottom half // If in bottom half
if (pixel[1] > splitPoint) { if (pixel[1] > splitPoint) {
targetPixelY = height * 0.25; // Target: Center of top half targetPixelY = headerHeight + (height / 2 - headerHeight) / 2; // Target: Center of visible area above bottom sheet
needsPan = true; needsPan = true;
} }
} }
@@ -877,11 +904,10 @@ export default class MapComponent extends Component {
// 3. Header Logic (Any screen size) // 3. Header Logic (Any screen size)
// Check if the (potentially new) target Y is under the header // Check if the (potentially new) target Y is under the header
const headerHeight = 60; const minTopDistance = headerHeight + 20; // Provide some padding
const minTopDistance = headerHeight + 20; // 80px
if (targetPixelY < minTopDistance) { if (targetPixelY < minTopDistance) {
targetPixelY = minTopDistance + 30; // Move it to ~110px, clear of header targetPixelY = minTopDistance + 30; // Move it clear of header
needsPan = true; needsPan = true;
} }
@@ -1039,6 +1065,7 @@ export default class MapComponent extends Component {
const view = this.mapInstance.getView(); const view = this.mapInstance.getView();
const center = toLonLat(view.getCenter()); const center = toLonLat(view.getCenter());
this.mapUi.updateCenter(center[1], center[0]); this.mapUi.updateCenter(center[1], center[0]);
this.mapUi.updateZoom(view.getZoom());
// If in creation mode, update the coordinates in the service AND the URL // If in creation mode, update the coordinates in the service AND the URL
if (this.mapUi.isCreating) { if (this.mapUi.isCreating) {
@@ -1144,6 +1171,8 @@ export default class MapComponent extends Component {
this.mapUi.returnToSearch = true; this.mapUi.returnToSearch = true;
} }
this.mapUi.preventNextZoom = true; this.mapUi.preventNextZoom = true;
this.mapUi.selectPlace(place, { preventZoom: true });
this.mapUi.showSidebar();
this.router.transitionTo('place', place); this.router.transitionTo('place', place);
}; };
+47 -21
View File
@@ -1,9 +1,39 @@
import Component from '@glimmer/component'; import Component from '@glimmer/component';
import { action } from '@ember/object'; import { action } from '@ember/object';
import { on } from '@ember/modifier'; import { on } from '@ember/modifier';
import config from 'marco/config/environment';
import Icon from './icon'; import Icon from './icon';
const ModalContent = <template>
<div class="modal-overlay" role="dialog" tabindex="-1" {{on "click" @close}}>
<div
class="modal-content"
role="document"
tabindex="0"
{{on "click" @stopProp}}
>
<button
type="button"
class="close-modal-btn btn-text {{if @disableClose 'disabled'}}"
disabled={{@disableClose}}
{{on "click" @close}}
>
<Icon @name="x" @size={{24}} @color="currentColor" />
</button>
{{yield}}
</div>
</div>
</template>;
export default class Modal extends Component { export default class Modal extends Component {
get isTesting() {
return config.environment === 'test';
}
get destinationElement() {
return document.getElementById('modal-portal') || document.body;
}
@action @action
stopProp(e) { stopProp(e) {
e.stopPropagation(); e.stopPropagation();
@@ -18,28 +48,24 @@ export default class Modal extends Component {
} }
<template> <template>
<div {{#if this.isTesting}}
class="modal-overlay" <ModalContent
role="dialog" @close={{this.close}}
tabindex="-1" @stopProp={{this.stopProp}}
{{on "click" this.close}} @disableClose={{@disableClose}}
>
<div
class="modal-content"
role="document"
tabindex="0"
{{on "click" this.stopProp}}
> >
<button
type="button"
class="close-modal-btn btn-text {{if @disableClose 'disabled'}}"
disabled={{@disableClose}}
{{on "click" this.close}}
>
<Icon @name="x" @size={{24}} @color="currentColor" />
</button>
{{yield}} {{yield}}
</div> </ModalContent>
</div> {{else}}
{{#in-element this.destinationElement}}
<ModalContent
@close={{this.close}}
@stopProp={{this.stopProp}}
@disableClose={{@disableClose}}
>
{{yield}}
</ModalContent>
{{/in-element}}
{{/if}}
</template> </template>
} }
+48 -3
View File
@@ -41,6 +41,40 @@ export default class NostrConnectComponent extends Component {
} }
} }
@action
async copyConnectUri() {
const text = this.nostrAuth.connectUri;
try {
if (navigator.clipboard && window.isSecureContext) {
await navigator.clipboard.writeText(text);
} else {
const textArea = document.createElement('textarea');
textArea.value = text;
textArea.style.position = 'fixed';
textArea.style.top = '0';
textArea.style.left = '0';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const successful = document.execCommand('copy');
document.body.removeChild(textArea);
if (!successful) {
throw new Error('Fallback copy failed');
}
}
this.toast.show('Connection link copied to clipboard');
} catch (err) {
console.error('Failed to copy text: ', err);
alert('Failed to copy link');
}
}
<template> <template>
<div class="nostr-connect-modal"> <div class="nostr-connect-modal">
<h2>Connect with Nostr</h2> <h2>Connect with Nostr</h2>
@@ -59,7 +93,7 @@ export default class NostrConnectComponent extends Component {
class="btn btn-outline" class="btn btn-outline"
type="button" type="button"
disabled disabled
title="No Nostr extension found in your browser." title="No Nostr extension found in your browser"
> >
Browser Extension (Not Found) Browser Extension (Not Found)
</button> </button>
@@ -79,9 +113,20 @@ export default class NostrConnectComponent extends Component {
{{#if this.nostrAuth.isMobile}} {{#if this.nostrAuth.isMobile}}
<p>Waiting for you to approve the connection in your mobile signer <p>Waiting for you to approve the connection in your mobile signer
app...</p> app...</p>
<div class="mobile-connect-actions">
<a href={{this.nostrAuth.connectUri}} class="btn btn-primary">
Open Signer App
</a>
<button
class="btn btn-outline"
type="button"
{{on "click" this.copyConnectUri}}
>
Copy Connection Link
</button>
</div>
{{else}} {{else}}
<p>Scan this QR code with a compatible Nostr signer app (like <p>Scan this QR code with a Nostr signer app (like Amber):</p>
Amber):</p>
<div class="qr-code-container"> <div class="qr-code-container">
<canvas {{qrCode this.nostrAuth.connectUri}}></canvas> <canvas {{qrCode this.nostrAuth.connectUri}}></canvas>
</div> </div>
+317
View File
@@ -0,0 +1,317 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import { fn } from '@ember/helper';
import { and, eq } from 'ember-truth-helpers';
import Blurhash from './blurhash';
import Icon from './icon';
import fadeInImage from '../modifiers/fade-in-image';
import { on } from '@ember/modifier';
import { modifier } from 'ember-modifier';
import config from 'marco/config/environment';
export default class PhotoCarousel extends Component {
@tracked canScrollLeft = false;
@tracked canScrollRight = false;
internalEventId = null;
carouselElement = null;
get photos() {
return this.args.photos || [];
}
get showChevrons() {
// Only show chevrons if there's more than one photo
return this.photos.length > 1;
}
get cannotScrollLeft() {
return !this.canScrollLeft;
}
get cannotScrollRight() {
return !this.canScrollRight;
}
get isGalleryMain() {
return this.args.variant === 'gallery-main';
}
get isGalleryThumbnails() {
return this.args.variant === 'gallery-thumbnails';
}
get variantClass() {
return this.args.variant || 'inline';
}
lastResetKey = null;
resetScrollPosition = modifier((element, [resetKey]) => {
if (resetKey !== undefined && resetKey !== this.lastResetKey) {
this.lastResetKey = resetKey;
element.scrollLeft = 0;
setTimeout(() => this.updateScrollState(), 50);
}
});
isProgrammaticScroll = false;
scrollToNewPhoto = modifier((element, [eventId]) => {
if (eventId && eventId !== this.lastEventId) {
const isInitial = !this.lastEventId;
this.lastEventId = eventId;
// Prevent feedback loop if this carousel initiated the change
if (this.internalEventId === eventId) {
return;
}
this.internalEventId = eventId;
this.isProgrammaticScroll = true;
const scrollAction = () => {
const targetSlide = element.querySelector(
`[data-event-id="${eventId}"]`
);
if (targetSlide) {
if (isInitial) {
const originalScrollBehavior = element.style.scrollBehavior;
element.style.scrollBehavior = 'auto';
element.scrollLeft = targetSlide.offsetLeft;
// Restore smooth scroll after the jump
setTimeout(() => {
element.style.scrollBehavior = originalScrollBehavior;
this.isProgrammaticScroll = false;
}, 50);
} else {
// Use native CSS smooth scrolling for subsequent clicks
element.scrollLeft = targetSlide.offsetLeft;
// Clear programmatic scroll flag after a delay to let scroll finish
setTimeout(() => {
this.isProgrammaticScroll = false;
}, 500);
}
} else {
this.isProgrammaticScroll = false;
}
};
if (isInitial) {
// Execute immediately for the first render to prevent flash
scrollAction();
} else {
// Allow DOM to update first for subsequent clicks
setTimeout(scrollAction, 100);
}
}
});
setupCarousel = modifier((element) => {
this.carouselElement = element;
// Defer the initial calculation slightly to ensure CSS and images have applied
setTimeout(() => {
this.updateScrollState();
}, 50);
let resizeObserver;
if (window.ResizeObserver) {
resizeObserver = new ResizeObserver(() => this.updateScrollState());
resizeObserver.observe(element);
}
let intersectionObserver;
if (
this.args.onVisiblePhotoChange &&
window.IntersectionObserver &&
config.environment !== 'test'
) {
// Set up intersection observer to track which photo is currently "most" visible
intersectionObserver = new IntersectionObserver(
(entries) => {
if (this.isProgrammaticScroll) return;
for (let entry of entries) {
if (entry.isIntersecting && entry.intersectionRatio >= 0.5) {
const eventId = entry.target.dataset.eventId;
this.internalEventId = eventId;
const photo = this.photos.find((p) => p.eventId === eventId);
if (photo) {
this.args.onVisiblePhotoChange(photo);
}
}
}
},
{
root: element,
threshold: 0.5,
}
);
const slides = element.querySelectorAll('.carousel-slide');
slides.forEach((slide) => intersectionObserver.observe(slide));
}
return () => {
if (resizeObserver) {
resizeObserver.unobserve(element);
}
if (intersectionObserver) {
intersectionObserver.disconnect();
}
};
});
@action
updateScrollState() {
if (!this.carouselElement) return;
const { scrollLeft, scrollWidth, clientWidth } = this.carouselElement;
// tolerance of 1px for floating point rounding issues
this.canScrollLeft = scrollLeft > 1;
this.canScrollRight = scrollLeft + clientWidth < scrollWidth - 1;
}
@action
scrollLeft() {
if (!this.carouselElement) return;
this.carouselElement.scrollBy({
left: -this.carouselElement.clientWidth,
behavior: 'smooth',
});
}
@action
scrollRight() {
if (!this.carouselElement) return;
this.carouselElement.scrollBy({
left: this.carouselElement.clientWidth,
behavior: 'smooth',
});
}
@action
handlePhotoClick(photo) {
if (this.args.onPhotoClick) {
this.args.onPhotoClick(photo);
}
}
<template>
{{#if this.photos.length}}
<div class="photo-carousel {{this.variantClass}}">
<div
class="photo-carousel-track"
{{this.setupCarousel}}
{{this.resetScrollPosition @resetKey}}
{{this.scrollToNewPhoto @scrollToEventId}}
{{on "scroll" this.updateScrollState}}
>
{{#each this.photos as |photo|}}
{{! template-lint-disable no-inline-styles no-invalid-interactive }}
<div
class="carousel-slide
{{if photo.isLandscape 'landscape' 'portrait'}}
{{if
(and @scrollToEventId (eq photo.eventId @scrollToEventId))
'active'
}}"
style={{photo.style}}
data-event-id={{photo.eventId}}
{{on "click" (fn this.handlePhotoClick photo)}}
>
{{#if photo.blurhash}}
<Blurhash
@hash={{photo.blurhash}}
@width={{32}}
@height={{18}}
class="place-header-photo-blur"
/>
{{/if}}
{{#if this.isGalleryMain}}
<img
data-src={{photo.url}}
class="place-header-photo
{{if photo.isLandscape 'landscape' 'portrait'}}"
alt={{@name}}
{{fadeInImage photo.url}}
/>
{{else if this.isGalleryThumbnails}}
<img
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
class="place-header-photo
{{if photo.isLandscape 'landscape' 'portrait'}}"
alt={{@name}}
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
/>
{{else}}
{{#if photo.isLandscape}}
<picture>
{{#if photo.thumbUrl}}
<source
media="(max-width: 768px)"
data-srcset={{photo.thumbUrl}}
/>
{{/if}}
<img
data-src={{photo.url}}
class="place-header-photo landscape"
alt={{@name}}
{{fadeInImage photo.url}}
/>
</picture>
{{else}}
{{! Portrait uses thumb everywhere if available }}
<img
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
class="place-header-photo portrait"
alt={{@name}}
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
/>
{{/if}}
{{/if}}
</div>
{{/each}}
<div class="carousel-placeholder"></div>
</div>
{{#if this.showChevrons}}
<button
type="button"
class="carousel-nav-btn prev
{{if this.cannotScrollLeft 'disabled'}}"
{{on "click" this.scrollLeft}}
disabled={{this.cannotScrollLeft}}
aria-label="Previous photo"
>
<Icon
@name="chevron-left"
@color="currentColor"
@size={{if (eq @variant "gallery-main") 24 16}}
/>
</button>
<button
type="button"
class="carousel-nav-btn next
{{if this.cannotScrollRight 'disabled'}}"
{{on "click" this.scrollRight}}
disabled={{this.cannotScrollRight}}
aria-label="Next photo"
>
<Icon
@name="chevron-right"
@color="currentColor"
@size={{if (eq @variant "gallery-main") 24 16}}
/>
</button>
{{/if}}
</div>
{{/if}}
</template>
}
+295
View File
@@ -0,0 +1,295 @@
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { on } from '@ember/modifier';
import { fn } from '@ember/helper';
import { service } from '@ember/service';
import { modifier } from 'ember-modifier';
import { task } from 'ember-concurrency';
import { EventFactory } from 'applesauce-factory';
import config from 'marco/config/environment';
import DropdownMenu from './dropdown-menu';
import PhotoCarousel from './photo-carousel';
import Icon from './icon';
const GalleryContent = <template>
<div
class="photo-gallery-overlay"
role="dialog"
tabindex="-1"
{{on "click" @handleBackgroundClick}}
{{@bindKeyboard @handleKeydown}}
>
{{! template-lint-disable no-invalid-interactive }}
<div
class="photo-gallery-content"
data-current-event-id={{@currentPhoto.eventId}}
>
<div class="actions-btn-container">
<DropdownMenu
@iconSize={{24}}
@triggerIcon="more-horizontal"
@iconColor="white"
as |closeMenu|
>
<button
class="dropdown-item"
type="button"
{{on "click" (fn @copyEventId closeMenu)}}
>Copy Photo Event ID</button>
{{#if @canDeletePhoto}}
<button
class="dropdown-item text-danger"
type="button"
{{on "click" (fn @deletePhotoTask.perform closeMenu)}}
>Delete Photo</button>
{{/if}}
</DropdownMenu>
</div>
<button
type="button"
class="close-btn btn-text"
{{on "click" @handleClose}}
aria-label="Close gallery"
title="Close"
>
<Icon @name="x" @size={{24}} @color="white" />
</button>
<div class="main-photo-container">
<PhotoCarousel
@variant="gallery-main"
@photos={{@photos}}
@scrollToEventId={{@currentPhoto.eventId}}
@onVisiblePhotoChange={{@handleVisiblePhotoChange}}
@name={{@placeName}}
/>
</div>
<div class="thumbnail-strip-container">
<PhotoCarousel
@variant="gallery-thumbnails"
@photos={{@photos}}
@scrollToEventId={{@currentPhoto.eventId}}
@onPhotoClick={{@selectPhoto}}
@name={{@placeName}}
/>
</div>
</div>
</div>
</template>;
export default class PhotoGallery extends Component {
get isTesting() {
return config.environment === 'test';
}
get destinationElement() {
return document.getElementById('modal-portal') || document.body;
}
@service toast;
@service nostrAuth;
@service nostrData;
@service nostrRelay;
@service blossom;
@service settings;
@tracked currentPhoto = this.args.selectedPhoto || this.args.photos?.[0];
get isCreator() {
return (
this.currentPhoto?.pubkey &&
this.nostrAuth.pubkey &&
this.currentPhoto.pubkey === this.nostrAuth.pubkey
);
}
get canDeletePhoto() {
return (
this.isCreator && this.settings.experimentalEnablePhotoDeletion === true
);
}
bindKeyboard = modifier((element, [handler]) => {
document.addEventListener('keydown', handler);
return () => document.removeEventListener('keydown', handler);
});
@action
handleClose() {
if (this.args.onClose) {
this.args.onClose();
}
}
@action
handleBackgroundClick(e) {
// Don't close if clicking on thumbnails, nav buttons, or the close button itself
if (
e.target.closest('.thumbnail-strip-container') ||
e.target.closest('.carousel-nav-btn') ||
e.target.closest('.close-btn') ||
e.target.closest('.actions-btn-container')
) {
return;
}
this.handleClose();
}
@action
selectPhoto(photo) {
this.currentPhoto = photo;
}
@action
handleVisiblePhotoChange(photo) {
if (this.currentPhoto !== photo) {
this.currentPhoto = photo;
}
}
@action
handleKeydown(e) {
if (!this.args.photos || this.args.photos.length === 0) return;
if (e.key === 'Escape') {
this.handleClose();
return;
}
const currentIndex = this.args.photos.indexOf(this.currentPhoto);
if (currentIndex === -1) return;
if (e.key === 'ArrowLeft' && currentIndex > 0) {
this.currentPhoto = this.args.photos[currentIndex - 1];
} else if (
e.key === 'ArrowRight' &&
currentIndex < this.args.photos.length - 1
) {
this.currentPhoto = this.args.photos[currentIndex + 1];
}
}
@action
async copyEventId(closeMenu) {
if (this.currentPhoto?.eventId) {
try {
await navigator.clipboard.writeText(this.currentPhoto.eventId);
this.toast.show('Event ID copied to clipboard');
} catch (err) {
console.error('Failed to copy event ID:', err);
this.toast.show('Failed to copy event ID');
}
}
closeMenu();
}
deletePhotoTask = task(async (closeMenu) => {
if (
!confirm(
'Are you sure you want to delete this photo? This cannot be undone.'
)
) {
if (closeMenu) closeMenu();
return;
}
try {
const eventId = this.currentPhoto.eventId;
// Publish Nostr kind: 5 deletion event first so we don't end up with dead blossom links on a failure
const factory = new EventFactory({ signer: this.nostrAuth.signer });
const tags = [['e', eventId]];
if (this.currentPhoto.placeIdentifier) {
tags.push(['i', this.currentPhoto.placeIdentifier]);
}
const template = {
kind: 5,
created_at: Math.floor(Date.now() / 1000),
content: 'Deleted photo',
tags,
};
const event = await factory.sign(template);
await this.nostrRelay.publish(this.nostrData.activeWriteRelays, event);
// Remove from local store by adding the kind 5 to it
this.nostrData.store.add(event);
// Now that the event is published, try to delete from Blossom
const hashRegex = /[0-9a-f]{64}/i;
if (this.currentPhoto.url) {
const match = this.currentPhoto.url.match(hashRegex);
if (match) {
try {
await this.blossom.delete(match[0]);
} catch (e) {
console.warn('Failed to delete main image from blossom:', e);
}
}
}
if (this.currentPhoto.thumbUrl) {
const match = this.currentPhoto.thumbUrl.match(hashRegex);
if (match) {
try {
await this.blossom.delete(match[0]);
} catch (e) {
console.warn('Failed to delete thumb image from blossom:', e);
}
}
}
this.toast.show('Photo deleted successfully');
if (closeMenu) closeMenu();
this.handleClose();
} catch (e) {
console.error('Failed to delete photo:', e);
this.toast.show('Failed to delete photo: ' + e.message);
if (closeMenu) closeMenu();
}
});
<template>
{{#if this.isTesting}}
<GalleryContent
@handleBackgroundClick={{this.handleBackgroundClick}}
@bindKeyboard={{this.bindKeyboard}}
@handleKeydown={{this.handleKeydown}}
@copyEventId={{this.copyEventId}}
@canDeletePhoto={{this.canDeletePhoto}}
@deletePhotoTask={{this.deletePhotoTask}}
@handleClose={{this.handleClose}}
@photos={{@photos}}
@currentPhoto={{this.currentPhoto}}
@handleVisiblePhotoChange={{this.handleVisiblePhotoChange}}
@placeName={{@placeName}}
@selectPhoto={{this.selectPhoto}}
/>
{{else}}
{{#in-element this.destinationElement}}
<GalleryContent
@handleBackgroundClick={{this.handleBackgroundClick}}
@bindKeyboard={{this.bindKeyboard}}
@handleKeydown={{this.handleKeydown}}
@copyEventId={{this.copyEventId}}
@canDeletePhoto={{this.canDeletePhoto}}
@deletePhotoTask={{this.deletePhotoTask}}
@handleClose={{this.handleClose}}
@photos={{@photos}}
@currentPhoto={{this.currentPhoto}}
@handleVisiblePhotoChange={{this.handleVisiblePhotoChange}}
@placeName={{@placeName}}
@selectPhoto={{this.selectPhoto}}
/>
{{/in-element}}
{{/if}}
</template>
}
+31 -4
View File
@@ -13,7 +13,8 @@ import PlaceListsManager from './place-lists-manager';
import PlacePhotoUpload from './place-photo-upload'; import PlacePhotoUpload from './place-photo-upload';
import NostrConnect from './nostr-connect'; import NostrConnect from './nostr-connect';
import Modal from './modal'; import Modal from './modal';
import PlacePhotosCarousel from './place-photos-carousel'; import PhotoCarousel from './photo-carousel';
import PhotoGallery from './photo-gallery';
import { tracked } from '@glimmer/tracking'; import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object'; import { action } from '@ember/object';
@@ -24,10 +25,13 @@ export default class PlaceDetails extends Component {
@service nostrData; @service nostrData;
@tracked isEditing = false; @tracked isEditing = false;
@tracked showLists = false; @tracked showLists = false;
@tracked isPhotoUploadActive = false;
@tracked isConnectingNostr = false;
@tracked isGalleryOpen = false;
@tracked selectedGalleryPhoto = null;
@tracked isPhotoUploadModalOpen = false; @tracked isPhotoUploadModalOpen = false;
@tracked isNostrConnectModalOpen = false; @tracked isNostrConnectModalOpen = false;
@tracked newlyUploadedPhotoId = null; @tracked newlyUploadedPhotoId = null;
@tracked isPhotoUploadActive = false;
@action @action
handleUploadStateChange(isActive) { handleUploadStateChange(isActive) {
@@ -362,6 +366,18 @@ export default class PlaceDetails extends Component {
return !!this.place.description; return !!this.place.description;
} }
@action
openGallery(photo) {
this.selectedGalleryPhoto = photo;
this.isGalleryOpen = true;
}
@action
closeGallery() {
this.isGalleryOpen = false;
this.selectedGalleryPhoto = null;
}
<template> <template>
<div class="place-details"> <div class="place-details">
{{#if this.isEditing}} {{#if this.isEditing}}
@@ -371,11 +387,13 @@ export default class PlaceDetails extends Component {
@onCancel={{this.cancelEditing}} @onCancel={{this.cancelEditing}}
/> />
{{else}} {{else}}
<PlacePhotosCarousel <PhotoCarousel
@variant="inline"
@photos={{this.photos}} @photos={{this.photos}}
@name={{this.name}} @name={{this.name}}
@resetKey={{this.place.osmId}} @resetKey={{this.place.osmId}}
@scrollToEventId={{this.newlyUploadedPhotoId}} @scrollToEventId={{this.newlyUploadedPhotoId}}
@onPhotoClick={{this.openGallery}}
/> />
<h3>{{this.name}}</h3> <h3>{{this.name}}</h3>
<p class="place-type"> <p class="place-type">
@@ -576,7 +594,7 @@ export default class PlaceDetails extends Component {
{{#if this.osmUrl}} {{#if this.osmUrl}}
<div class="meta-info"> <div class="meta-info">
<p class="content-with-icon"> <p class="content-with-icon">
<Icon @name="camera" /> <Icon @name="feather-camera" />
<span> <span>
<button <button
type="button" type="button"
@@ -609,5 +627,14 @@ export default class PlaceDetails extends Component {
<NostrConnect @onConnect={{this.onNostrConnected}} /> <NostrConnect @onConnect={{this.onNostrConnected}} />
</Modal> </Modal>
{{/if}} {{/if}}
{{#if this.isGalleryOpen}}
<PhotoGallery
@photos={{this.photos}}
@selectedPhoto={{this.selectedGalleryPhoto}}
@placeName={{this.name}}
@onClose={{this.closeGallery}}
/>
{{/if}}
</template> </template>
} }
+1 -1
View File
@@ -10,7 +10,7 @@ import { isMobile } from '../utils/device';
import Blurhash from './blurhash'; import Blurhash from './blurhash';
const MAX_IMAGE_DIMENSION = 1920; const MAX_IMAGE_DIMENSION = 1920;
const IMAGE_QUALITY = 0.94; const IMAGE_QUALITY = 0.9;
const MAX_THUMBNAIL_DIMENSION = 350; const MAX_THUMBNAIL_DIMENSION = 350;
const THUMBNAIL_QUALITY = 0.9; const THUMBNAIL_QUALITY = 0.9;
+44
View File
@@ -8,6 +8,10 @@ import { task } from 'ember-concurrency';
import Geohash from 'latlon-geohash'; import Geohash from 'latlon-geohash';
import PlacePhotoUploadItem from './place-photo-upload-item'; import PlacePhotoUploadItem from './place-photo-upload-item';
import Icon from '#components/icon'; import Icon from '#components/icon';
import { getSuggestedPhotoTags } from '../utils/photo-tag-suggestions';
import capitalize from '../helpers/capitalize';
import includes from '../helpers/includes';
import { fn } from '@ember/helper';
import { or, not } from 'ember-truth-helpers'; import { or, not } from 'ember-truth-helpers';
export default class PlacePhotoUpload extends Component { export default class PlacePhotoUpload extends Component {
@@ -22,6 +26,7 @@ export default class PlacePhotoUpload extends Component {
@tracked error = ''; @tracked error = '';
@tracked isPublishing = false; @tracked isPublishing = false;
@tracked isDragging = false; @tracked isDragging = false;
@tracked selectedTags = [];
get place() { get place() {
return this.args.place || {}; return this.args.place || {};
@@ -37,6 +42,10 @@ export default class PlacePhotoUpload extends Component {
); );
} }
get suggestedTags() {
return getSuggestedPhotoTags(this.place);
}
@action @action
handleFileSelect(event) { handleFileSelect(event) {
this.addFile(event.target.files[0]); this.addFile(event.target.files[0]);
@@ -93,11 +102,22 @@ export default class PlacePhotoUpload extends Component {
} }
this.file = null; this.file = null;
this.uploadedPhoto = null; this.uploadedPhoto = null;
this.selectedTags = [];
if (this.args.onUploadStateChange) { if (this.args.onUploadStateChange) {
this.args.onUploadStateChange(false); this.args.onUploadStateChange(false);
} }
} }
@action
toggleTag(tag) {
if (this.selectedTags.includes(tag)) {
this.selectedTags = [];
return;
}
this.selectedTags = [tag];
}
deletePhotoTask = task(async (photoData) => { deletePhotoTask = task(async (photoData) => {
try { try {
if (photoData.hash) { if (photoData.hash) {
@@ -139,6 +159,10 @@ export default class PlacePhotoUpload extends Component {
const tags = [['i', `osm:${osmType}:${osmId}`]]; const tags = [['i', `osm:${osmType}:${osmId}`]];
for (const tag of this.selectedTags) {
tags.push(['t', tag]);
}
if (lat && lon) { if (lat && lon) {
tags.push(['g', Geohash.encode(lat, lon, 4)]); tags.push(['g', Geohash.encode(lat, lon, 4)]);
tags.push(['g', Geohash.encode(lat, lon, 6)]); tags.push(['g', Geohash.encode(lat, lon, 6)]);
@@ -227,6 +251,26 @@ export default class PlacePhotoUpload extends Component {
/> />
</div> </div>
{{#if this.suggestedTags.length}}
<div class="photo-tag-suggestions">
<p class="photo-tag-suggestions-title">
Choose a tag/category (optional):
</p>
<div class="photo-tag-suggestions-list">
{{#each this.suggestedTags as |tag|}}
<button
type="button"
class="photo-tag-chip
{{if (includes this.selectedTags tag) 'is-selected'}}"
{{on "click" (fn this.toggleTag tag)}}
>
{{capitalize tag}}
</button>
{{/each}}
</div>
</div>
{{/if}}
<button <button
type="button" type="button"
class="btn btn-primary btn-publish" class="btn btn-primary btn-publish"
-189
View File
@@ -1,189 +0,0 @@
import Component from '@glimmer/component';
import { tracked } from '@glimmer/tracking';
import { action } from '@ember/object';
import Blurhash from './blurhash';
import Icon from './icon';
import fadeInImage from '../modifiers/fade-in-image';
import { on } from '@ember/modifier';
import { modifier } from 'ember-modifier';
export default class PlacePhotosCarousel extends Component {
@tracked canScrollLeft = false;
@tracked canScrollRight = false;
carouselElement = null;
get photos() {
return this.args.photos || [];
}
get showChevrons() {
return this.photos.length > 1;
}
get cannotScrollLeft() {
return !this.canScrollLeft;
}
get cannotScrollRight() {
return !this.canScrollRight;
}
lastResetKey = null;
resetScrollPosition = modifier((element, [resetKey]) => {
if (resetKey !== undefined && resetKey !== this.lastResetKey) {
this.lastResetKey = resetKey;
element.scrollLeft = 0;
setTimeout(() => this.updateScrollState(), 50);
}
});
scrollToNewPhoto = modifier((element, [eventId]) => {
if (eventId && eventId !== this.lastEventId) {
this.lastEventId = eventId;
// Allow DOM to update first since the photo was *just* added to the store
setTimeout(() => {
const targetSlide = element.querySelector(
`[data-event-id="${eventId}"]`
);
if (targetSlide) {
element.scrollLeft = targetSlide.offsetLeft;
}
}, 100);
}
});
setupCarousel = modifier((element) => {
this.carouselElement = element;
// Defer the initial calculation slightly to ensure CSS and images have applied
setTimeout(() => {
this.updateScrollState();
}, 50);
let resizeObserver;
if (window.ResizeObserver) {
resizeObserver = new ResizeObserver(() => this.updateScrollState());
resizeObserver.observe(element);
}
return () => {
if (resizeObserver) {
resizeObserver.unobserve(element);
}
};
});
@action
updateScrollState() {
if (!this.carouselElement) return;
const { scrollLeft, scrollWidth, clientWidth } = this.carouselElement;
// tolerance of 1px for floating point rounding issues
this.canScrollLeft = scrollLeft > 1;
this.canScrollRight = scrollLeft + clientWidth < scrollWidth - 1;
}
@action
scrollLeft() {
if (!this.carouselElement) return;
this.carouselElement.scrollBy({
left: -this.carouselElement.clientWidth,
behavior: 'smooth',
});
}
@action
scrollRight() {
if (!this.carouselElement) return;
this.carouselElement.scrollBy({
left: this.carouselElement.clientWidth,
behavior: 'smooth',
});
}
<template>
{{#if this.photos.length}}
<div class="place-photos-carousel-wrapper">
<div
class="place-photos-carousel-track"
{{this.setupCarousel}}
{{this.resetScrollPosition @resetKey}}
{{this.scrollToNewPhoto @scrollToEventId}}
{{on "scroll" this.updateScrollState}}
>
{{#each this.photos as |photo|}}
{{! template-lint-disable no-inline-styles }}
<div
class="carousel-slide
{{if photo.isLandscape 'landscape' 'portrait'}}"
style={{photo.style}}
data-event-id={{photo.eventId}}
>
{{#if photo.blurhash}}
<Blurhash
@hash={{photo.blurhash}}
@width={{32}}
@height={{18}}
class="place-header-photo-blur"
/>
{{/if}}
{{#if photo.isLandscape}}
<picture>
{{#if photo.thumbUrl}}
<source
media="(max-width: 768px)"
data-srcset={{photo.thumbUrl}}
/>
{{/if}}
<img
data-src={{photo.url}}
class="place-header-photo landscape"
alt={{@name}}
{{fadeInImage photo.url}}
/>
</picture>
{{else}}
{{! Portrait uses thumb everywhere if available }}
<img
data-src={{if photo.thumbUrl photo.thumbUrl photo.url}}
class="place-header-photo portrait"
alt={{@name}}
{{fadeInImage (if photo.thumbUrl photo.thumbUrl photo.url)}}
/>
{{/if}}
</div>
{{/each}}
<div class="carousel-placeholder"></div>
</div>
{{#if this.showChevrons}}
<button
type="button"
class="carousel-nav-btn prev
{{if this.cannotScrollLeft 'disabled'}}"
{{on "click" this.scrollLeft}}
disabled={{this.cannotScrollLeft}}
aria-label="Previous photo"
>
<Icon @name="chevron-left" @color="currentColor" />
</button>
<button
type="button"
class="carousel-nav-btn next
{{if this.cannotScrollRight 'disabled'}}"
{{on "click" this.scrollRight}}
disabled={{this.cannotScrollRight}}
aria-label="Next photo"
>
<Icon @name="chevron-right" @color="currentColor" />
</button>
{{/if}}
</div>
{{/if}}
</template>
}
+52 -3
View File
@@ -13,6 +13,7 @@ import { eq, or } from 'ember-truth-helpers';
export default class SearchBoxComponent extends Component { export default class SearchBoxComponent extends Component {
@service photon; @service photon;
@service osm; @service osm;
@service storage;
@service router; @service router;
@service mapUi; @service mapUi;
@service map; // Assuming we might need map context, but mostly we use router @service map; // Assuming we might need map context, but mostly we use router
@@ -50,6 +51,29 @@ export default class SearchBoxComponent extends Component {
this.searchTask.perform(value); this.searchTask.perform(value);
} }
formatSavedPlace(place) {
const listNames = (place._listIds || [])
.map((id) => this.storage.lists?.find((l) => l.id === id)?.title)
.filter(Boolean)
.join(', ');
const description = listNames
? `Saved place (${listNames})`
: 'Saved place';
return {
source: 'saved',
id: place.id,
title: place.title,
icon: 'bookmark',
description,
osmId: place.osmId,
osmType: place.osmType,
lat: place.lat,
lon: place.lon,
};
}
searchTask = task({ restartable: true }, async (term) => { searchTask = task({ restartable: true }, async (term) => {
await timeout(300); await timeout(300);
@@ -76,8 +100,29 @@ export default class SearchBoxComponent extends Component {
icon: 'search', icon: 'search',
})); }));
// Filter saved places (minimum 3 characters)
let savedMatches = [];
if (q.length >= 3) {
savedMatches = this.storage.savedPlaces
.filter((p) => p.title && p.title.toLowerCase().includes(q))
.map((p) => this.formatSavedPlace(p));
}
const results = await this.photon.search(query, lat, lon); const results = await this.photon.search(query, lat, lon);
this.results = [...categoryMatches, ...results];
// Deduplicate Photon results that are already in saved matches
const savedOsmIds = new Set(
savedMatches.map((s) => s.osmId).filter(Boolean)
);
const filteredPhotonResults = results.filter(
(r) => !savedOsmIds.has(r.osmId)
);
this.results = [
...categoryMatches,
...savedMatches,
...filteredPhotonResults,
];
} catch (e) { } catch (e) {
console.error('Search failed', e); console.error('Search failed', e);
this.results = []; this.results = [];
@@ -156,8 +201,12 @@ export default class SearchBoxComponent extends Component {
} }
this.results = []; // Hide popover this.results = []; // Hide popover
// If it has an OSM ID, go to place details // If it's a custom saved place without an OSM ID, go to place details via internal ID
if (place.osmId) { if (place.source === 'saved' && place.id && !place.osmId) {
this.router.transitionTo('place', place.id);
}
// If it has an OSM ID, go to place details via OSM ID
else if (place.osmId) {
// Format: osm:node:123 // Format: osm:node:123
// place.osmType is already normalized to 'node', 'way', or 'relation' by PhotonService // place.osmType is already normalized to 'node', 'way', or 'relation' by PhotonService
const id = `osm:${place.osmType}:${place.osmId}`; const id = `osm:${place.osmType}:${place.osmId}`;
+184
View File
@@ -1,6 +1,16 @@
import Controller from '@ember/controller'; import Controller from '@ember/controller';
import { service } from '@ember/service';
import { task } from 'ember-concurrency';
import { getDistance } from '../utils/geo';
export default class SearchController extends Controller { export default class SearchController extends Controller {
@service osm;
@service photon;
@service mapUi;
@service storage;
@service router;
@service toast;
queryParams = ['lat', 'lon', 'q', 'selected', 'category']; queryParams = ['lat', 'lon', 'q', 'selected', 'category'];
lat = null; lat = null;
@@ -8,4 +18,178 @@ export default class SearchController extends Controller {
q = null; q = null;
selected = null; selected = null;
category = null; category = null;
fetchResultsTask = task({ restartable: true }, async (params) => {
// Hide sidebar and clear previous results immediately to signal a new search
this.mapUi.hideSidebar();
this.mapUi.clearSearchResults();
const lat = params.lat ? parseFloat(params.lat) : null;
const lon = params.lon ? parseFloat(params.lon) : null;
let pois = [];
let loadingType = null;
let loadingValue = null;
try {
// Case 0: Category Search (category parameter present)
if (params.category && lat && lon) {
loadingType = 'category';
loadingValue = params.category;
this.mapUi.startLoading(loadingType, loadingValue);
// We need bounds. If we have active map state, use it.
let bounds = this.mapUi.currentBounds;
// If we don't have bounds (direct URL visit), estimate them from lat/lon/zoom(16)
// or just use a fixed box around the center.
if (!bounds) {
// Approximate 0.01 degrees ~ 1km at equator. A viewport is roughly 0.02x0.01 at zoom 16?
// Let's take a safe box of ~1km radius.
const delta = 0.01;
bounds = {
minLat: lat - delta,
maxLat: lat + delta,
minLon: lon - delta,
maxLon: lon + delta,
};
}
pois = await this.osm.getCategoryPois(
bounds,
params.category,
lat,
lon
);
// Sort by distance from center
pois = pois
.map((p) => ({
...p,
_distance: getDistance(lat, lon, p.lat, p.lon),
}))
.sort((a, b) => a._distance - b._distance);
}
// Case 1: Text Search (q parameter present)
else if (params.q) {
loadingType = 'text';
loadingValue = params.q;
this.mapUi.startLoading(loadingType, loadingValue);
// Search with Photon (using lat/lon for bias if available)
pois = await this.photon.search(params.q, lat, lon);
// Search local bookmarks by name (minimum 3 characters)
const queryLower = params.q.toLowerCase();
let localMatches = [];
if (queryLower.length >= 3) {
localMatches = this.storage.savedPlaces.filter((p) => {
return (
p.title?.toLowerCase().includes(queryLower) ||
p.description?.toLowerCase().includes(queryLower)
);
});
}
// Merge local matches
localMatches.forEach((local) => {
const exists = pois.find(
(poi) =>
(local.osmId && poi.osmId === local.osmId) ||
(poi.id && poi.id === local.id)
);
if (!exists) {
pois.push(local);
}
});
}
// Case 2: Nearby Search (lat/lon present, no q)
else if (lat && lon) {
// Nearby search does NOT trigger loading state (pulse is used instead)
const searchRadius = 50; // Default radius
// Fetch POIs from Overpass
pois = await this.osm.getNearbyPois(lat, lon, searchRadius);
// Get cached/saved places in search radius
const localMatches = this.storage.savedPlaces.filter((p) => {
const dist = getDistance(lat, lon, p.lat, p.lon);
return dist <= searchRadius;
});
// Merge local matches
localMatches.forEach((local) => {
const exists = pois.find(
(poi) =>
(local.osmId && poi.osmId === local.osmId) ||
(poi.id && poi.id === local.id)
);
if (!exists) {
pois.push(local);
}
});
// Sort by distance from click
pois = pois
.map((p) => {
return {
...p,
_distance: getDistance(lat, lon, p.lat, p.lon),
};
})
.sort((a, b) => a._distance - b._distance);
}
} catch (error) {
console.error('Search request failed.', error);
this.toast.show('Search request failed. Please try again.');
this.mapUi.stopSearch();
return;
} finally {
if (loadingType && loadingValue) {
this.mapUi.stopLoading(loadingType, loadingValue);
}
}
// Check if any of these are already bookmarked
// We resolve them to the bookmark version if they exist
pois = pois.map((p) => {
const saved = this.storage.findPlaceById(p.osmId);
return saved || p;
});
const targetName = params.selected || params.q;
if (targetName && pois.length > 0) {
let matchedPlace = null;
// 1. Exact Name Match
matchedPlace = pois.find(
(p) =>
p.osmTags &&
(p.osmTags.name === targetName || p.osmTags['name:en'] === targetName)
);
// 2. High Proximity Match (<= 10m) - Only if we don't have a name match
// Note: MapComponent had logic for <=20m + type match.
// We might want to pass the 'type' in queryParams if we want to be that precise.
// For now, let's stick to name or very close proximity.
if (!matchedPlace) {
const topCandidate = pois[0];
if (topCandidate._distance <= 10) {
matchedPlace = topCandidate;
}
}
if (matchedPlace) {
// Direct transition!
this.router.replaceWith('place', matchedPlace);
this.mapUi.stopSearch();
return;
}
}
this.mapUi.setSearchResults(pois);
this.mapUi.showSidebar();
this.mapUi.stopSearch();
});
} }
+6
View File
@@ -0,0 +1,6 @@
import { helper } from '@ember/component/helper';
import { capitalize as format } from '../utils/format-text';
export default helper(function capitalize([text]) {
return format(text);
});
+6
View File
@@ -0,0 +1,6 @@
import { helper } from '@ember/component/helper';
export default helper(function includes([collection, value]) {
if (!Array.isArray(collection)) return false;
return collection.includes(value);
});
+18
View File
@@ -9,11 +9,28 @@ export default modifier((element, [url]) => {
let observer; let observer;
const hideBlurhash = () => {
const parent = element.parentElement;
const slide =
parent && parent.tagName === 'PICTURE' ? parent.parentElement : parent;
// Only hide the blurhash if we're in the gallery-main view.
// In the inline view, we want to keep the blurhash visible behind portrait photos
// to fill the 16:9 container gracefully.
if (slide && slide.closest('.photo-carousel.gallery-main')) {
const blur = slide.querySelector('.place-header-photo-blur');
if (blur) {
blur.style.opacity = '0';
}
}
};
const handleLoad = () => { const handleLoad = () => {
// Only apply the fade-in animation if it wasn't already loaded instantly // Only apply the fade-in animation if it wasn't already loaded instantly
if (!element.classList.contains('loaded-instant')) { if (!element.classList.contains('loaded-instant')) {
element.classList.add('loaded'); element.classList.add('loaded');
} }
hideBlurhash();
}; };
element.addEventListener('load', handleLoad); element.addEventListener('load', handleLoad);
@@ -33,6 +50,7 @@ export default modifier((element, [url]) => {
if (img.complete) { if (img.complete) {
// Already in browser cache, skip the animation // Already in browser cache, skip the animation
element.classList.add('loaded-instant'); element.classList.add('loaded-instant');
hideBlurhash();
} }
// If this image is inside a <picture> tag, we also need to swap <source> tags // If this image is inside a <picture> tag, we also need to swap <source> tags
+1
View File
@@ -96,6 +96,7 @@ export default class PlaceRoute extends Route {
if (model) { if (model) {
const options = { preventZoom: this.mapUi.preventNextZoom }; const options = { preventZoom: this.mapUi.preventNextZoom };
this.mapUi.selectPlace(model, options); this.mapUi.selectPlace(model, options);
this.mapUi.showSidebar();
this.mapUi.preventNextZoom = false; this.mapUi.preventNextZoom = false;
} }
// Stop the pulse animation if it was running (e.g. redirected from search) // Stop the pulse animation if it was running (e.g. redirected from search)
+1
View File
@@ -22,6 +22,7 @@ export default class PlaceNewRoute extends Route {
this.mapUi.updateCreationCoordinates(model.lat, model.lon); this.mapUi.updateCreationCoordinates(model.lat, model.lon);
} }
this.mapUi.startCreating(); this.mapUi.startCreating();
this.mapUi.showSidebar();
} }
deactivate() { deactivate() {
+14 -176
View File
@@ -1,14 +1,9 @@
import Route from '@ember/routing/route'; import Route from '@ember/routing/route';
import { service } from '@ember/service'; import { service } from '@ember/service';
import { action } from '@ember/object'; import { action } from '@ember/object';
import { getDistance } from '../utils/geo';
export default class SearchRoute extends Route { export default class SearchRoute extends Route {
@service osm;
@service photon;
@service mapUi; @service mapUi;
@service storage;
@service router;
@service toast; @service toast;
queryParams = { queryParams = {
@@ -19,186 +14,29 @@ export default class SearchRoute extends Route {
category: { refreshModel: true }, category: { refreshModel: true },
}; };
async model(params) { model(params) {
const lat = params.lat ? parseFloat(params.lat) : null; // Just return params, doing the async fetch in the controller
const lon = params.lon ? parseFloat(params.lon) : null; return params;
let pois = [];
let loadingType = null;
let loadingValue = null;
try {
// Case 0: Category Search (category parameter present)
if (params.category && lat && lon) {
loadingType = 'category';
loadingValue = params.category;
this.mapUi.startLoading(loadingType, loadingValue);
// We need bounds. If we have active map state, use it.
let bounds = this.mapUi.currentBounds;
// If we don't have bounds (direct URL visit), estimate them from lat/lon/zoom(16)
// or just use a fixed box around the center.
if (!bounds) {
// Approximate 0.01 degrees ~ 1km at equator. A viewport is roughly 0.02x0.01 at zoom 16?
// Let's take a safe box of ~1km radius.
const delta = 0.01;
bounds = {
minLat: lat - delta,
maxLat: lat + delta,
minLon: lon - delta,
maxLon: lon + delta,
};
}
pois = await this.osm.getCategoryPois(
bounds,
params.category,
lat,
lon
);
// Sort by distance from center
pois = pois
.map((p) => ({
...p,
_distance: getDistance(lat, lon, p.lat, p.lon),
}))
.sort((a, b) => a._distance - b._distance);
}
// Case 1: Text Search (q parameter present)
else if (params.q) {
loadingType = 'text';
loadingValue = params.q;
this.mapUi.startLoading(loadingType, loadingValue);
// Search with Photon (using lat/lon for bias if available)
pois = await this.photon.search(params.q, lat, lon);
// Search local bookmarks by name
const queryLower = params.q.toLowerCase();
const localMatches = this.storage.savedPlaces.filter((p) => {
return (
p.title?.toLowerCase().includes(queryLower) ||
p.description?.toLowerCase().includes(queryLower)
);
});
// Merge local matches
localMatches.forEach((local) => {
const exists = pois.find(
(poi) =>
(local.osmId && poi.osmId === local.osmId) ||
(poi.id && poi.id === local.id)
);
if (!exists) {
pois.push(local);
}
});
}
// Case 2: Nearby Search (lat/lon present, no q)
else if (lat && lon) {
// Nearby search does NOT trigger loading state (pulse is used instead)
const searchRadius = 50; // Default radius
// Fetch POIs from Overpass
pois = await this.osm.getNearbyPois(lat, lon, searchRadius);
// Get cached/saved places in search radius
const localMatches = this.storage.savedPlaces.filter((p) => {
const dist = getDistance(lat, lon, p.lat, p.lon);
return dist <= searchRadius;
});
// Merge local matches
localMatches.forEach((local) => {
const exists = pois.find(
(poi) =>
(local.osmId && poi.osmId === local.osmId) ||
(poi.id && poi.id === local.id)
);
if (!exists) {
pois.push(local);
}
});
// Sort by distance from click
pois = pois
.map((p) => {
return {
...p,
_distance: getDistance(lat, lon, p.lat, p.lon),
};
})
.sort((a, b) => a._distance - b._distance);
}
} finally {
if (loadingType && loadingValue) {
this.mapUi.stopLoading(loadingType, loadingValue);
}
}
// Check if any of these are already bookmarked
// We resolve them to the bookmark version if they exist
pois = pois.map((p) => {
const saved = this.storage.findPlaceById(p.osmId);
return saved || p;
});
return pois;
}
afterModel(model, transition) {
const { q, selected } = transition.to.queryParams;
// Heuristic Match Logic (ported from MapComponent)
// If 'selected' is provided (from map click), try to find that specific feature.
// If 'q' is provided (from text search), try to find an exact match to auto-select.
const targetName = selected || q;
if (targetName && model.length > 0) {
let matchedPlace = null;
// 1. Exact Name Match
matchedPlace = model.find(
(p) =>
p.osmTags &&
(p.osmTags.name === targetName || p.osmTags['name:en'] === targetName)
);
// 2. High Proximity Match (<= 10m) - Only if we don't have a name match
// Note: MapComponent had logic for <=20m + type match.
// We might want to pass the 'type' in queryParams if we want to be that precise.
// For now, let's stick to name or very close proximity.
if (!matchedPlace) {
const topCandidate = model[0];
if (topCandidate._distance <= 10) {
matchedPlace = topCandidate;
}
}
if (matchedPlace) {
// Direct transition!
this.router.replaceWith('place', matchedPlace);
return;
}
}
// Stop the pulse animation since search is done (and we are staying here)
this.mapUi.stopSearch();
} }
setupController(controller, model) { setupController(controller, model) {
super.setupController(controller, model); super.setupController(controller, model);
// Ensure pulse is stopped if we reach here
this.mapUi.stopSearch(); // Trigger the background task to fetch results
this.mapUi.setSearchResults(model); controller.fetchResultsTask.perform(model);
// Store current search params to allow "Up" navigation from place details // Store current search params to allow "Up" navigation from place details
const { q, category, lat, lon } = this.paramsFor('search'); const { q, category, lat, lon } = this.paramsFor('search');
this.mapUi.currentSearch = { q, category, lat, lon }; this.mapUi.currentSearch = { q, category, lat, lon };
} }
resetController(controller, isExiting) {
if (isExiting) {
controller.fetchResultsTask.cancelAll();
this.mapUi.stopSearch();
}
}
@action @action
error(error, transition) { error(error, transition) {
this.mapUi.stopSearch(); this.mapUi.stopSearch();
@@ -206,6 +44,6 @@ export default class SearchRoute extends Route {
if (transition) { if (transition) {
transition.abort(); transition.abort();
} }
return false; // Prevent bubble and stop transition return false;
} }
} }
+14
View File
@@ -11,12 +11,22 @@ export default class MapUiService extends Service {
@tracked returnToSearch = false; @tracked returnToSearch = false;
@tracked currentCenter = null; @tracked currentCenter = null;
@tracked currentBounds = null; @tracked currentBounds = null;
@tracked currentZoom = null;
@tracked searchBoxHasFocus = false; @tracked searchBoxHasFocus = false;
@tracked selectionOptions = {}; @tracked selectionOptions = {};
@tracked preventNextZoom = false; @tracked preventNextZoom = false;
@tracked searchResults = []; @tracked searchResults = [];
@tracked currentSearch = null; @tracked currentSearch = null;
@tracked loadingState = null; @tracked loadingState = null;
@tracked isSidebarVisible = false;
showSidebar() {
this.isSidebarVisible = true;
}
hideSidebar() {
this.isSidebarVisible = false;
}
selectPlace(place, options = {}) { selectPlace(place, options = {}) {
this.selectedPlace = place; this.selectedPlace = place;
@@ -72,6 +82,10 @@ export default class MapUiService extends Service {
this.currentCenter = { lat, lon }; this.currentCenter = { lat, lon };
} }
updateZoom(zoom) {
this.currentZoom = zoom;
}
updateBounds(bounds) { updateBounds(bounds) {
this.currentBounds = bounds; this.currentBounds = bounds;
} }
+4 -3
View File
@@ -55,10 +55,11 @@ export default class NostrDataService extends Service {
this._stopPersisting = persistEventsToCache( this._stopPersisting = persistEventsToCache(
this.store, this.store,
async (events) => { async (events) => {
// Only cache profiles, mailboxes, blossom servers, and place photos // Only cache profiles, mailboxes, blossom servers, and place photos, and deletions
const toCache = events.filter( const toCache = events.filter(
(e) => (e) =>
e.kind === 0 || e.kind === 0 ||
e.kind === 5 ||
e.kind === 10002 || e.kind === 10002 ||
e.kind === 10063 || e.kind === 10063 ||
e.kind === 360 e.kind === 360
@@ -215,7 +216,7 @@ export default class NostrDataService extends Service {
const cachedEvents = await this.cache.query([ const cachedEvents = await this.cache.query([
{ {
kinds: [360], kinds: [360, 5],
'#i': [entityId], '#i': [entityId],
}, },
]); ]);
@@ -236,7 +237,7 @@ export default class NostrDataService extends Service {
this.nostrRelay.pool this.nostrRelay.pool
.request(this.activeReadRelays, [ .request(this.activeReadRelays, [
{ {
kinds: [360], kinds: [360, 5],
'#i': [entityId], '#i': [entityId],
}, },
]) ])
+1 -1
View File
@@ -2,7 +2,7 @@ import Service from '@ember/service';
import { RelayPool } from 'applesauce-relay'; import { RelayPool } from 'applesauce-relay';
export default class NostrRelayService extends Service { export default class NostrRelayService extends Service {
pool = new RelayPool(); pool = new RelayPool({ enablePing: true });
async publish(relays, event) { async publish(relays, event) {
if (!relays || relays.length === 0) { if (!relays || relays.length === 0) {
+6
View File
@@ -9,6 +9,7 @@ const DEFAULT_SETTINGS = {
nostrPhotoFallbackUploads: false, nostrPhotoFallbackUploads: false,
nostrReadRelays: null, nostrReadRelays: null,
nostrWriteRelays: null, nostrWriteRelays: null,
experimentalEnablePhotoDeletion: false,
}; };
export default class SettingsService extends Service { export default class SettingsService extends Service {
@@ -20,6 +21,8 @@ export default class SettingsService extends Service {
DEFAULT_SETTINGS.nostrPhotoFallbackUploads; DEFAULT_SETTINGS.nostrPhotoFallbackUploads;
@tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays; @tracked nostrReadRelays = DEFAULT_SETTINGS.nostrReadRelays;
@tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays; @tracked nostrWriteRelays = DEFAULT_SETTINGS.nostrWriteRelays;
@tracked experimentalEnablePhotoDeletion =
DEFAULT_SETTINGS.experimentalEnablePhotoDeletion;
overpassApis = [ overpassApis = [
{ {
@@ -108,6 +111,8 @@ export default class SettingsService extends Service {
this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads; this.nostrPhotoFallbackUploads = finalSettings.nostrPhotoFallbackUploads;
this.nostrReadRelays = finalSettings.nostrReadRelays; this.nostrReadRelays = finalSettings.nostrReadRelays;
this.nostrWriteRelays = finalSettings.nostrWriteRelays; this.nostrWriteRelays = finalSettings.nostrWriteRelays;
this.experimentalEnablePhotoDeletion =
finalSettings.experimentalEnablePhotoDeletion;
// Save to ensure migrated settings are stored in the new format // Save to ensure migrated settings are stored in the new format
this.saveSettings(); this.saveSettings();
@@ -122,6 +127,7 @@ export default class SettingsService extends Service {
nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads, nostrPhotoFallbackUploads: this.nostrPhotoFallbackUploads,
nostrReadRelays: this.nostrReadRelays, nostrReadRelays: this.nostrReadRelays,
nostrWriteRelays: this.nostrWriteRelays, nostrWriteRelays: this.nostrWriteRelays,
experimentalEnablePhotoDeletion: this.experimentalEnablePhotoDeletion,
}; };
localStorage.setItem('marco:settings', JSON.stringify(settings)); localStorage.setItem('marco:settings', JSON.stringify(settings));
} }
+36
View File
@@ -11,6 +11,7 @@ import { getLocalizedName } from '../utils/osm';
export default class StorageService extends Service { export default class StorageService extends Service {
@service osm; @service osm;
@service toast;
rs; rs;
widget; widget;
@tracked placesInView = []; @tracked placesInView = [];
@@ -23,10 +24,13 @@ export default class StorageService extends Service {
@tracked connected = false; @tracked connected = false;
@tracked userAddress = null; @tracked userAddress = null;
@tracked isWidgetOpen = false; @tracked isWidgetOpen = false;
isNewConnection = true;
constructor() { constructor() {
super(...arguments); super(...arguments);
this.checkInitialConnectionState();
this.rs = new RemoteStorage({ this.rs = new RemoteStorage({
modules: [Places], modules: [Places],
}); });
@@ -57,6 +61,12 @@ export default class StorageService extends Service {
this.rs.on('connected', () => { this.rs.on('connected', () => {
this.connected = true; this.connected = true;
this.userAddress = this.rs.remote.userAddress; this.userAddress = this.rs.remote.userAddress;
if (this.isNewConnection) {
this.toast.show('Remote storage connected', 3000);
this.isNewConnection = false;
}
this.loadLists(); this.loadLists();
}); });
@@ -72,6 +82,7 @@ export default class StorageService extends Service {
this.loadedPrefixes = []; this.loadedPrefixes = [];
this.lists = []; this.lists = [];
this.initialSyncDone = false; this.initialSyncDone = false;
this.isNewConnection = true;
}); });
this.rs.on('sync-done', () => { this.rs.on('sync-done', () => {
@@ -93,6 +104,31 @@ export default class StorageService extends Service {
}); });
} }
checkInitialConnectionState() {
this.isNewConnection = true;
try {
if (window.localStorage) {
const keys = [
'remotestorage:wireclient',
'remotestorage:dropbox',
'remotestorage:googledrive',
];
for (const key of keys) {
const data = window.localStorage.getItem(key);
if (data) {
const parsed = JSON.parse(data);
if (parsed && parsed.token) {
this.isNewConnection = false;
break;
}
}
}
}
} catch (e) {
console.warn('Failed to check localStorage for existing connection:', e);
}
}
handlePlaceChange(event) { handlePlaceChange(event) {
const { newValue, relativePath } = event; const { newValue, relativePath } = event;
+271 -14
View File
@@ -594,6 +594,9 @@ body {
padding: 0 1.4rem 1rem; padding: 0 1.4rem 1rem;
animation: details-slide-down 0.2s ease-out; animation: details-slide-down 0.2s ease-out;
font-size: 0.9rem; font-size: 0.9rem;
}
.sidebar-content details .details-content.form-layout {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
@@ -890,12 +893,15 @@ abbr[title] {
padding-bottom: 2rem; padding-bottom: 2rem;
} }
.place-photos-carousel-wrapper { .photo-carousel {
position: relative; position: relative;
}
.photo-carousel.inline {
margin: -1rem -1rem 1rem; margin: -1rem -1rem 1rem;
} }
.place-photos-carousel-track { .photo-carousel-track {
display: flex; display: flex;
overflow-x: auto; overflow-x: auto;
scroll-behavior: smooth; scroll-behavior: smooth;
@@ -904,11 +910,12 @@ abbr[title] {
background-color: var(--hover-bg); background-color: var(--hover-bg);
} }
.place-photos-carousel-track::-webkit-scrollbar { .photo-carousel-track::-webkit-scrollbar {
display: none; /* Safari and Chrome */ display: none; /* Safari and Chrome */
} }
.carousel-slide { .carousel-slide {
cursor: pointer;
position: relative; position: relative;
flex: 0 0 100%; flex: 0 0 100%;
scroll-snap-align: start; scroll-snap-align: start;
@@ -984,7 +991,7 @@ abbr[title] {
padding: 0; padding: 0;
} }
.place-photos-carousel-wrapper:hover .carousel-nav-btn:not(.disabled) { .photo-carousel:hover .carousel-nav-btn:not(.disabled) {
opacity: 1; opacity: 1;
} }
@@ -1006,40 +1013,40 @@ abbr[title] {
} }
@media (width <= 768px) { @media (width <= 768px) {
.place-photos-carousel-track { .photo-carousel.inline .photo-carousel-track {
scroll-snap-type: none; scroll-snap-type: none;
gap: 2px; gap: 2px;
background-color: #fff; background-color: #fff;
} }
.carousel-slide { .photo-carousel.inline .carousel-slide {
flex: 0 0 auto; flex: 0 0 auto;
height: 100px; height: 100px;
width: auto; width: auto;
scroll-snap-align: none; scroll-snap-align: none;
} }
.carousel-slide.landscape { .photo-carousel.inline .carousel-slide.landscape {
aspect-ratio: var(--slide-ratio, 16 / 9); aspect-ratio: var(--slide-ratio, 16 / 9);
} }
.carousel-slide.portrait { .photo-carousel.inline .carousel-slide.portrait {
aspect-ratio: 1 / 1; aspect-ratio: 1 / 1;
} }
.carousel-placeholder { .photo-carousel.inline .carousel-placeholder {
display: block; display: block;
background-color: var(--hover-bg); background-color: var(--hover-bg);
flex: 1 1 0%; flex: 1 1 0%;
min-width: 0; min-width: 0;
} }
.place-header-photo.landscape, .photo-carousel.inline .place-header-photo.landscape,
.place-header-photo.portrait { .photo-carousel.inline .place-header-photo.portrait {
object-fit: cover; object-fit: cover;
} }
.carousel-nav-btn { .photo-carousel.inline .carousel-nav-btn {
display: none; display: none;
} }
} }
@@ -1077,6 +1084,7 @@ abbr[title] {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
gap: 0.5rem; gap: 0.5rem;
text-decoration: none;
} }
.btn:disabled { .btn:disabled {
@@ -1365,10 +1373,10 @@ span.icon {
@media (width <= 768px) { @media (width <= 768px) {
/* Sidebar/Bottom Sheet is open (Mobile: Bottom 50%) */ /* Sidebar/Bottom Sheet is open (Mobile: Bottom 50%) */
/* Center Y = (height/2) / 2 = height/4 = 25% */ /* Center Y = (height/2) / 2 = height/4 = 25% + half header height */
.map-container.sidebar-open .map-crosshair { .map-container.sidebar-open .map-crosshair {
left: 50%; /* Reset desktop shift */ left: 50%; /* Reset desktop shift */
top: 25%; top: calc(25% + 30px); /* 30px approx half header height */
} }
} }
@@ -1773,6 +1781,13 @@ button.create-place {
margin-top: 1.5rem; margin-top: 1.5rem;
} }
.mobile-connect-actions {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-top: 1rem;
}
.nostr-connect-status { .nostr-connect-status {
margin-top: 1.5rem; margin-top: 1.5rem;
text-align: center; text-align: center;
@@ -1828,6 +1843,42 @@ button.create-place {
font-size: 1.2rem; font-size: 1.2rem;
} }
.photo-tag-suggestions {
margin: 1rem 0 1.5rem;
}
.photo-tag-suggestions-title {
color: #898989;
margin: 0 0 0.75rem;
font-weight: normal;
font-size: 0.9rem;
}
.photo-tag-suggestions-list {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
}
.photo-tag-chip {
background: #f8f9fa;
color: #333;
border: none;
border-radius: 16px;
padding: 6px 12px;
font-size: 0.9rem;
cursor: pointer;
transition: background-color 0.1s;
}
.photo-tag-chip:hover {
background: #eee;
}
.photo-tag-chip.is-selected {
background: rgb(255 204 51 / 30%);
}
.alert { .alert {
padding: 0.5rem; padding: 0.5rem;
margin-bottom: 1rem; margin-bottom: 1rem;
@@ -1866,3 +1917,209 @@ button.create-place {
.btn-link:hover { .btn-link:hover {
text-decoration: underline; text-decoration: underline;
} }
/* Photo Gallery */
.photo-gallery-overlay {
position: fixed;
inset: 0;
background: rgb(0 0 0 / 90%);
z-index: 9999;
display: flex;
flex-direction: column;
}
.photo-gallery-overlay .photo-gallery-content {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
position: relative;
}
.photo-gallery-overlay .close-btn {
position: absolute;
top: 0.5rem;
right: 1rem;
width: 48px;
height: 48px;
z-index: 10;
color: white;
background: transparent;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s ease;
}
@media (width <= 768px) {
.photo-gallery-overlay .close-btn {
right: 0.5rem;
}
}
.photo-gallery-overlay .main-photo-container {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
padding: 2rem;
overflow: hidden;
}
@media (width <= 768px) {
.photo-gallery-overlay .main-photo-container {
padding: 2rem 0;
}
}
.photo-gallery-overlay .main-photo-container img {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
/* Photo Carousel: Gallery Main Variant */
.photo-carousel.gallery-main {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
}
.photo-carousel.gallery-main .photo-carousel-track {
height: 100%;
background: transparent;
}
.photo-carousel.gallery-main .carousel-slide {
height: 100%;
flex: 0 0 100%;
aspect-ratio: auto;
cursor: default;
}
.photo-carousel.gallery-main .carousel-nav-btn {
width: 48px;
height: 48px;
}
.photo-carousel.gallery-main .place-header-photo-blur,
.photo-carousel.gallery-main .place-header-photo.landscape,
.photo-carousel.gallery-main .place-header-photo.portrait {
object-fit: contain;
}
@media (width <= 768px) {
.photo-carousel.gallery-main .carousel-nav-btn {
display: none;
}
}
/* Photo Carousel: Gallery Thumbnails Variant */
.photo-carousel.gallery-thumbnails {
width: 100%;
height: 100px;
background: rgb(0 0 0 / 50%);
padding-bottom: env(safe-area-inset-bottom, 0); /* Support mobile safe area */
}
.photo-carousel.gallery-thumbnails .photo-carousel-track {
height: 100%;
background: transparent;
gap: 4px;
scroll-snap-type: none;
padding: 0;
}
.photo-carousel.gallery-thumbnails .carousel-slide {
flex: 0 0 auto;
height: 100%;
width: auto;
scroll-snap-align: none;
opacity: 0.6;
transition: opacity 0.2s;
}
.photo-carousel.gallery-thumbnails .carousel-slide.landscape {
aspect-ratio: var(--slide-ratio, 16 / 9);
}
.photo-carousel.gallery-thumbnails .carousel-slide.portrait {
aspect-ratio: 1 / 1;
}
.photo-carousel.gallery-thumbnails .carousel-slide:hover,
.photo-carousel.gallery-thumbnails .carousel-slide.active {
opacity: 1;
}
.photo-carousel.gallery-thumbnails .place-header-photo.landscape,
.photo-carousel.gallery-thumbnails .place-header-photo.portrait {
object-fit: cover;
height: 100%;
}
.photo-carousel.gallery-thumbnails .carousel-nav-btn {
display: none;
}
/* Dropdown Menu Component */
.dropdown-menu-container {
position: relative;
display: inline-block;
}
.dropdown-trigger-btn {
background: transparent;
border: none;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
.dropdown-popover {
position: absolute;
top: 100%;
left: 0;
margin-top: 5px;
background: white;
border-radius: 8px;
box-shadow: 0 4px 12px rgb(0 0 0 / 15%);
padding: 0.5rem 0;
z-index: 3001;
min-width: 150px;
display: flex;
flex-direction: column;
gap: 0.25rem;
}
.dropdown-item {
background: transparent;
border: none;
padding: 0.5rem 1rem;
text-align: left;
cursor: pointer;
font-size: 0.95rem;
color: #333;
white-space: nowrap;
}
.dropdown-item:hover {
background: #f0f0f0;
}
/* Actions button in photo gallery */
.photo-gallery-overlay .actions-btn-container {
position: absolute;
top: 0.5rem;
left: 0.5rem;
width: 48px;
height: 48px;
z-index: 10;
display: flex;
align-items: center;
justify-content: center;
}
+11 -11
View File
@@ -18,12 +18,13 @@ export default class ApplicationComponent extends Component {
@tracked isAppMenuOpen = false; @tracked isAppMenuOpen = false;
get isSidebarOpen() { get isSidebarOpen() {
// We consider the sidebar "open" if we are in search or place routes. // We consider the sidebar "open" if we are in search or place routes AND it's visible.
// This helps the map know if it should shift the center or adjust view. // This helps the map know if it should shift the center or adjust view.
return ( return (
this.router.currentRouteName === 'place' || this.mapUi.isSidebarVisible &&
this.router.currentRouteName === 'place.new' || (this.router.currentRouteName === 'place' ||
this.router.currentRouteName === 'search' this.router.currentRouteName === 'place.new' ||
this.router.currentRouteName === 'search')
); );
} }
@@ -48,13 +49,12 @@ export default class ApplicationComponent extends Component {
handleOutsideClick() { handleOutsideClick() {
if (this.isAppMenuOpen) { if (this.isAppMenuOpen) {
this.closeAppMenu(); this.closeAppMenu();
} else if (this.router.currentRouteName === 'search') { } else if (
this.router.transitionTo('index'); this.router.currentRouteName === 'search' ||
} else if (this.router.currentRouteName === 'place') { this.router.currentRouteName === 'place'
// If in place route, decide if we want to go back to search or index ) {
// For now, let's go to index or maybe back to search if search params exist? this.mapUi.clearSelection();
// Simplest behavior: clear selection this.mapUi.hideSidebar();
this.router.transitionTo('index');
} }
} }
+13 -9
View File
@@ -79,6 +79,7 @@ export default class PlaceTemplate extends Component {
if (place === null) { if (place === null) {
// If we have an active search context, return to it (UP navigation) // If we have an active search context, return to it (UP navigation)
if (this.mapUi.returnToSearch && this.mapUi.currentSearch) { if (this.mapUi.returnToSearch && this.mapUi.currentSearch) {
this.mapUi.showSidebar();
this.router.transitionTo('search', { this.router.transitionTo('search', {
queryParams: this.mapUi.currentSearch, queryParams: this.mapUi.currentSearch,
}); });
@@ -88,23 +89,26 @@ export default class PlaceTemplate extends Component {
} }
} else { } else {
// If a place is selected (unlikely in this view, but possible if we add related links) // If a place is selected (unlikely in this view, but possible if we add related links)
this.mapUi.showSidebar();
this.router.transitionTo('place', place); this.router.transitionTo('place', place);
} }
} }
@action @action
close() { close() {
// Clear search results so we don't fall back to the list this.mapUi.clearSelection();
this.router.transitionTo('index'); this.mapUi.hideSidebar();
} }
<template> <template>
<PlacesSidebar {{#if this.mapUi.isSidebarVisible}}
@selectedPlace={{this.place}} <PlacesSidebar
@onClose={{this.close}} @selectedPlace={{this.place}}
@onSelect={{this.navigateBack}} @onClose={{this.close}}
@onBookmarkChange={{this.refreshMap}} @onSelect={{this.navigateBack}}
@onUpdate={{this.handleUpdate}} @onBookmarkChange={{this.refreshMap}}
/> @onUpdate={{this.handleUpdate}}
/>
{{/if}}
</template> </template>
} }
+22 -20
View File
@@ -56,28 +56,30 @@ export default class PlaceNewTemplate extends Component {
} }
<template> <template>
<div class="sidebar"> {{#if this.mapUi.isSidebarVisible}}
<div class="sidebar-header"> <div class="sidebar">
<h2><Icon @name="plus-circle" @size={{20}} @color="#ea4335" /> <div class="sidebar-header">
New Place</h2> <h2><Icon @name="plus-circle" @size={{20}} @color="#ea4335" />
<button type="button" class="close-btn" {{on "click" this.close}}><Icon New Place</h2>
@name="x" <button
@size={{20}} type="button"
@color="#333" class="close-btn"
/></button> {{on "click" this.close}}
</div> ><Icon @name="x" @size={{20}} @color="#333" /></button>
</div>
<div class="sidebar-content"> <div class="sidebar-content">
<p class="helper-text"> <p class="helper-text">
Drag the map to position the crosshair. Drag the map to position the crosshair.
</p> </p>
<PlaceEditForm <PlaceEditForm
@place={{this.initialPlace}} @place={{this.initialPlace}}
@onSave={{this.savePlace}} @onSave={{this.savePlace}}
@onCancel={{this.close}} @onCancel={{this.close}}
/> />
</div>
</div> </div>
</div> {{/if}}
</template> </template>
} }
+10 -6
View File
@@ -11,6 +11,8 @@ export default class SearchTemplate extends Component {
selectPlace(place) { selectPlace(place) {
if (place) { if (place) {
this.mapUi.returnToSearch = true; this.mapUi.returnToSearch = true;
this.mapUi.showSidebar();
this.mapUi.preventNextZoom = true;
// We don't need to manually set currentSearch here because // We don't need to manually set currentSearch here because
// it was already set in the route's setupController // it was already set in the route's setupController
this.router.transitionTo('place', place); this.router.transitionTo('place', place);
@@ -19,14 +21,16 @@ export default class SearchTemplate extends Component {
@action @action
close() { close() {
this.router.transitionTo('index'); this.mapUi.hideSidebar();
} }
<template> <template>
<PlacesSidebar {{#if this.mapUi.isSidebarVisible}}
@places={{@model}} <PlacesSidebar
@onSelect={{this.selectPlace}} @places={{this.mapUi.searchResults}}
@onClose={{this.close}} @onSelect={{this.selectPlace}}
/> @onClose={{this.close}}
/>
{{/if}}
</template> </template>
} }
+5
View File
@@ -7,3 +7,8 @@ export function humanizeOsmTag(text) {
w.replace(/^\w/, (c) => c.toUpperCase()) w.replace(/^\w/, (c) => c.toUpperCase())
); );
} }
export function capitalize(text) {
if (typeof text !== 'string' || !text) return '';
return text.charAt(0).toUpperCase() + text.slice(1);
}
+1 -1
View File
@@ -23,7 +23,7 @@ export function getGeohashPrefixesInBbox(bbox) {
// Safety check to avoid infinite loops or massive arrays if bbox is weird // Safety check to avoid infinite loops or massive arrays if bbox is weird
if (Math.abs(maxLat - minLat) > 20 || Math.abs(maxLon - minLon) > 20) { if (Math.abs(maxLat - minLat) > 20 || Math.abs(maxLon - minLon) > 20) {
console.warn( console.debug(
'BBox too large for 4-char geohash scanning, aborting fine scan.' 'BBox too large for 4-char geohash scanning, aborting fine scan.'
); );
return []; return [];
+17 -9
View File
@@ -2,10 +2,11 @@
import activity from 'feather-icons/dist/icons/activity.svg?raw'; import activity from 'feather-icons/dist/icons/activity.svg?raw';
import arrowLeft from 'feather-icons/dist/icons/arrow-left.svg?raw'; import arrowLeft from 'feather-icons/dist/icons/arrow-left.svg?raw';
import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw'; import bookmark from 'feather-icons/dist/icons/bookmark.svg?raw';
import camera from 'feather-icons/dist/icons/camera.svg?raw'; import featherCamera from 'feather-icons/dist/icons/camera.svg?raw';
import checkSquare from 'feather-icons/dist/icons/check-square.svg?raw'; import checkSquare from 'feather-icons/dist/icons/check-square.svg?raw';
import chevronLeft from 'feather-icons/dist/icons/chevron-left.svg?raw'; import chevronLeft from 'feather-icons/dist/icons/chevron-left.svg?raw';
import chevronRight from 'feather-icons/dist/icons/chevron-right.svg?raw'; import chevronRight from 'feather-icons/dist/icons/chevron-right.svg?raw';
import alertTriangle from 'feather-icons/dist/icons/alert-triangle.svg?raw';
import clock from 'feather-icons/dist/icons/clock.svg?raw'; import clock from 'feather-icons/dist/icons/clock.svg?raw';
import database from 'feather-icons/dist/icons/database.svg?raw'; import database from 'feather-icons/dist/icons/database.svg?raw';
import edit from 'feather-icons/dist/icons/edit.svg?raw'; import edit from 'feather-icons/dist/icons/edit.svg?raw';
@@ -22,6 +23,8 @@ import mail from 'feather-icons/dist/icons/mail.svg?raw';
import map from 'feather-icons/dist/icons/map.svg?raw'; import map from 'feather-icons/dist/icons/map.svg?raw';
import mapPin from 'feather-icons/dist/icons/map-pin.svg?raw'; import mapPin from 'feather-icons/dist/icons/map-pin.svg?raw';
import menu from 'feather-icons/dist/icons/menu.svg?raw'; import menu from 'feather-icons/dist/icons/menu.svg?raw';
import moreHorizontal from 'feather-icons/dist/icons/more-horizontal.svg?raw';
import moreVertical from 'feather-icons/dist/icons/more-vertical.svg?raw';
import navigation from 'feather-icons/dist/icons/navigation.svg?raw'; import navigation from 'feather-icons/dist/icons/navigation.svg?raw';
import phone from 'feather-icons/dist/icons/phone.svg?raw'; import phone from 'feather-icons/dist/icons/phone.svg?raw';
import plus from 'feather-icons/dist/icons/plus.svg?raw'; import plus from 'feather-icons/dist/icons/plus.svg?raw';
@@ -45,7 +48,9 @@ import badgeShieldWithFire from '@waysidemapping/pinhead/dist/icons/badge_shield
import beachUmbrellaInGround from '@waysidemapping/pinhead/dist/icons/beach_umbrella_in_ground.svg?raw'; import beachUmbrellaInGround from '@waysidemapping/pinhead/dist/icons/beach_umbrella_in_ground.svg?raw';
import beerMugWithFoam from '@waysidemapping/pinhead/dist/icons/beer_mug_with_foam.svg?raw'; import beerMugWithFoam from '@waysidemapping/pinhead/dist/icons/beer_mug_with_foam.svg?raw';
import burgerAndDrinkCupWithStraw from '@waysidemapping/pinhead/dist/icons/burger_and_drink_cup_with_straw.svg?raw'; import burgerAndDrinkCupWithStraw from '@waysidemapping/pinhead/dist/icons/burger_and_drink_cup_with_straw.svg?raw';
import bridge from '@waysidemapping/pinhead/dist/icons/bridge.svg?raw';
import bus from '@waysidemapping/pinhead/dist/icons/bus.svg?raw'; import bus from '@waysidemapping/pinhead/dist/icons/bus.svg?raw';
import camera from '@waysidemapping/pinhead/dist/icons/camera.svg?raw';
import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.svg?raw'; import boxingGloveUp from '@waysidemapping/pinhead/dist/icons/boxing_glove_up.svg?raw';
import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw'; import car from '@waysidemapping/pinhead/dist/icons/car.svg?raw';
import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw'; import cigaretteWithSmokeCurl from '@waysidemapping/pinhead/dist/icons/cigarette_with_smoke_curl.svg?raw';
@@ -76,12 +81,9 @@ import gravestone from '@waysidemapping/pinhead/dist/icons/gravestone.svg?raw';
import grecianVase from '@waysidemapping/pinhead/dist/icons/grecian_vase.svg?raw'; import grecianVase from '@waysidemapping/pinhead/dist/icons/grecian_vase.svg?raw';
import greekCross from '@waysidemapping/pinhead/dist/icons/greek_cross.svg?raw'; import greekCross from '@waysidemapping/pinhead/dist/icons/greek_cross.svg?raw';
import iceCreamOnCone from '@waysidemapping/pinhead/dist/icons/ice_cream_on_cone.svg?raw'; import iceCreamOnCone from '@waysidemapping/pinhead/dist/icons/ice_cream_on_cone.svg?raw';
import industrialBuilding from '@waysidemapping/pinhead/dist/icons/industrial_building.svg?raw';
import jewel from '@waysidemapping/pinhead/dist/icons/jewel.svg?raw'; import jewel from '@waysidemapping/pinhead/dist/icons/jewel.svg?raw';
import lowriseBuilding from '@waysidemapping/pinhead/dist/icons/lowrise_building.svg?raw'; import lowriseBuilding from '@waysidemapping/pinhead/dist/icons/lowrise_building.svg?raw';
import marketStall from '@waysidemapping/pinhead/dist/icons/market_stall.svg?raw';
import memorialStoneWithInscription from '@waysidemapping/pinhead/dist/icons/memorial_stone_with_inscription.svg?raw';
import mobilePhoneWithKeypadAndAntenna from '@waysidemapping/pinhead/dist/icons/mobile_phone_with_keypad_and_antenna.svg?raw';
import molarTooth from '@waysidemapping/pinhead/dist/icons/molar_tooth.svg?raw';
import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw'; import needleAndSpoolOfThread from '@waysidemapping/pinhead/dist/icons/needle_and_spool_of_thread.svg?raw';
import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw'; import openBook from '@waysidemapping/pinhead/dist/icons/open_book.svg?raw';
import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw'; import palace from '@waysidemapping/pinhead/dist/icons/palace.svg?raw';
@@ -103,6 +105,7 @@ import roundStructureWithFlag from '@waysidemapping/pinhead/dist/icons/round_str
import sailingShipInWater from '@waysidemapping/pinhead/dist/icons/sailing_ship_in_water.svg?raw'; import sailingShipInWater from '@waysidemapping/pinhead/dist/icons/sailing_ship_in_water.svg?raw';
import scissorsOpen from '@waysidemapping/pinhead/dist/icons/scissors_open.svg?raw'; import scissorsOpen from '@waysidemapping/pinhead/dist/icons/scissors_open.svg?raw';
import shipwreckInWater from '@waysidemapping/pinhead/dist/icons/shipwreck_in_water.svg?raw'; import shipwreckInWater from '@waysidemapping/pinhead/dist/icons/shipwreck_in_water.svg?raw';
import steamTrainOnRailwayTrack from '@waysidemapping/pinhead/dist/icons/steam_train_on_railway_track.svg?raw';
import shoppingBag from '@waysidemapping/pinhead/dist/icons/shopping_bag.svg?raw'; import shoppingBag from '@waysidemapping/pinhead/dist/icons/shopping_bag.svg?raw';
import shoppingBasket from '@waysidemapping/pinhead/dist/icons/shopping_basket.svg?raw'; import shoppingBasket from '@waysidemapping/pinhead/dist/icons/shopping_basket.svg?raw';
import shoppingCart from '@waysidemapping/pinhead/dist/icons/shopping_cart.svg?raw'; import shoppingCart from '@waysidemapping/pinhead/dist/icons/shopping_cart.svg?raw';
@@ -112,6 +115,7 @@ import toolbox from '@waysidemapping/pinhead/dist/icons/toolbox.svg?raw';
import treeAndBenchWithBackrest from '@waysidemapping/pinhead/dist/icons/tree_and_bench_with_backrest.svg?raw'; import treeAndBenchWithBackrest from '@waysidemapping/pinhead/dist/icons/tree_and_bench_with_backrest.svg?raw';
import villageBuildings from '@waysidemapping/pinhead/dist/icons/village_buildings.svg?raw'; import villageBuildings from '@waysidemapping/pinhead/dist/icons/village_buildings.svg?raw';
import wallHangingWithMountainsAndSun from '@waysidemapping/pinhead/dist/icons/wall_hanging_with_mountains_and_sun.svg?raw'; import wallHangingWithMountainsAndSun from '@waysidemapping/pinhead/dist/icons/wall_hanging_with_mountains_and_sun.svg?raw';
import windingWayWide from '@waysidemapping/pinhead/dist/icons/winding_way_wide.svg?raw';
import womensAndMensRestroomSymbol from '@waysidemapping/pinhead/dist/icons/womens_and_mens_restroom_symbol.svg?raw'; import womensAndMensRestroomSymbol from '@waysidemapping/pinhead/dist/icons/womens_and_mens_restroom_symbol.svg?raw';
import loadingRing from '../icons/270-ring.svg?raw'; import loadingRing from '../icons/270-ring.svg?raw';
@@ -132,8 +136,10 @@ const ICONS = {
bookmark, bookmark,
'boxing-glove-up': boxingGloveUp, 'boxing-glove-up': boxingGloveUp,
'burger-and-drink-cup-with-straw': burgerAndDrinkCupWithStraw, 'burger-and-drink-cup-with-straw': burgerAndDrinkCupWithStraw,
bridge,
bus, bus,
camera, camera,
'feather-camera': featherCamera,
'check-square': checkSquare, 'check-square': checkSquare,
'chevron-left': chevronLeft, 'chevron-left': chevronLeft,
'chevron-right': chevronRight, 'chevron-right': chevronRight,
@@ -141,6 +147,7 @@ const ICONS = {
climbing_wall: climbingWall, climbing_wall: climbingWall,
check, check,
'alert-circle': alertCircle, 'alert-circle': alertCircle,
'alert-triangle': alertTriangle,
'classical-building': classicalBuilding, 'classical-building': classicalBuilding,
'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag, 'classical-building-with-dome-and-flag': classicalBuildingWithDomeAndFlag,
'classical-building-with-flag': classicalBuildingWithFlag, 'classical-building-with-flag': classicalBuildingWithFlag,
@@ -176,6 +183,7 @@ const ICONS = {
heart, heart,
home, home,
'ice-cream-on-cone': iceCreamOnCone, 'ice-cream-on-cone': iceCreamOnCone,
'industrial-building': industrialBuilding,
info, info,
instagram, instagram,
jewel, jewel,
@@ -185,11 +193,9 @@ const ICONS = {
mail, mail,
map, map,
'map-pin': mapPin, 'map-pin': mapPin,
'market-stall': marketStall,
'memorial-stone-with-inscription': memorialStoneWithInscription,
menu, menu,
'mobile-phone-with-keypad-and-antenna': mobilePhoneWithKeypadAndAntenna, 'more-horizontal': moreHorizontal,
'molar-tooth': molarTooth, 'more-vertical': moreVertical,
navigation, navigation,
'needle-and-spool-of-thread': needleAndSpoolOfThread, 'needle-and-spool-of-thread': needleAndSpoolOfThread,
nostrich, nostrich,
@@ -216,6 +222,7 @@ const ICONS = {
'sailing-ship-in-water': sailingShipInWater, 'sailing-ship-in-water': sailingShipInWater,
'scissors-open': scissorsOpen, 'scissors-open': scissorsOpen,
'shipwreck-in-water': shipwreckInWater, 'shipwreck-in-water': shipwreckInWater,
'steam-train-on-railway-track': steamTrainOnRailwayTrack,
'shopping-bag': shoppingBag, 'shopping-bag': shoppingBag,
search, search,
server, server,
@@ -235,6 +242,7 @@ const ICONS = {
'womens-and-mens-restroom-symbol': womensAndMensRestroomSymbol, 'womens-and-mens-restroom-symbol': womensAndMensRestroomSymbol,
whatsapp, whatsapp,
wikipedia, wikipedia,
winding_way_wide: windingWayWide,
parking_p: parkingP, parking_p: parkingP,
car, car,
x, x,
+8
View File
@@ -30,6 +30,11 @@ export function parsePlacePhotos(events) {
const allPhotos = []; const allPhotos = [];
for (const event of sortedEvents) { for (const event of sortedEvents) {
const eventTagValues = event.tags
.filter((t) => t[0] === 't')
.map((t) => t[1])
.filter(Boolean);
// Find all imeta tags // Find all imeta tags
const imetas = event.tags.filter((t) => t[0] === 'imeta'); const imetas = event.tags.filter((t) => t[0] === 'imeta');
for (const imeta of imetas) { for (const imeta of imetas) {
@@ -38,6 +43,7 @@ export function parsePlacePhotos(events) {
let blurhash = null; let blurhash = null;
let isLandscape = false; let isLandscape = false;
let aspectRatio = 16 / 9; // default let aspectRatio = 16 / 9; // default
let placeIdentifier = event.tags.find((t) => t[0] === 'i')?.[1];
for (const tag of imeta.slice(1)) { for (const tag of imeta.slice(1)) {
if (tag.startsWith('url ')) { if (tag.startsWith('url ')) {
@@ -68,6 +74,8 @@ export function parsePlacePhotos(events) {
blurhash, blurhash,
isLandscape, isLandscape,
aspectRatio, aspectRatio,
placeIdentifier,
tags: eventTagValues,
}); });
} }
} }
+9
View File
@@ -109,6 +109,9 @@ export const POI_ICON_RULES = [
{ tags: { amenity: 'arts_center' }, icon: 'comedy-mask-and-tragedy-mask' }, { tags: { amenity: 'arts_center' }, icon: 'comedy-mask-and-tragedy-mask' },
// Historic // Historic
{ tags: { historic: 'canal' }, icon: 'winding_way_wide' },
{ tags: { historic: 'bridge' }, icon: 'bridge' },
{ tags: { historic: 'bridge_site' }, icon: 'bridge' },
{ tags: { historic: 'fort' }, icon: 'fort' }, { tags: { historic: 'fort' }, icon: 'fort' },
{ tags: { historic: 'castle' }, icon: 'palace' }, { tags: { historic: 'castle' }, icon: 'palace' },
{ tags: { historic: 'building' }, icon: 'classical-building-with-flag' }, { tags: { historic: 'building' }, icon: 'classical-building-with-flag' },
@@ -119,6 +122,12 @@ export const POI_ICON_RULES = [
tags: { historic: 'monument' }, tags: { historic: 'monument' },
icon: 'classical-building-with-dome-and-flag', icon: 'classical-building-with-dome-and-flag',
}, },
{ tags: { historic: 'folly' }, icon: 'classical-building' },
{ tags: { historic: 'industrial' }, icon: 'industrial-building' },
{
tags: { historic: 'railway_station' },
icon: 'steam-train-on-railway-track',
},
{ tags: { historic: 'ship' }, icon: 'sailing-ship-in-water' }, { tags: { historic: 'ship' }, icon: 'sailing-ship-in-water' },
{ tags: { historic: 'wreck' }, icon: 'shipwreck-in-water' }, { tags: { historic: 'wreck' }, icon: 'shipwreck-in-water' },
{ tags: { historic: 'ruins' }, icon: 'camera' }, { tags: { historic: 'ruins' }, icon: 'camera' },
+32
View File
@@ -0,0 +1,32 @@
import { POI_CATEGORIES } from './poi-categories';
import { getMatchingPoiCategoryIds } from './poi-category-matcher';
export const CATEGORY_TAGS = {
restaurants: ['food', 'menu', 'vibe', 'front'],
coffee: ['food', 'menu', 'vibe', 'front'],
groceries: ['front', 'food'],
'things-to-do': ['architecture', 'amenities', 'vibe', 'front'],
accommodation: ['rooms', 'amenities', 'food', 'vibe', 'front'],
};
export function getSuggestedPhotoTags(place) {
const osmTags = place?.osmTags || place?.tags || {};
const categoryIds = getMatchingPoiCategoryIds(osmTags, POI_CATEGORIES);
const suggested = [];
for (const categoryId of categoryIds) {
const tags = CATEGORY_TAGS[categoryId];
if (!Array.isArray(tags)) continue;
for (const tag of tags) {
if (!suggested.includes(tag)) {
suggested.push(tag);
}
}
}
if (suggested.length === 0) {
return [];
}
return suggested;
}
+1 -1
View File
@@ -41,7 +41,7 @@ export const POI_CATEGORIES = [
{ {
id: 'things-to-do', id: 'things-to-do',
label: 'Things to do', label: 'Things to do',
icon: 'camera', icon: 'feather-camera',
filter: [ filter: [
'["tourism"~"^(museum|gallery|attraction|viewpoint|zoo|theme_park|aquarium|artwork)$"]', '["tourism"~"^(museum|gallery|attraction|viewpoint|zoo|theme_park|aquarium|artwork)$"]',
'["amenity"~"^(cinema|theatre|arts_centre|planetarium)$"]', '["amenity"~"^(cinema|theatre|arts_centre|planetarium)$"]',
+95
View File
@@ -0,0 +1,95 @@
export function getMatchingPoiCategories(osmTags, categories) {
if (!Array.isArray(categories) || !osmTags) return [];
return categories.filter((category) => {
if (!Array.isArray(category.filter)) return false;
return category.filter.some((filterStr) =>
matchesFilter(osmTags, filterStr)
);
});
}
export function getMatchingPoiCategoryIds(osmTags, categories) {
return getMatchingPoiCategories(osmTags, categories).map((c) => c.id);
}
function matchesFilter(osmTags, filterStr) {
const clauses = parseOverpassClauses(filterStr);
if (clauses.length === 0) return false;
return clauses.every((clause) => matchesClause(osmTags, clause));
}
function parseOverpassClauses(filterStr) {
if (!filterStr) return [];
const matches = filterStr.match(/\[[^\]]+\]/g);
if (!matches) return [];
return matches
.map((raw) => parseClause(raw.slice(1, -1).trim()))
.filter(Boolean);
}
function parseClause(content) {
const presenceMatch = content.match(/^"([^"]+)"$/);
if (presenceMatch) {
return { type: 'presence', key: presenceMatch[1] };
}
const equalsMatch = content.match(/^"([^"]+)"\s*=\s*"([^"]*)"$/);
if (equalsMatch) {
return { type: 'equals', key: equalsMatch[1], value: equalsMatch[2] };
}
const regexMatch = content.match(/^"([^"]+)"\s*~\s*"([^"]*)"$/);
if (regexMatch) {
return {
type: 'regex',
key: regexMatch[1],
pattern: regexMatch[2],
regex: new RegExp(regexMatch[2]),
};
}
const notRegexMatch = content.match(/^"([^"]+)"\s*!~\s*"([^"]*)"$/);
if (notRegexMatch) {
return {
type: 'not-regex',
key: notRegexMatch[1],
pattern: notRegexMatch[2],
regex: new RegExp(notRegexMatch[2]),
};
}
return null;
}
function matchesClause(osmTags, clause) {
const tagValues = getTagValues(osmTags, clause.key);
switch (clause.type) {
case 'presence':
return tagValues.length > 0;
case 'equals':
return tagValues.some((value) => value === clause.value);
case 'regex':
return tagValues.some((value) => clause.regex.test(value));
case 'not-regex':
return (
tagValues.length === 0 ||
!tagValues.some((value) => clause.regex.test(value))
);
default:
return false;
}
}
function getTagValues(osmTags, key) {
if (!osmTags || !key) return [];
const rawValue = osmTags[key];
if (rawValue === undefined || rawValue === null) return [];
return String(rawValue)
.split(';')
.map((value) => value.trim())
.filter(Boolean);
}
+1
View File
@@ -42,6 +42,7 @@
<link rel="stylesheet" href="/app/styles/app.css"> <link rel="stylesheet" href="/app/styles/app.css">
</head> </head>
<body> <body>
<div id="modal-portal"></div>
<script type="module"> <script type="module">
import Application from './app/app'; import Application from './app/app';
import environment from './app/config/environment'; import environment from './app/config/environment';
+2 -2
View File
@@ -1,6 +1,6 @@
{ {
"name": "marco", "name": "marco",
"version": "1.20.3", "version": "1.24.0",
"private": true, "private": true,
"description": "Unhosted maps app", "description": "Unhosted maps app",
"repository": { "repository": {
@@ -88,7 +88,7 @@
"qunit": "^2.25.0", "qunit": "^2.25.0",
"qunit-dom": "^3.5.0", "qunit-dom": "^3.5.0",
"remotestorage-widget": "^1.8.1", "remotestorage-widget": "^1.8.1",
"remotestoragejs": "2.0.0-beta.8", "remotestoragejs": "2.0.0-beta.9",
"sinon": "^21.0.1", "sinon": "^21.0.1",
"stylelint": "^16.26.1", "stylelint": "^16.26.1",
"stylelint-config-standard": "^38.0.0", "stylelint-config-standard": "^38.0.0",
+9 -28
View File
@@ -196,8 +196,8 @@ importers:
specifier: ^1.8.1 specifier: ^1.8.1
version: 1.8.1 version: 1.8.1
remotestoragejs: remotestoragejs:
specifier: 2.0.0-beta.8 specifier: 2.0.0-beta.9
version: 2.0.0-beta.8 version: 2.0.0-beta.9
sinon: sinon:
specifier: ^21.0.1 specifier: ^21.0.1
version: 21.0.1 version: 21.0.1
@@ -1720,9 +1720,6 @@ packages:
'@types/ms@2.1.0': '@types/ms@2.1.0':
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
'@types/node@20.14.0':
resolution: {integrity: sha512-5cHBxFGJx6L4s56Bubp4fglrEpmyJypsqI6RgzMfBHWUJQGWAAi8cWcgetEbZXHYXo9C2Fa4EEds/uSyS4cxmA==}
'@types/node@25.0.7': '@types/node@25.0.7':
resolution: {integrity: sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==} resolution: {integrity: sha512-C/er7DlIZgRJO7WtTdYovjIFzGsz0I95UlMyR9anTb4aCpBSRWe5Jc1/RvLKUfzmOxHPGjSE5+63HgLtndxU4w==}
@@ -1735,9 +1732,6 @@ packages:
'@types/symlink-or-copy@1.2.2': '@types/symlink-or-copy@1.2.2':
resolution: {integrity: sha512-MQ1AnmTLOncwEf9IVU+B2e4Hchrku5N67NkgcAHW0p3sdzPe0FNMANxEm6OJUzPniEQGkeT3OROLlCwZJLWFZA==} resolution: {integrity: sha512-MQ1AnmTLOncwEf9IVU+B2e4Hchrku5N67NkgcAHW0p3sdzPe0FNMANxEm6OJUzPniEQGkeT3OROLlCwZJLWFZA==}
'@types/tv4@1.2.33':
resolution: {integrity: sha512-7phCVTXC6Bj50IV1iKOwqGkR4JONJyMbRZnKTSuujv1S/tO9rG5OdCt7BMSjytO+zJmYdn1/I4fd3SH0gtO99g==}
'@types/unist@3.0.3': '@types/unist@3.0.3':
resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==}
@@ -4906,8 +4900,8 @@ packages:
remotestorage-widget@1.8.1: remotestorage-widget@1.8.1:
resolution: {integrity: sha512-HxNu2VvIRW3wzkf5fLEzs56ySQ7+YQbRqyp3CKvmw/G+zKhRsmj06HtFoAcm3B14/nJh2SOAv3LyfKuXfUsKPw==} resolution: {integrity: sha512-HxNu2VvIRW3wzkf5fLEzs56ySQ7+YQbRqyp3CKvmw/G+zKhRsmj06HtFoAcm3B14/nJh2SOAv3LyfKuXfUsKPw==}
remotestoragejs@2.0.0-beta.8: remotestoragejs@2.0.0-beta.9:
resolution: {integrity: sha512-rtyHTG2VbtiKTRmbwjponRf5VTPJMcHv/ijNid1zX48C0Z0F8ZCBBfkKD2QCxTQyQvCupkWNy3wuIu4HE+AEng==} resolution: {integrity: sha512-d09ByL7ecbZLMuzl4mQ3SXMFlsCwvvINm6l1CfdR8ylvX9E1nsq44t8gmRxzW6GUS5cwonyYA4FRXYKEhARjTA==}
remove-types@1.0.0: remove-types@1.0.0:
resolution: {integrity: sha512-G7Hk1Q+UJ5DvlNAoJZObxANkBZGiGdp589rVcTW/tYqJWJ5rwfraSnKSQaETN8Epaytw8J40nS/zC7bcHGv36w==} resolution: {integrity: sha512-G7Hk1Q+UJ5DvlNAoJZObxANkBZGiGdp589rVcTW/tYqJWJ5rwfraSnKSQaETN8Epaytw8J40nS/zC7bcHGv36w==}
@@ -5578,9 +5572,6 @@ packages:
underscore@1.13.7: underscore@1.13.7:
resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==} resolution: {integrity: sha512-GMXzWtsc57XAtguZgaQViUOzs0KTkk8ojr3/xAxXLITqf/3EMwxC0inyETfDFjH/Krbhuep0HNbbjI9i/q3F3g==}
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
undici-types@7.16.0: undici-types@7.16.0:
resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==}
@@ -5747,8 +5738,8 @@ packages:
web-worker@1.5.0: web-worker@1.5.0:
resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==} resolution: {integrity: sha512-RiMReJrTAiA+mBjGONMnjVDP2u3p9R1vkcGz6gDIrOMT3oGuYwX2WRMYI9ipkphSuE5XKEhydbhNEJh4NY9mlw==}
webfinger.js@2.8.2: webfinger.js@3.0.4:
resolution: {integrity: sha512-Zqn9KXkGrD1tVEm029bVUIfmzef2KXs3G7OZrdqehDHtgv9YSxX1oy4RoPoMk2PHWIifwWCA0xwKZOAZqXMpfg==} resolution: {integrity: sha512-5c15N1n4qCm/jGJjUt32mBdPVlSugLbAztIDNBpuDfukGz2E9NhmXPfLikayn2p3kcgEZsI/UOdOwVpxOr8qJA==}
webidl-conversions@7.0.0: webidl-conversions@7.0.0:
resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==} resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
@@ -7648,10 +7639,6 @@ snapshots:
'@types/ms@2.1.0': {} '@types/ms@2.1.0': {}
'@types/node@20.14.0':
dependencies:
undici-types: 5.26.5
'@types/node@25.0.7': '@types/node@25.0.7':
dependencies: dependencies:
undici-types: 7.16.0 undici-types: 7.16.0
@@ -7665,8 +7652,6 @@ snapshots:
'@types/symlink-or-copy@1.2.2': {} '@types/symlink-or-copy@1.2.2': {}
'@types/tv4@1.2.33': {}
'@types/unist@3.0.3': {} '@types/unist@3.0.3': {}
'@typescript-eslint/tsconfig-utils@8.53.0(typescript@5.9.3)': '@typescript-eslint/tsconfig-utils@8.53.0(typescript@5.9.3)':
@@ -11530,13 +11515,11 @@ snapshots:
remotestorage-widget@1.8.1: {} remotestorage-widget@1.8.1: {}
remotestoragejs@2.0.0-beta.8: remotestoragejs@2.0.0-beta.9:
dependencies: dependencies:
'@types/node': 20.14.0
'@types/tv4': 1.2.33
esm: 3.2.25 esm: 3.2.25
tv4: 1.3.0 tv4: 1.3.0
webfinger.js: 2.8.2 webfinger.js: 3.0.4
xhr2: 0.2.1 xhr2: 0.2.1
optionalDependencies: optionalDependencies:
fsevents: 2.3.3 fsevents: 2.3.3
@@ -12416,8 +12399,6 @@ snapshots:
underscore@1.13.7: {} underscore@1.13.7: {}
undici-types@5.26.5: {}
undici-types@7.16.0: {} undici-types@7.16.0: {}
unicode-canonical-property-names-ecmascript@2.0.1: {} unicode-canonical-property-names-ecmascript@2.0.1: {}
@@ -12569,7 +12550,7 @@ snapshots:
web-worker@1.5.0: {} web-worker@1.5.0: {}
webfinger.js@2.8.2: {} webfinger.js@3.0.4: {}
webidl-conversions@7.0.0: {} webidl-conversions@7.0.0: {}
-1
View File
@@ -1 +0,0 @@
:root{--default-list-color: #fc3;--hover-bg: #f8f9fa;--sidebar-width: 350px;--link-color: #2a7fff;--link-color-visited: #6a4fbf;--marker-color-primary: #ea4335;--marker-color-dark: #b31412;--danger-color: var(--marker-color-primary);--danger-color-dark: var(--marker-color-dark)}html,body{height:100%;overscroll-behavior:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}button{-webkit-tap-highlight-color:transparent}body{margin:0;font-family:Noto Sans,sans-serif;font-size:16px;font-weight:400;color:#333}#root,#ember-testing{height:100%}.map-container{background:#f8f9fa;-webkit-tap-highlight-color:transparent;outline:none;position:absolute;inset:0}#rs-widget-container{position:absolute;top:60px;right:10px;z-index:4000;display:none}#rs-widget-container.visible{display:block}.rs-backdrop{position:fixed;inset:0;z-index:3999}.app-header{position:absolute;top:0;left:0;right:0;height:60px;padding:.5rem 1rem;z-index:3000;pointer-events:none;display:grid;grid-template-columns:1fr auto 1fr;grid-template-areas:"search chips user";align-items:center;gap:1rem}@media(max-width:768px){.app-header{padding:.5rem .5rem 0;height:auto;grid-template-columns:1fr auto;grid-template-areas:"search user" "chips chips";row-gap:8px}}.header-left,.header-right,.header-center{pointer-events:auto}.header-left{display:flex;align-items:center;grid-area:search;justify-self:start;width:100%}@media not (max-width:768px){.header-left{min-width:300px;max-width:400px}}@media not (max-width:768px){.header-left{min-width:300px;max-width:350px}}.header-right{grid-area:user;justify-self:end}.header-center{grid-area:chips;display:flex;justify-content:center;min-width:0}@media not (max-width:768px){.header-center .category-chips-scroll{width:auto;max-width:100%}}@media(max-width:768px){.header-center{width:100%;overflow:hidden;justify-content:start}.header-center.searching{display:none}}.btn-press{transition:transform .1s}.btn-press:active{transform:scale(.95)}.user-btn{background:none;border:none;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center}.user-avatar-placeholder{width:40px;height:40px;background:#2a3743;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0}.user-avatar{width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0;display:block}.photo-preview-img{max-width:100%;height:auto}.dropzone{border:2px dashed #ccc;border-radius:8px;text-align:center;transition:all .2s ease;margin:1.5rem 0 1rem;background-color:#ffffff05;cursor:pointer;aspect-ratio:4 / 3}.dropzone.is-dragging{border-color:#61afef;background-color:#61afef0d}.dropzone-label{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;cursor:pointer;color:#898989;width:100%;height:100%}.dropzone-label p{margin:0}.file-input-hidden{display:none}.photo-grid{display:flex;flex-direction:column;gap:12px;margin-bottom:20px}.photo-upload-item{position:relative;aspect-ratio:4 / 3;border-radius:6px;overflow:hidden;background:#1e262e;width:100%}.photo-upload-item img{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain;display:block;z-index:1}.photo-upload-item .overlay,.photo-upload-item .btn-remove-photo{z-index:2}.photo-upload-item .overlay{position:absolute;inset:0;background:#0009;display:flex;flex-direction:column;align-items:center;justify-content:center}.upload-status-text{color:#fff;margin-top:1rem;font-size:.9rem;text-shadow:0 1px 3px rgb(0 0 0 / 80%);text-align:center;padding:0 1rem}.photo-upload-item .error-overlay{background:#e06c75cc;cursor:pointer;border:none;padding:0;margin:0;width:100%}.photo-upload-item .btn-remove-photo{position:absolute;top:4px;right:4px;background:#000000b3;border:none;border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#fff;padding:0}.photo-upload-item .btn-remove-photo:hover{background:var(--danger-color)}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.btn-publish{width:100%}.user-menu-container{position:relative}.user-menu-popover{position:absolute;top:100%;right:0;margin-top:10px;width:280px;background:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:1rem;z-index:3001}.menu-backdrop{position:fixed;inset:0;z-index:3000}.account-list{list-style:none;padding:0;margin:0}.account-item{display:flex;flex-direction:column;padding:1rem 0;border-bottom:1px solid #eee}.account-item:last-child{border-bottom:none;padding-bottom:0}.account-item:first-child{padding-top:0}.account-header{display:flex;justify-content:space-between;align-items:center}.account-status{font-size:.85rem;color:#898989;margin-top:.35rem;margin-left:calc(18px + .75rem);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.account-status strong{font-weight:600}.account-item.disabled{opacity:.5;pointer-events:none}.account-iLine truncated
+25
View File
@@ -0,0 +1,25 @@
const amdModule0=Object.freeze(Object.defineProperty({__proto__:null,get default(){return App}},Symbol.toStringTag,{value:"Module"}));!function(){const e=document.createElement("link").relList;if(!(e&&e.supports&&e.supports("modulepreload"))){for(const e of document.querySelectorAll('link[rel="modulepreload"]'))t(e);new MutationObserver(e=>{for(const r of e)if("childList"===r.type)for(const e of r.addedNodes)"LINK"===e.tagName&&"modulepreload"===e.rel&&t(e)}).observe(document,{childList:!0,subtree:!0})}function t(e){if(e.ep)return;e.ep=!0;const t=function(e){const t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),"use-credentials"===e.crossOrigin?t.credentials="include":"anonymous"===e.crossOrigin?t.credentials="omit":t.credentials="same-origin",t}(e);fetch(e.href,t)}}();const hasDOM="object"==typeof self&&null!==self&&self.Object===Object&&"undefined"!=typeof Window&&self.constructor===Window&&"object"==typeof document&&null!==document&&self.document===document&&"object"==typeof location&&null!==location&&self.location===location&&"object"==typeof history&&null!==history&&self.history===history&&"object"==typeof navigator&&null!==navigator&&self.navigator===navigator&&"string"==typeof navigator.userAgent,window$1=hasDOM?self:null,location$1=hasDOM?self.location:null,history$1=hasDOM?self.history:null,userAgent=hasDOM?self.navigator.userAgent:"Lynx (textmode)",isChrome=!(!hasDOM||"object"!=typeof chrome||"object"==typeof opera),isFirefox=!!hasDOM&&/Firefox|FxiOS/.test(userAgent),environment=Object.freeze(Object.defineProperty({__proto__:null,hasDOM:hasDOM,history:history$1,isChrome:isChrome,isFirefox:isFirefox,location:location$1,userAgent:userAgent,window:window$1},Symbol.toStringTag,{value:"Module"}));function checkGlobal(e){return e&&e.Object===Object?e:void 0}function checkElementIdShadowing(e){return e&&void 0===e.nodeType?e:void 0}const global$1=checkGlobal(checkElementIdShadowing("object"==typeof global&&global))||checkGlobal("object"==typeof self&&self)||checkGlobal("object"==typeof window&&window)||"undefined"!=typeof mainContext&&mainContext||new Function("return this")(),context$1=function(e,t){return void 0===t?{imports:e,exports:e,lookup:e}:{imports:t.imports||e,exports:t.exports||e,lookup:t.lookup||e}}(global$1,global$1.Ember);function getLookup(){return context$1.lookup}function setLookup(e){context$1.lookup=e}const ENV$1={ENABLE_OPTIONAL_FEATURES:!1,EXTEND_PROTOTYPES:{Array:!1},LOG_STACKTRACE_ON_DEPRECATION:!0,LOG_VERSION:!0,RAISE_ON_DEPRECATION:!1,STRUCTURED_PROFILE:!1,_DEBUG_RENDER_TREE:!1,_ALL_DEPRECATIONS_ENABLED:!1,_OVERRIDE_DEPRECATION_VERSION:null,_DEFAULT_ASYNC_OBSERVERS:!1,_RERENDER_LOOP_LIMIT:1e3,EMBER_LOAD_HOOKS:{},FEATURES:{}};function getENV(){return ENV$1}(e=>{if("object"!=typeof e||null===e)return;for(let n in e){if(!Object.prototype.hasOwnProperty.call(e,n)||"EXTEND_PROTOTYPES"===n||"EMBER_LOAD_HOOKS"===n)continue;let t=ENV$1[n];ENV$1[n]=!0===t?!1!==e[n]:!1===t?!0===e[n]:e[n]}let{EMBER_LOAD_HOOKS:t}=e;if("object"==typeof t&&null!==t)for(let n in t){if(!Object.prototype.hasOwnProperty.call(t,n))continue;let e=t[n];Array.isArray(e)&&(ENV$1.EMBER_LOAD_HOOKS[n]=e.filter(e=>"function"==typeof e))}let{FEATURES:r}=e;if("object"==typeof r&&null!==r)for(let n in r)Object.prototype.hasOwnProperty.call(r,n)&&(ENV$1.FEATURES[n]=!0===r[n])})(global$1.EmberENV);let assert$3=()=>{},registerHandler$1=()=>{},deprecate$1=()=>{};const defaultDeprecate=deprecate$1;let testing=!1;function isTesting(){return testing}function setTesting(e){testing=Boolean(e)}let registerHandler=()=>{};const{toString:objectToString$1}=Object.prototype,{toString:functionToString}=Function.prototype,{isArray:isArray$6}=Array,{keys:objectKeys}=Object,{stringify:stringify}=JSON,LIST_LIMIT=100,DEPTH_LIMIT=4,SAFE_KEY=/^[\w$]+$/;function inspect(e){return"number"==typeof e&&2===arguments.length?this:inspectValue(e,0)}function inspectValue(e,t,r){let n=!1;switch(typeof e){case"undefined":return"undefined";case"object":if(null===e)return"null";if(isArray$6(e)){n=!0;break}if(e.toString===objectToString$1||void 0===e.toString)break;return e.toString();case"function":return e.toString===functionToString?e.name?`[Function:${e.name}]`:"[Function]":e.toString();case"string":return stringify(e);default:return e.toString()}if(void 0===r)r=new WeakSet;else if(r.has(e))return"[Circular]";return r.add(e),n?inspectArray(e,t+1,r):inspectObject(e,t+1,r)}function inspectKey(e){return SAFE_KEY.test(e)?e:stringify(e)}function inspectObject(e,t,r){if(t>DEPTH_LIMIT)return"[Object]";let n="{",i=objectKeys(e);for(let o=0;o<i.length;o++){if(n+=0===o?" ":", ",o>=LIST_LIMIT){n+=`... ${i.length-LIST_LIMIT} more keys`;break}let s=i[o];n+=`${inspectKey(String(s))}: ${inspectValue(e[s],t,r)}`}return n+=" }",n}function inspectArray(e,t,r){if(t>DEPTH_LIMIT)return"[Array]";let n="[";for(let i=0;i<e.length;i++){if(n+=0===i?" ":", ",i>=LIST_LIMIT){n+=`... ${e.length-LIST_LIMIT} more items`;break}n+=insLine truncated
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */if(!isLE$3)throw new Error("Non little-endian hardware is not supported");function utf8ToBytes$2(e){if("string"!=typeof e)throw new Error("utf8ToBytes expected string, got "+typeof e);return new Uint8Array((new TextEncoder).encode(e))}function toBytes$1(e){if("string"==typeof e&&(e=utf8ToBytes$2(e)),!u8a$2(e))throw new Error("expected Uint8Array, got "+typeof e);return e}function concatBytes$3(...e){const t=new Uint8Array(e.reduce((e,t)=>e+t.length,0));let r=0;return e.forEach(e=>{if(!u8a$2(e))throw new Error("Uint8Array expected");t.set(e,r),r+=e.length}),t}let Hash$1=class{clone(){return this._cloneInto()}};function wrapConstructor$1(e){const t=t=>e().update(toBytes$1(t)).digest(),r=e();return t.outputLen=r.outputLen,t.blockLen=r.blockLen,t.create=()=>e(),t}function randomBytes$2(e=32){if(crypto$2&&"function"==typeof crypto$2.getRandomValues)return crypto$2.getRandomValues(new Uint8Array(e));throw new Error("crypto.getRandomValues must be defined")}function setBigUint64$1(e,t,r,n){if("function"==typeof e.setBigUint64)return e.setBigUint64(t,r,n);const i=BigInt(32),o=BigInt(4294967295),s=Number(r>>i&o),a=Number(r&o),l=n?4:0,c=n?0:4;e.setUint32(t+l,s,n),e.setUint32(t+c,a,n)}let SHA2$1=class extends Hash$1{constructor(e,t,r,n){super(),this.blockLen=e,this.outputLen=t,this.padOffset=r,this.isLE=n,this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.buffer=new Uint8Array(e),this.view=createView$3(this.buffer)}update(e){exists$1(this);const{view:t,buffer:r,blockLen:n}=this,i=(e=toBytes$1(e)).length;for(let o=0;o<i;){const s=Math.min(n-this.pos,i-o);if(s===n){const t=createView$3(e);for(;n<=i-o;o+=n)this.process(t,o);continue}r.set(e.subarray(o,o+s),this.pos),this.pos+=s,o+=s,this.pos===n&&(this.process(t,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){exists$1(this),output$1(e,this),this.finished=!0;const{buffer:t,view:r,blockLen:n,isLE:i}=this;let{pos:o}=this;t[o++]=128,this.buffer.subarray(o).fill(0),this.padOffset>n-o&&(this.process(r,0),o=0);for(let u=o;u<n;u++)t[u]=0;setBigUint64$1(r,n-8,BigInt(8*this.length),i),this.process(r,0);const s=createView$3(e),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const l=a/4,c=this.get();if(l>c.length)throw new Error("_sha2: outputLen bigger than state");for(let u=0;u<l;u++)s.setUint32(4*u,c[u],i)}digest(){const{buffer:e,outputLen:t}=this;this.digestInto(e);const r=e.slice(0,t);return this.destroy(),r}_cloneInto(e){e||(e=new this.constructor),e.set(...this.get());const{blockLen:t,buffer:r,length:n,finished:i,destroyed:o,pos:s}=this;return e.length=n,e.pos=s,e.finished=i,e.destroyed=o,n%t&&e.buffer.set(r),e}};const Chi$3=(e,t,r)=>e&t^~e&r,Maj$3=(e,t,r)=>e&t^e&r^t&r,SHA256_K$3=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),IV$1=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),SHA256_W$3=new Uint32Array(64);let SHA256$1=class extends SHA2$1{constructor(){super(64,32,8,!1),this.A=0|IV$1[0],this.B=0|IV$1[1],this.C=0|IV$1[2],this.D=0|IV$1[3],this.E=0|IV$1[4],this.F=0|IV$1[5],this.G=0|IV$1[6],this.H=0|IV$1[7]}get(){const{A:e,B:t,C:r,D:n,E:i,F:o,G:s,H:a}=this;return[e,t,r,n,i,o,s,a]}set(e,t,r,n,i,o,s,a){this.A=0|e,this.B=0|t,this.C=0|r,this.D=0|n,this.E=0|i,this.F=0|o,this.G=0|s,this.H=0|a}process(e,t){for(let u=0;u<16;u++,t+=4)SHA256_W$3[u]=e.getUint32(t,!1);for(let u=16;u<64;u++){const e=SHA256_W$3[u-15],t=SHA256_W$3[u-2],r=rotr$3(e,7)^rotr$3(e,18)^e>>>3,n=rotr$3(t,17)^rotr$3(t,19)^t>>>10;SHA256_W$3[u]=n+SHA256_W$3[u-7]+r+SHA256_W$3[u-16]|0}let{A:r,B:n,C:i,D:o,E:s,F:a,G:l,H:c}=this;for(let u=0;u<64;u++){const e=c+(rotr$3(s,6)^rotr$3(s,11)^rotr$3(s,25))+Chi$3(s,a,l)+SHA256_K$3[u]+SHA256_W$3[u]|0,t=(rotr$3(r,2)^rotr$3(r,13)^rotr$3(r,22))+Maj$3(r,n,i)|0;c=l,l=a,a=s,s=o+e|0,o=i,i=n,n=r,r=e+t|0}r=r+this.A|0,n=n+this.B|0,i=i+this.C|0,o=o+this.D|0,s=s+this.E|0,a=a+this.F|0,l=l+this.G|0,c=c+this.H|0,this.set(r,n,i,o,s,a,l,c)}roundClean(){SHA256_W$3.fill(0)}destroy(){this.set(0,0,0,0,0,0,0,0),this.buffer.fill(0)}};const sha256$3=wrapConstructor$1(()=>new SHA256$1),_0n$a=BigInt(0),_1n$9=BigInt(1),_2n$6=BigInt(2),u8a$1=e=>e instanceof Uint8Array,hexes$4=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function bytesToHex$4(e){if(!u8a$1(e))throw new Error("Uint8Array expected");let t="";for(let r=0;r<e.length;r++)t+=hexes$4[e[r]];return t}function numberToHexUnpadded$2(e){const t=e.toString(16);return 1&t.length?`0${t}`:t}function hexToNumber$2(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);return BigInt(""===e?"0":`0x${e}`)}function hexToBytes$4(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);const t=e.length;if(t%2)throw new Error("padded hex string expected, got unpadded hex of length "+t);const r=new Uint8Array(t/2);for(let n=0;n<r.length;n++){const t=2*n,i=e.slice(t,t+2),o=Number.parseInt(i,16);if(Number.isNaN(o)||o<0)throw new Error("Invalid byte sequence");r[n]=o}return r}function bytesToNumberBE$1(e){return hexToNumber$2(bytesToHex$4(e))}function bytesToNumberLE$1(e){if(!u8a$1(e))throw new Error("Uint8Array expected");return hexToNumber$2(bytesToHex$4(Uint8Array.from(e).reverse()))}function numberToBytesBE$1(e,t){return hexToBytes$4(e.toString(16).padStart(2*t,"0"))}function numberToBytesLE$1(e,t){return numberToBytesBE$1(e,t).reverse()}function numberToVarBytesBE(e){return hexToBytes$4(numberToHexUnpadded$2(e))}function ensureBytes$1(e,t,r){let n;if("string"==typeof t)try{n=hexToBytes$4(t)}catch(o){throw new Error(`${e} must be valid hex string, got "${t}". Cause: ${o}`)}else{if(!u8a$1(t))throw new Error(`${e} must be hex string or Uint8Array`);n=Uint8Array.from(t)}const i=n.length;if("number"==typeof r&&i!==r)throw new Error(`${e} expected ${r} bytes, got ${i}`);return n}function concatBytes$2(...e){const t=new Uint8Array(e.reduce((e,t)=>e+t.length,0));let r=0;return e.forEach(e=>{if(!u8a$1(e))throw new Error("Uint8Array expected");t.set(e,r),r+=e.length}),t}function equalBytes$2(e,t){if(e.length!==t.length)return!1;for(let r=0;r<e.length;r++)if(e[r]!==t[r])return!1;return!0}function utf8ToBytes$1(e){if("string"!=typeof e)throw new Error("utf8ToBytes expected string, got "+typeof e);return new Uint8Array((new TextEncoder).encode(e))}function bitLen$1(e){let t;for(t=0;e>_0n$a;e>>=_1n$9,t+=1);return t}function bitGet(e,t){return e>>BigInt(t)&_1n$9}const bitSet=(e,t,r)=>e|(r?_1n$9:_0n$a)<<BigInt(t),bitMask$1=e=>(_2n$6<<BigInt(e-1))-_1n$9,u8n=e=>new Uint8Array(e),u8fr=e=>Uint8Array.from(e);function createHmacDrbg$1(e,t,r){if("number"!=typeof e||e<2)throw new Error("hashLen must be a number");if("number"!=typeof t||t<2)throw new Error("qByteLen must be a number");if("function"!=typeof r)throw new Error("hmacFn must be a function");let n=u8n(e),i=u8n(e),o=0;const s=()=>{n.fill(1),i.fill(0),o=0},a=(...e)=>r(i,n,...e),l=(e=u8n())=>{i=a(u8fr([0]),e),n=a(),0!==e.length&&(i=a(u8fr([1]),e),n=a())},c=()=>{if(o++>=1e3)throw new Error("drbg: tried 1000 values");let e=0;const r=[];for(;e<t;){n=a();const t=n.slice();r.push(t),e+=n.length}return concatBytes$2(...r)};return(e,t)=>{let r;for(s(),l(e);!(r=t(c()));)l();return s(),r}}const validatorFns={bigint:e=>"bigint"==typeof e,function:e=>"function"==typeof e,boolean:e=>"boolean"==typeof e,string:e=>"string"==typeof e,stringOrUint8Array:e=>"string"==typeof e||e instanceof Uint8Array,isSafeInteger:e=>Number.isSafeInteger(e),array:e=>Array.isArray(e),field:(e,t)=>t.Fp.isValid(e),hash:e=>"function"==typeof e&&Number.isSafeInteger(e.outputLen)};function validateObject$1(e,t,r={}){const n=(t,r,n)=>{const i=validatorFns[r];if("function"!=typeof i)throw new Error(`Invalid validator "${r}", expected function`);const o=e[t];if(!(n&&void 0===o||i(o,e)))throw new Error(`Invalid param ${String(t)}=${o} (${typeof o}), expected ${r}`)};for(const[i,o]of Object.entries(t))n(i,o,!1);for(const[i,o]of Object.entries(r))n(i,o,!0);return e}const ut=Object.freeze(Object.defineProperty({__proto__:null,bitGet:bitGet,bitLen:bitLen$1,bitMask:bitMask$1,bitSet:bitSet,bytesToHex:bytesToHex$4,bytesToNumberBE:bytesToNumberBE$1,bytesToNumberLE:bytesToNumberLE$1,concatBytes:concatBytes$2,createHmacDrbg:createHmacDrbg$1,ensureBytes:ensureBytes$1,equalBytes:equalBytes$2,hexToBytes:hexToBytes$4,hexToNumber:hexToNumber$2,numberToBytesBE:numberToBytesBE$1,numberToBytesLE:numberToBytesLE$1,numberToHexUnpadded:numberToHexUnpadded$2,numberToVarBytesBE:numberToVarBytesBE,utf8ToBytes:utf8ToBytes$1,validateObject:validateObject$1},Symbol.toStringTag,{value:"Module"})),_0n$9=BigInt(0),_1n$8=BigInt(1),_2n$5=BigInt(2),_3n$4=BigInt(3),_4n$2=BigInt(4),_5n$1=BigInt(5),_8n$2=BigInt(8);
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function mod$2(e,t){const r=e%t;return r>=_0n$9?r:t+r}function pow(e,t,r){if(r<=_0n$9||t<_0n$9)throw new Error("Expected power/modulo > 0");if(r===_1n$8)return _0n$9;let n=_1n$8;for(;t>_0n$9;)t&_1n$8&&(n=n*e%r),e=e*e%r,t>>=_1n$8;return n}function pow2$2(e,t,r){let n=e;for(;t-- >_0n$9;)n*=n,n%=r;return n}function invert$2(e,t){if(e===_0n$9||t<=_0n$9)throw new Error(`invert: expected positive integers, got n=${e} mod=${t}`);let r=mod$2(e,t),n=t,i=_0n$9,o=_1n$8;for(;r!==_0n$9;){const e=n%r,t=i-o*(n/r);n=r,r=e,i=o,o=t}if(n!==_1n$8)throw new Error("invert: does not exist");return mod$2(i,t)}function tonelliShanks$1(e){const t=(e-_1n$8)/_2n$5;let r,n,i;for(r=e-_1n$8,n=0;r%_2n$5===_0n$9;r/=_2n$5,n++);for(i=_2n$5;i<e&&pow(i,t,e)!==e-_1n$8;i++);if(1===n){const t=(e+_1n$8)/_4n$2;return function(e,r){const n=e.pow(r,t);if(!e.eql(e.sqr(n),r))throw new Error("Cannot find square root");return n}}const o=(r+_1n$8)/_2n$5;return function(e,s){if(e.pow(s,t)===e.neg(e.ONE))throw new Error("Cannot find square root");let a=n,l=e.pow(e.mul(e.ONE,i),r),c=e.pow(s,o),u=e.pow(s,r);for(;!e.eql(u,e.ONE);){if(e.eql(u,e.ZERO))return e.ZERO;let t=1;for(let n=e.sqr(u);t<a&&!e.eql(n,e.ONE);t++)n=e.sqr(n);const r=e.pow(l,_1n$8<<BigInt(a-t-1));l=e.sqr(r),c=e.mul(c,r),u=e.mul(u,l),a=t}return c}}function FpSqrt$1(e){if(e%_4n$2===_3n$4){const t=(e+_1n$8)/_4n$2;return function(e,r){const n=e.pow(r,t);if(!e.eql(e.sqr(n),r))throw new Error("Cannot find square root");return n}}if(e%_8n$2===_5n$1){const t=(e-_5n$1)/_8n$2;return function(e,r){const n=e.mul(r,_2n$5),i=e.pow(n,t),o=e.mul(r,i),s=e.mul(e.mul(o,_2n$5),i),a=e.mul(o,e.sub(s,e.ONE));if(!e.eql(e.sqr(a),r))throw new Error("Cannot find square root");return a}}return tonelliShanks$1(e)}BigInt(9),BigInt(16);const FIELD_FIELDS$1=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function validateField$1(e){const t=FIELD_FIELDS$1.reduce((e,t)=>(e[t]="function",e),{ORDER:"bigint",MASK:"bigint",BYTES:"isSafeInteger",BITS:"isSafeInteger"});return validateObject$1(e,t)}function FpPow$1(e,t,r){if(r<_0n$9)throw new Error("Expected power > 0");if(r===_0n$9)return e.ONE;if(r===_1n$8)return t;let n=e.ONE,i=t;for(;r>_0n$9;)r&_1n$8&&(n=e.mul(n,i)),i=e.sqr(i),r>>=_1n$8;return n}function FpInvertBatch$1(e,t){const r=new Array(t.length),n=t.reduce((t,n,i)=>e.is0(n)?t:(r[i]=t,e.mul(t,n)),e.ONE),i=e.inv(n);return t.reduceRight((t,n,i)=>e.is0(n)?t:(r[i]=e.mul(t,r[i]),e.mul(t,n)),i),r}function nLength$1(e,t){const r=void 0!==t?t:e.toString(2).length;return{nBitLength:r,nByteLength:Math.ceil(r/8)}}function Field$1(e,t,r=!1,n={}){if(e<=_0n$9)throw new Error(`Expected Field ORDER > 0, got ${e}`);const{nBitLength:i,nByteLength:o}=nLength$1(e,t);if(o>2048)throw new Error("Field lengths over 2048 bytes are not supported");const s=FpSqrt$1(e),a=Object.freeze({ORDER:e,BITS:i,BYTES:o,MASK:bitMask$1(i),ZERO:_0n$9,ONE:_1n$8,create:t=>mod$2(t,e),isValid:t=>{if("bigint"!=typeof t)throw new Error("Invalid field element: expected bigint, got "+typeof t);return _0n$9<=t&&t<e},is0:e=>e===_0n$9,isOdd:e=>(e&_1n$8)===_1n$8,neg:t=>mod$2(-t,e),eql:(e,t)=>e===t,sqr:t=>mod$2(t*t,e),add:(t,r)=>mod$2(t+r,e),sub:(t,r)=>mod$2(t-r,e),mul:(t,r)=>mod$2(t*r,e),pow:(e,t)=>FpPow$1(a,e,t),div:(t,r)=>mod$2(t*invert$2(r,e),e),sqrN:e=>e*e,addN:(e,t)=>e+t,subN:(e,t)=>e-t,mulN:(e,t)=>e*t,inv:t=>invert$2(t,e),sqrt:n.sqrt||(e=>s(a,e)),invertBatch:e=>FpInvertBatch$1(a,e),cmov:(e,t,r)=>r?t:e,toBytes:e=>r?numberToBytesLE$1(e,o):numberToBytesBE$1(e,o),fromBytes:e=>{if(e.length!==o)throw new Error(`Fp.fromBytes: expected ${o}, got ${e.length}`);return r?bytesToNumberLE$1(e):bytesToNumberBE$1(e)}});return Object.freeze(a)}function getFieldBytesLength$1(e){if("bigint"!=typeof e)throw new Error("field order must be bigint");const t=e.toString(2).length;return Math.ceil(t/8)}function getMinHashLength$1(e){const t=getFieldBytesLength$1(e);return t+Math.ceil(t/2)}function mapHashToField$1(e,t,r=!1){const n=e.length,i=getFieldBytesLength$1(t),o=getMinHashLength$1(t);if(n<16||n<o||n>1024)throw new Error(`expected ${o}-1024 bytes of input, got ${n}`);const s=mod$2(r?bytesToNumberBE$1(e):bytesToNumberLE$1(e),t-_1n$8)+_1n$8;return r?numberToBytesLE$1(s,i):numberToBytesBE$1(s,i)}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const _0n$8=BigInt(0),_1n$7=BigInt(1);function wNAF$1(e,t){const r=(e,t)=>{const r=t.negate();return e?r:t},n=e=>({windows:Math.ceil(t/e)+1,windowSize:2**(e-1)});return{constTimeNegate:r,unsafeLadder(t,r){let n=e.ZERO,i=t;for(;r>_0n$8;)r&_1n$7&&(n=n.add(i)),i=i.double(),r>>=_1n$7;return n},precomputeWindow(e,t){const{windows:r,windowSize:i}=n(t),o=[];let s=e,a=s;for(let n=0;n<r;n++){a=s,o.push(a);for(let e=1;e<i;e++)a=a.add(s),o.push(a);s=a.double()}return o},wNAF(t,i,o){const{windows:s,windowSize:a}=n(t);let l=e.ZERO,c=e.BASE;const u=BigInt(2**t-1),h=2**t,d=BigInt(t);for(let e=0;e<s;e++){const t=e*a;let n=Number(o&u);o>>=d,n>a&&(n-=h,o+=_1n$7);const s=t,p=t+Math.abs(n)-1,f=e%2!=0,g=n<0;0===n?c=c.add(r(f,i[s])):l=l.add(r(g,i[p]))}return{p:l,f:c}},wNAFCached(e,t,r,n){const i=e._WINDOW_SIZE||1;let o=t.get(e);return o||(o=this.precomputeWindow(e,i),1!==i&&t.set(e,n(o))),this.wNAF(i,o,r)}}}function validateBasic(e){return validateField$1(e.Fp),validateObject$1(e,{n:"bigint",h:"bigint",Gx:"field",Gy:"field"},{nBitLength:"isSafeInteger",nByteLength:"isSafeInteger"}),Object.freeze({...nLength$1(e.n,e.nBitLength),...e,p:e.Fp.ORDER})}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function validatePointOpts(e){const t=validateBasic(e);validateObject$1(t,{a:"field",b:"field"},{allowedPrivateKeyLengths:"array",wrapPrivateKey:"boolean",isTorsionFree:"function",clearCofactor:"function",allowInfinityPoint:"boolean",fromBytes:"function",toBytes:"function"});const{endo:r,Fp:n,a:i}=t;if(r){if(!n.eql(i,n.ZERO))throw new Error("Endomorphism can only be defined for Koblitz curves that have a=0");if("object"!=typeof r||"bigint"!=typeof r.beta||"function"!=typeof r.splitScalar)throw new Error("Expected endomorphism with beta: bigint and splitScalar: function")}return Object.freeze({...t})}const{bytesToNumberBE:b2n,hexToBytes:h2b}=ut,DER$1={Err:class extends Error{constructor(e=""){super(e)}},_parseInt(e){const{Err:t}=DER$1;if(e.length<2||2!==e[0])throw new t("Invalid signature integer tag");const r=e[1],n=e.subarray(2,r+2);if(!r||n.length!==r)throw new t("Invalid signature integer: wrong length");if(128&n[0])throw new t("Invalid signature integer: negative");if(0===n[0]&&!(128&n[1]))throw new t("Invalid signature integer: unnecessary leading zero");return{d:b2n(n),l:e.subarray(r+2)}},toSig(e){const{Err:t}=DER$1,r="string"==typeof e?h2b(e):e;if(!(r instanceof Uint8Array))throw new Error("ui8a expected");let n=r.length;if(n<2||48!=r[0])throw new t("Invalid signature tag");if(r[1]!==n-2)throw new t("Invalid signature: incorrect length");const{d:i,l:o}=DER$1._parseInt(r.subarray(2)),{d:s,l:a}=DER$1._parseInt(o);if(a.length)throw new t("Invalid signature: left bytes after parsing");return{r:i,s:s}},hexFromSig(e){const t=e=>8&Number.parseInt(e[0],16)?"00"+e:e,r=e=>{const t=e.toString(16);return 1&t.length?`0${t}`:t},n=t(r(e.s)),i=t(r(e.r)),o=n.length/2,s=i.length/2,a=r(o),l=r(s);return`30${r(s+o+4)}02${l}${i}02${a}${n}`}},_0n$7=BigInt(0),_1n$6=BigInt(1);BigInt(2);const _3n$3=BigInt(3);function weierstrassPoints(e){const t=validatePointOpts(e),{Fp:r}=t,n=t.toBytes||((e,t)=>{const n=t.toAffine();return concatBytes$2(Uint8Array.from([4]),r.toBytes(n.x),r.toBytes(n.y))}),i=t.fromBytes||(e=>{const t=e.subarray(1);return{x:r.fromBytes(t.subarray(0,r.BYTES)),y:r.fromBytes(t.subarray(r.BYTES,2*r.BYTES))}});function o(e){const{a:n,b:i}=t,o=r.sqr(e),s=r.mul(o,e);return r.add(r.add(s,r.mul(e,n)),i)}if(!r.eql(r.sqr(t.Gy),o(t.Gx)))throw new Error("bad generator point: equation left != right");function s(e){return"bigint"==typeof e&&_0n$7<e&&e<t.n}function a(e){if(!s(e))throw new Error("Expected valid bigint: 0 < bigint < curve.n")}function l(e){const{allowedPrivateKeyLengths:r,nByteLength:n,wrapPrivateKey:i,n:o}=t;if(r&&"bigint"!=typeof e){if(e instanceof Uint8Array&&(e=bytesToHex$4(e)),"string"!=typeof e||!r.includes(e.length))throw new Error("Invalid key");e=e.padStart(2*n,"0")}let s;try{s="bigint"==typeof e?e:bytesToNumberBE$1(ensureBytes$1("private key",e,n))}catch(error){throw new Error(`private key must be ${n} bytes, hex or bigint, not ${typeof e}`)}return i&&(s=mod$2(s,o)),a(s),s}const c=new Map;function u(e){if(!(e instanceof h))throw new Error("ProjectivePoint expected")}class h{constructor(e,t,n){if(this.px=e,this.py=t,this.pz=n,null==e||!r.isValid(e))throw new Error("x required");if(null==t||!r.isValid(t))throw new Error("y required");if(null==n||!r.isValid(n))throw new Error("z required")}static fromAffine(e){const{x:t,y:n}=e||{};if(!e||!r.isValid(t)||!r.isValid(n))throw new Error("invalid affine point");if(e instanceof h)throw new Error("projective point not allowed");const i=e=>r.eql(e,r.ZERO);return i(t)&&i(n)?h.ZERO:new h(t,n,r.ONE)}get x(){return this.toAffine().x}get y(){return this.toAffine().y}static normalizeZ(e){const t=r.invertBatch(e.map(e=>e.pz));return e.map((e,r)=>e.toAffine(t[r])).map(h.fromAffine)}static fromHex(e){const t=h.fromAffine(i(ensureBytes$1("pointHex",e)));return t.assertValidity(),t}static fromPrivateKey(e){return h.BASE.multiply(l(e))}_setWindowSize(e){this._WINDOW_SIZE=e,c.delete(this)}assertValidity(){if(this.is0()){if(t.allowInfinityPoint&&!r.is0(this.py))return;throw new Error("bad point: ZERO")}const{x:e,y:n}=this.toAffine();if(!r.isValid(e)||!r.isValid(n))throw new Error("bad point: x or y not FE");const i=r.sqr(n),s=o(e);if(!r.eql(i,s))throw new Error("bad point: equation left != right");if(!this.isTorsionFree())throw new Error("bad point: not in prime-order subgroup")}hasEvenY(){const{y:e}=this.toAffine();if(r.isOdd)return!r.isOdd(e);throw new Error("Field doesn't support isOdd")}equals(e){u(e);const{px:t,py:n,pz:i}=this,{px:o,py:s,pz:a}=e,l=r.eql(r.mul(t,a),r.mul(o,i)),c=r.eql(r.mul(n,a),r.mul(s,i));return l&&c}negate(){return new h(this.px,r.neg(this.py),this.pz)}double(){const{a:e,b:n}=t,i=r.mul(n,_3n$3),{px:o,py:s,pz:a}=this;let l=r.ZERO,c=r.ZERO,u=r.ZERO,d=r.mul(o,o),p=r.mul(s,s),f=r.mul(a,a),g=r.mul(o,s);return g=r.add(g,g),u=r.mul(o,a),u=r.add(u,u),l=r.mul(e,u),c=r.mul(i,f),c=r.add(l,c),l=r.sub(p,c),c=r.add(p,c),c=r.mul(l,c),l=r.mul(g,l),u=r.mul(i,u),f=r.mul(e,f),g=r.sub(d,f),g=r.mul(e,g)Line truncated
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
function getHash(e){return{hash:e,hmac:(t,...r)=>hmac$2(e,t,concatBytes$3(...r)),randomBytes:randomBytes$2}}function createCurve(e,t){const r=t=>weierstrass$2({...e,...getHash(t)});return Object.freeze({...r(t),create:r})}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */hmac$2.create=(e,t)=>new HMAC$1(e,t);const secp256k1P=BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),secp256k1N=BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),_1n$5=BigInt(1),_2n$4=BigInt(2),divNearest$2=(e,t)=>(e+t/_2n$4)/t;function sqrtMod$2(e){const t=secp256k1P,r=BigInt(3),n=BigInt(6),i=BigInt(11),o=BigInt(22),s=BigInt(23),a=BigInt(44),l=BigInt(88),c=e*e*e%t,u=c*c*e%t,h=pow2$2(u,r,t)*u%t,d=pow2$2(h,r,t)*u%t,p=pow2$2(d,_2n$4,t)*c%t,f=pow2$2(p,i,t)*p%t,g=pow2$2(f,o,t)*f%t,m=pow2$2(g,a,t)*g%t,y=pow2$2(m,l,t)*m%t,b=pow2$2(y,a,t)*g%t,v=pow2$2(b,r,t)*u%t,_=pow2$2(v,s,t)*f%t,E=pow2$2(_,n,t)*c%t,w=pow2$2(E,_2n$4,t);if(!Fp.eql(Fp.sqr(w),e))throw new Error("Cannot find square root");return w}const Fp=Field$1(secp256k1P,void 0,void 0,{sqrt:sqrtMod$2}),secp256k1$1=createCurve({a:BigInt(0),b:BigInt(7),Fp:Fp,n:secp256k1N,Gx:BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"),Gy:BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"),h:BigInt(1),lowS:!0,endo:{beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),splitScalar:e=>{const t=secp256k1N,r=BigInt("0x3086d221a7d46bcde86c90e49284eb15"),n=-_1n$5*BigInt("0xe4437ed6010e88286f547fa90abfe4c3"),i=BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),o=r,s=BigInt("0x100000000000000000000000000000000"),a=divNearest$2(o*e,t),l=divNearest$2(-n*e,t);let c=mod$2(e-a*r-l*i,t),u=mod$2(-a*n-l*o,t);const h=c>s,d=u>s;if(h&&(c=t-c),d&&(u=t-u),c>s||u>s)throw new Error("splitScalar: Endomorphism failed, k="+e);return{k1neg:h,k1:c,k2neg:d,k2:u}}}},sha256$3),_0n$6=BigInt(0),fe=e=>"bigint"==typeof e&&_0n$6<e&&e<secp256k1P,ge=e=>"bigint"==typeof e&&_0n$6<e&&e<secp256k1N,TAGGED_HASH_PREFIXES$1={};function taggedHash$1(e,...t){let r=TAGGED_HASH_PREFIXES$1[e];if(void 0===r){const t=sha256$3(Uint8Array.from(e,e=>e.charCodeAt(0)));r=concatBytes$2(t,t),TAGGED_HASH_PREFIXES$1[e]=r}return sha256$3(concatBytes$2(r,...t))}const pointToBytes$1=e=>e.toRawBytes(!0).slice(1),numTo32b=e=>numberToBytesBE$1(e,32),modP=e=>mod$2(e,secp256k1P),modN=e=>mod$2(e,secp256k1N),Point$2=secp256k1$1.ProjectivePoint,GmulAdd=(e,t,r)=>Point$2.BASE.multiplyAndAddUnsafe(e,t,r);function schnorrGetExtPubKey$1(e){let t=secp256k1$1.utils.normPrivateKeyToScalar(e),r=Point$2.fromPrivateKey(t);return{scalar:r.hasEvenY()?t:modN(-t),bytes:pointToBytes$1(r)}}function lift_x$1(e){if(!fe(e))throw new Error("bad x: need 0 < x < p");const t=modP(e*e);let r=sqrtMod$2(modP(t*e+BigInt(7)));r%_2n$4!==_0n$6&&(r=modP(-r));const n=new Point$2(e,r,_1n$5);return n.assertValidity(),n}function challenge$1(...e){return modN(bytesToNumberBE$1(taggedHash$1("BIP0340/challenge",...e)))}function schnorrGetPublicKey$1(e){return schnorrGetExtPubKey$1(e).bytes}function schnorrSign$1(e,t,r=randomBytes$2(32)){const n=ensureBytes$1("message",e),{bytes:i,scalar:o}=schnorrGetExtPubKey$1(t),s=ensureBytes$1("auxRand",r,32),a=numTo32b(o^bytesToNumberBE$1(taggedHash$1("BIP0340/aux",s))),l=taggedHash$1("BIP0340/nonce",a,i,n),c=modN(bytesToNumberBE$1(l));if(c===_0n$6)throw new Error("sign failed: k is zero");const{bytes:u,scalar:h}=schnorrGetExtPubKey$1(c),d=challenge$1(u,i,n),p=new Uint8Array(64);if(p.set(u,0),p.set(numTo32b(modN(h+d*o)),32),!schnorrVerify$1(p,n,i))throw new Error("sign: Invalid signature produced");return p}function schnorrVerify$1(e,t,r){const n=ensureBytes$1("signature",e,64),i=ensureBytes$1("message",t),o=ensureBytes$1("publicKey",r,32);try{const e=lift_x$1(bytesToNumberBE$1(o)),t=bytesToNumberBE$1(n.subarray(0,32));if(!fe(t))return!1;const r=bytesToNumberBE$1(n.subarray(32,64));if(!ge(r))return!1;const s=challenge$1(numTo32b(t),pointToBytes$1(e),i),a=GmulAdd(e,r,modN(-s));return!(!a||!a.hasEvenY()||a.toAffine().x!==t)}catch(error){return!1}}const schnorr$1=(()=>({getPublicKey:schnorrGetPublicKey$1,sign:schnorrSign$1,verify:schnorrVerify$1,utils:{randomPrivateKey:secp256k1$1.utils.randomPrivateKey,lift_x:lift_x$1,pointToBytes:pointToBytes$1,numberToBytesBE:numberToBytesBE$1,bytesToNumberBE:bytesToNumberBE$1,taggedHash:taggedHash$1,mod:mod$2}}))(),crypto$1="object"==typeof globalThis&&"crypto"in globalThis?globalThis.crypto:void 0,u8a=e=>e instanceof Uint8Array,createView$2=e=>new DataView(e.buffer,e.byteOffset,e.byteLength),rotr$2=(e,t)=>e<<32-t|e>>>t,isLE$2=68===new Uint8Array(new Uint32Array([287454020]).buffer)[0];if(!isLE$2)throw new Error("Non little-endian hardware is not supported");const hexes$3=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function bytesToHex$3(e){if(!u8a(e))throw new Error("Uint8Array expected");let t="";for(let r=0;r<e.length;r++)t+=hexes$3[e[r]];return t}function hexToBytes$3(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);const t=e.length;if(t%2)throw new Error("padded hex string expected, got unpadded hex of length "+t);const r=new Uint8Array(t/2);Line truncated
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
function assertNumber(e){if(!Number.isSafeInteger(e))throw new Error(`Wrong integer: ${e}`)}function chain$1(...e){const t=(e,t)=>r=>e(t(r)),r=Array.from(e).reverse().reduce((e,r)=>e?t(e,r.encode):r.encode,void 0),n=e.reduce((e,r)=>e?t(e,r.decode):r.decode,void 0);return{encode:r,decode:n}}function alphabet$1(e){return{encode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("alphabet.encode input should be an array of numbers");return t.map(t=>{if(assertNumber(t),t<0||t>=e.length)throw new Error(`Digit index outside alphabet: ${t} (alphabet: ${e.length})`);return e[t]})},decode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("alphabet.decode input should be array of strings");return t.map(t=>{if("string"!=typeof t)throw new Error(`alphabet.decode: not string element=${t}`);const r=e.indexOf(t);if(-1===r)throw new Error(`Unknown letter: "${t}". Allowed: ${e}`);return r})}}}function join$1(e=""){if("string"!=typeof e)throw new Error("join separator should be string");return{encode:t=>{if(!Array.isArray(t)||t.length&&"string"!=typeof t[0])throw new Error("join.encode input should be array of strings");for(let e of t)if("string"!=typeof e)throw new Error(`join.encode: non-string input=${e}`);return t.join(e)},decode:t=>{if("string"!=typeof t)throw new Error("join.decode input should be string");return t.split(e)}}}function padding$1(e,t="="){if(assertNumber(e),"string"!=typeof t)throw new Error("padding chr should be string");return{encode(r){if(!Array.isArray(r)||r.length&&"string"!=typeof r[0])throw new Error("padding.encode input should be array of strings");for(let e of r)if("string"!=typeof e)throw new Error(`padding.encode: non-string input=${e}`);for(;r.length*e%8;)r.push(t);return r},decode(r){if(!Array.isArray(r)||r.length&&"string"!=typeof r[0])throw new Error("padding.encode input should be array of strings");for(let e of r)if("string"!=typeof e)throw new Error(`padding.decode: non-string input=${e}`);let n=r.length;if(n*e%8)throw new Error("Invalid padding: string should have whole number of bytes");for(;n>0&&r[n-1]===t;n--)if(!((n-1)*e%8))throw new Error("Invalid padding: string has too much padding");return r.slice(0,n)}}}function normalize(e){if("function"!=typeof e)throw new Error("normalize fn should be function");return{encode:e=>e,decode:t=>e(t)}}function convertRadix(e,t,r){if(t<2)throw new Error(`convertRadix: wrong from=${t}, base cannot be less than 2`);if(r<2)throw new Error(`convertRadix: wrong to=${r}, base cannot be less than 2`);if(!Array.isArray(e))throw new Error("convertRadix: data should be array");if(!e.length)return[];let n=0;const i=[],o=Array.from(e);for(o.forEach(e=>{if(assertNumber(e),e<0||e>=t)throw new Error(`Wrong integer: ${e}`)});;){let e=0,s=!0;for(let i=n;i<o.length;i++){const a=o[i],l=t*e+a;if(!Number.isSafeInteger(l)||t*e/t!==e||l-a!==t*e)throw new Error("convertRadix: carry overflow");if(e=l%r,o[i]=Math.floor(l/r),!Number.isSafeInteger(o[i])||o[i]*r+e!==l)throw new Error("convertRadix: carry overflow");s&&(o[i]?s=!1:n=i)}if(i.push(e),s)break}for(let s=0;s<e.length-1&&0===e[s];s++)i.push(0);return i.reverse()}wrapConstructor(()=>new SHA224);const gcd$1=(e,t)=>t?gcd$1(t,e%t):e,radix2carry$1=(e,t)=>e+(t-gcd$1(e,t));function convertRadix2$1(e,t,r,n){if(!Array.isArray(e))throw new Error("convertRadix2: data should be array");if(t<=0||t>32)throw new Error(`convertRadix2: wrong from=${t}`);if(r<=0||r>32)throw new Error(`convertRadix2: wrong to=${r}`);if(radix2carry$1(t,r)>32)throw new Error(`convertRadix2: carry overflow from=${t} to=${r} carryBits=${radix2carry$1(t,r)}`);let i=0,o=0;const s=2**r-1,a=[];for(const l of e){if(assertNumber(l),l>=2**t)throw new Error(`convertRadix2: invalid data word=${l} from=${t}`);if(i=i<<t|l,o+t>32)throw new Error(`convertRadix2: carry overflow pos=${o} from=${t}`);for(o+=t;o>=r;o-=r)a.push((i>>o-r&s)>>>0);i&=2**o-1}if(i=i<<r-o&s,!n&&o>=t)throw new Error("Excess padding");if(!n&&i)throw new Error(`Non-zero padding: ${i}`);return n&&o>0&&a.push(i>>>0),a}function radix(e){return assertNumber(e),{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix.encode input should be Uint8Array");return convertRadix(Array.from(t),256,e)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix.decode input should be array of strings");return Uint8Array.from(convertRadix(t,e,256))}}}function radix2$1(e,t=!1){if(assertNumber(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(radix2carry$1(8,e)>32||radix2carry$1(e,8)>32)throw new Error("radix2: carry overflow");return{encode:r=>{if(!(r instanceof Uint8Array))throw new Error("radix2.encode input should be Uint8Array");return convertRadix2$1(Array.from(r),8,e,!t)},decode:r=>{if(!Array.isArray(r)||r.length&&"number"!=typeof r[0])throw new Error("radix2.decode input should be array of strings");return Uint8Array.from(convertRadix2$1(r,e,8,t))}}}function unsafeWrapper$1(e){if("function"!=typeof e)thrLine truncated
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */Object.keys(CODERS).join(", ");const u32$1=e=>new Uint32Array(e.buffer,e.byteOffset,Math.floor(e.byteLength/4)),isLE$1=68===new Uint8Array(new Uint32Array([287454020]).buffer)[0];if(!isLE$1)throw new Error("Non little-endian hardware is not supported");const hexes$2=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function bytesToHex$2(e){bytes(e);let t="";for(let r=0;r<e.length;r++)t+=hexes$2[e[r]];return t}const asciis$2={_0:48,_9:57,_A:65,_F:70,_a:97,_f:102};function asciiToBase16$2(e){return e>=asciis$2._0&&e<=asciis$2._9?e-asciis$2._0:e>=asciis$2._A&&e<=asciis$2._F?e-(asciis$2._A-10):e>=asciis$2._a&&e<=asciis$2._f?e-(asciis$2._a-10):void 0}function hexToBytes$2(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);const t=e.length,r=t/2;if(t%2)throw new Error("padded hex string expected, got unpadded hex of length "+t);const n=new Uint8Array(r);for(let i=0,o=0;i<r;i++,o+=2){const t=asciiToBase16$2(e.charCodeAt(o)),r=asciiToBase16$2(e.charCodeAt(o+1));if(void 0===t||void 0===r){const t=e[o]+e[o+1];throw new Error('hex string expected, got non-hex character "'+t+'" at index '+o)}n[i]=16*t+r}return n}function checkOpts$1(e,t){if(null==t||"object"!=typeof t)throw new Error("options must be defined");return Object.assign(e,t)}function equalBytes$1(e,t){if(e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e[n]^t[n];return 0===r}const wrapCipher$1=(e,t)=>(Object.assign(t,e),t),BLOCK_SIZE$1=16,POLY$1=283;function mul2$1(e){return e<<1^POLY$1&-(e>>7)}function mul$1(e,t){let r=0;for(;t>0;t>>=1)r^=e&-(1&t),e=mul2$1(e);return r}const sbox$1=(()=>{let e=new Uint8Array(256);for(let r=0,n=1;r<256;r++,n^=mul2$1(n))e[r]=n;const t=new Uint8Array(256);t[0]=99;for(let r=0;r<255;r++){let n=e[255-r];n|=n<<8,t[e[r]]=255&(n^n>>4^n>>5^n>>6^n>>7^99)}return t})(),invSbox$1=sbox$1.map((e,t)=>sbox$1.indexOf(t)),rotr32_8$1=e=>e<<24|e>>>8,rotl32_8$1=e=>e<<8|e>>>24;function genTtable$1(e,t){if(256!==e.length)throw new Error("Wrong sbox length");const r=new Uint32Array(256).map((r,n)=>t(e[n])),n=r.map(rotl32_8$1),i=n.map(rotl32_8$1),o=i.map(rotl32_8$1),s=new Uint32Array(65536),a=new Uint32Array(65536),l=new Uint16Array(65536);for(let c=0;c<256;c++)for(let t=0;t<256;t++){const u=256*c+t;s[u]=r[c]^n[t],a[u]=i[c]^o[t],l[u]=e[c]<<8|e[t]}return{sbox:e,sbox2:l,T0:r,T1:n,T2:i,T3:o,T01:s,T23:a}}const tableEncoding$1=genTtable$1(sbox$1,e=>mul$1(e,3)<<24|e<<16|e<<8|mul$1(e,2)),tableDecoding$1=genTtable$1(invSbox$1,e=>mul$1(e,11)<<24|mul$1(e,13)<<16|mul$1(e,9)<<8|mul$1(e,14)),xPowers$1=(()=>{const e=new Uint8Array(16);for(let t=0,r=1;t<16;t++,r=mul2$1(r))e[t]=r;return e})();function expandKeyLE$1(e){bytes(e);const t=e.length;if(![16,24,32].includes(t))throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${t}`);const{sbox2:r}=tableEncoding$1,n=u32$1(e),i=n.length,o=e=>applySbox$1(r,e,e,e,e),s=new Uint32Array(t+28);s.set(n);for(let a=i;a<s.length;a++){let e=s[a-1];a%i===0?e=o(rotr32_8$1(e))^xPowers$1[a/i-1]:i>6&&a%i===4&&(e=o(e)),s[a]=s[a-i]^e}return s}function expandKeyDecLE$1(e){const t=expandKeyLE$1(e),r=t.slice(),n=t.length,{sbox2:i}=tableEncoding$1,{T0:o,T1:s,T2:a,T3:l}=tableDecoding$1;for(let c=0;c<n;c+=4)for(let e=0;e<4;e++)r[c+e]=t[n-c-4+e];t.fill(0);for(let c=4;c<n-4;c++){const e=r[c],t=applySbox$1(i,e,e,e,e);r[c]=o[255&t]^s[t>>>8&255]^a[t>>>16&255]^l[t>>>24]}return r}function apply0123$1(e,t,r,n,i,o){return e[r<<8&65280|n>>>8&255]^t[i>>>8&65280|o>>>24&255]}function applySbox$1(e,t,r,n,i){return e[255&t|65280&r]|e[n>>>16&255|i>>>16&65280]<<16}function encrypt$4(e,t,r,n,i){const{sbox2:o,T01:s,T23:a}=tableEncoding$1;let l=0;t^=e[l++],r^=e[l++],n^=e[l++],i^=e[l++];const c=e.length/4-2;for(let u=0;u<c;u++){const o=e[l++]^apply0123$1(s,a,t,r,n,i),c=e[l++]^apply0123$1(s,a,r,n,i,t),u=e[l++]^apply0123$1(s,a,n,i,t,r),h=e[l++]^apply0123$1(s,a,i,t,r,n);t=o,r=c,n=u,i=h}return{s0:e[l++]^applySbox$1(o,t,r,n,i),s1:e[l++]^applySbox$1(o,r,n,i,t),s2:e[l++]^applySbox$1(o,n,i,t,r),s3:e[l++]^applySbox$1(o,i,t,r,n)}}function decrypt$4(e,t,r,n,i){const{sbox2:o,T01:s,T23:a}=tableDecoding$1;let l=0;t^=e[l++],r^=e[l++],n^=e[l++],i^=e[l++];const c=e.length/4-2;for(let u=0;u<c;u++){const o=e[l++]^apply0123$1(s,a,t,i,n,r),c=e[l++]^apply0123$1(s,a,r,t,i,n),u=e[l++]^apply0123$1(s,a,n,r,t,i),h=e[l++]^apply0123$1(s,a,i,n,r,t);t=o,r=c,n=u,i=h}return{s0:e[l++]^applySbox$1(o,t,i,n,r),s1:e[l++]^applySbox$1(o,r,t,i,n),s2:e[l++]^applySbox$1(o,n,r,t,i),s3:e[l++]^applySbox$1(o,i,n,r,t)}}function getDst(e,t){if(!t)return new Uint8Array(e);if(bytes(t),t.length<e)throw new Error(`aes: wrong destination length, expected at least ${e}, got: ${t.length}`);return t}function validateBlockDecrypt$1(e){if(bytes(e),e.length%BLOCK_SIZE$1!==0)throw new Error(`aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size ${BLOCK_SIZE$1}`)}function validateBlockEncrypt$1(e,t,r){let n=e.length;const i=n%BLOCK_SIZE$1;if(!t&&0!==i)throw new Error("aecLine truncated
/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */const _0n$5=BigInt(0),_1n$4=BigInt(1),_2n$3=BigInt(2),_3n$2=BigInt(3),_8n$1=BigInt(8),CURVE=Object.freeze({a:_0n$5,b:BigInt(7),P:BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),n:BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),h:_1n$4,Gx:BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"),Gy:BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"),beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee")}),divNearest$1=(e,t)=>(e+t/_2n$3)/t,endo={beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),splitScalar(e){const{n:t}=CURVE,r=BigInt("0x3086d221a7d46bcde86c90e49284eb15"),n=-_1n$4*BigInt("0xe4437ed6010e88286f547fa90abfe4c3"),i=BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),o=r,s=BigInt("0x100000000000000000000000000000000"),a=divNearest$1(o*e,t),l=divNearest$1(-n*e,t);let c=mod$1(e-a*r-l*i,t),u=mod$1(-a*n-l*o,t);const h=c>s,d=u>s;if(h&&(c=t-c),d&&(u=t-u),c>s||u>s)throw new Error("splitScalarEndo: Endomorphism failed, k="+e);return{k1neg:h,k1:c,k2neg:d,k2:u}}},fieldLen=32,groupLen=32,compressedLen=fieldLen+1,uncompressedLen=2*fieldLen+1;function weierstrass$1(e){const{a:t,b:r}=CURVE,n=mod$1(e*e),i=mod$1(n*e);return mod$1(i+t*e+r)}const USE_ENDOMORPHISM=CURVE.a===_0n$5;function assertJacPoint(e){if(!(e instanceof JacobianPoint))throw new TypeError("JacobianPoint expected")}class JacobianPoint{constructor(e,t,r){this.x=e,this.y=t,this.z=r}static fromAffine(e){if(!(e instanceof Point$1))throw new TypeError("JacobianPoint#fromAffine: expected Point");return e.equals(Point$1.ZERO)?JacobianPoint.ZERO:new JacobianPoint(e.x,e.y,_1n$4)}static toAffineBatch(e){const t=invertBatch(e.map(e=>e.z));return e.map((e,r)=>e.toAffine(t[r]))}static normalizeZ(e){return JacobianPoint.toAffineBatch(e).map(JacobianPoint.fromAffine)}equals(e){assertJacPoint(e);const{x:t,y:r,z:n}=this,{x:i,y:o,z:s}=e,a=mod$1(n*n),l=mod$1(s*s),c=mod$1(t*l),u=mod$1(i*a),h=mod$1(mod$1(r*s)*l),d=mod$1(mod$1(o*n)*a);return c===u&&h===d}negate(){return new JacobianPoint(this.x,mod$1(-this.y),this.z)}double(){const{x:e,y:t,z:r}=this,n=mod$1(e*e),i=mod$1(t*t),o=mod$1(i*i),s=e+i,a=mod$1(_2n$3*(mod$1(s*s)-n-o)),l=mod$1(_3n$2*n),c=mod$1(l*l),u=mod$1(c-_2n$3*a),h=mod$1(l*(a-u)-_8n$1*o),d=mod$1(_2n$3*t*r);return new JacobianPoint(u,h,d)}add(e){assertJacPoint(e);const{x:t,y:r,z:n}=this,{x:i,y:o,z:s}=e;if(i===_0n$5||o===_0n$5)return this;if(t===_0n$5||r===_0n$5)return e;const a=mod$1(n*n),l=mod$1(s*s),c=mod$1(t*l),u=mod$1(i*a),h=mod$1(mod$1(r*s)*l),d=mod$1(mod$1(o*n)*a),p=mod$1(u-c),f=mod$1(d-h);if(p===_0n$5)return f===_0n$5?this.double():JacobianPoint.ZERO;const g=mod$1(p*p),m=mod$1(p*g),y=mod$1(c*g),b=mod$1(f*f-m-_2n$3*y),v=mod$1(f*(y-b)-h*m),_=mod$1(n*s*p);return new JacobianPoint(b,v,_)}subtract(e){return this.add(e.negate())}multiplyUnsafe(e){const t=JacobianPoint.ZERO;if("bigint"==typeof e&&e===_0n$5)return t;let r=normalizeScalar(e);if(r===_1n$4)return this;if(!USE_ENDOMORPHISM){let e=t,n=this;for(;r>_0n$5;)r&_1n$4&&(e=e.add(n)),n=n.double(),r>>=_1n$4;return e}let{k1neg:n,k1:i,k2neg:o,k2:s}=endo.splitScalar(r),a=t,l=t,c=this;for(;i>_0n$5||s>_0n$5;)i&_1n$4&&(a=a.add(c)),s&_1n$4&&(l=l.add(c)),c=c.double(),i>>=_1n$4,s>>=_1n$4;return n&&(a=a.negate()),o&&(l=l.negate()),l=new JacobianPoint(mod$1(l.x*endo.beta),l.y,l.z),a.add(l)}precomputeWindow(e){const t=USE_ENDOMORPHISM?128/e+1:256/e+1,r=[];let n=this,i=n;for(let o=0;o<t;o++){i=n,r.push(i);for(let t=1;t<2**(e-1);t++)i=i.add(n),r.push(i);n=i.double()}return r}wNAF(e,t){!t&&this.equals(JacobianPoint.BASE)&&(t=Point$1.BASE);const r=t&&t._WINDOW_SIZE||1;if(256%r)throw new Error("Point#wNAF: Invalid precomputation window, must be power of 2");let n=t&&pointPrecomputes$1.get(t);n||(n=this.precomputeWindow(r),t&&1!==r&&(n=JacobianPoint.normalizeZ(n),pointPrecomputes$1.set(t,n)));let i=JacobianPoint.ZERO,o=JacobianPoint.BASE;const s=1+(USE_ENDOMORPHISM?128/r:256/r),a=2**(r-1),l=BigInt(2**r-1),c=2**r,u=BigInt(r);for(let h=0;h<s;h++){const t=h*a;let r=Number(e&l);e>>=u,r>a&&(r-=c,e+=_1n$4);const s=t,d=t+Math.abs(r)-1,p=h%2!=0,f=r<0;0===r?o=o.add(constTimeNegate(p,n[s])):i=i.add(constTimeNegate(f,n[d]))}return{p:i,f:o}}multiply(e,t){let r,n,i=normalizeScalar(e);if(USE_ENDOMORPHISM){const{k1neg:e,k1:o,k2neg:s,k2:a}=endo.splitScalar(i);let{p:l,f:c}=this.wNAF(o,t),{p:u,f:h}=this.wNAF(a,t);l=constTimeNegate(e,l),u=constTimeNegate(s,u),u=new JacobianPoint(mod$1(u.x*endo.beta),u.y,u.z),r=l.add(u),n=c.add(h)}else{const{p:e,f:o}=this.wNAF(i,t);r=e,n=o}return JacobianPoint.normalizeZ([r,n])[0]}toAffine(e){const{x:t,y:r,z:n}=this,i=this.equals(JacobianPoint.ZERO);null==e&&(e=i?_8n$1:invert$1(n));const o=e,s=mod$1(o*o),a=mod$1(s*o),l=mod$1(t*s),c=mod$1(r*a),u=mod$1(n*o);if(i)return Point$1.ZERO;if(u!==_1n$4)throw new Error("invZ was invalid");return new Point$1(l,c)}}function constTLine truncated
/*! For license information please see remotestorage.js.LICENSE.txt */function requireRemotestorage(){return hasRequiredRemotestorage||(hasRequiredRemotestorage=1,function(module,exports$1){var t;t=function(){return function(){var __webpack_modules__={548:function(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Access=void 0;class r{constructor(e){this.rs=e,this.reset(!1)}get scopes(){return Object.keys(this.scopeModeMap).map(e=>({name:e,mode:this.scopeModeMap[e]}))}get scopeParameter(){return this.scopes.map(e=>`${this._scopeNameForParameter(e)}:${e.mode}`).join(" ")}claim(e,t){if("string"!=typeof e||-1!==e.indexOf("/")||0===e.length)throw new Error("Scope should be a non-empty string without forward slashes");if("string"!=typeof t||!t.match(/^rw?$/))throw new Error("Mode should be either 'r' or 'rw'");this._adjustRootPaths(e),this.scopeModeMap[e]=t,this._notifyChange()}get(e){return this.scopeModeMap[e]}remove(e){const t={};for(const r in this.scopeModeMap)t[r]=this.scopeModeMap[r];this.reset(!1),delete t[e];for(const r in t)this._adjustRootPaths(r),this.scopeModeMap[r]=t[r];this._notifyChange()}checkPermission(e,t){const r=this.get(e);return r&&("r"===t||"rw"===r)}checkPathPermission(e,t){if(this.checkPermission("*",t))return!0;const r=this._getModuleName(e);return!!this.checkPermission(r,t)}reset(e=!0){this.rootPaths=[],this.scopeModeMap={},e&&this._notifyChange()}_getModuleName(e){if("/"!==e[0])throw new Error("Path should start with a slash");const t=e.replace(/^\/public/,"").match(/^\/([^/]*)\//);return t?t[1]:"*"}_adjustRootPaths(e){"*"in this.scopeModeMap||"*"===e?this.rootPaths=["/"]:e in this.scopeModeMap||(this.rootPaths.push("/"+e+"/"),this.rootPaths.push("/public/"+e+"/"))}_scopeNameForParameter(e){if("*"===e.name&&this.storageType){if("2012.04"===this.storageType)return"";if(this.storageType.match(/remotestorage-0[01]/))return"root"}return e.name}setStorageType(e){this.storageType=e}_notifyChange(){this.rs&&"function"==typeof this.rs._checkScopeChange&&this.rs._checkScopeChange()}static _rs_init(){}}t.Access=r,t.default=r},275:function(e,t,r){var n=this&&this.__awaiter||function(e,t,r,n){return new(r||(r=Promise))(function(i,o){function s(e){try{l(n.next(e))}catch(e){o(e)}}function a(e){try{l(n.throw(e))}catch(e){o(e)}}function l(e){var t;e.done?i(e.value):(t=e.value,t instanceof r?t:new r(function(e){e(t)})).then(s,a)}l((n=n.apply(e,t||[])).next())})},i=this&&this.__importDefault||function(e){return e&&e.__esModule?e:{default:e}};Object.defineProperty(t,"__esModule",{value:!0}),t.Authorize=void 0;const o=i(r(28)),s=i(r(334)),a=r(746),l=i(r(503)),c=r(368);let u;function h(e){const t=e||d.getLocation().href,r={};for(const[o,s]of new URL(t).searchParams)r[o]=s;const n=t.indexOf("#");if(-1===n)return r;const i=t.substring(n+1);return i.includes("=")?i.split("&").reduce(function(e,t){const r=t.split("=");if("state"===r[0]&&r[1].match(/rsDiscovery/)){let t=decodeURIComponent(r[1]);const n=t.substr(t.indexOf("rsDiscovery=")).split("&")[0].split("=")[1];e.rsDiscovery=JSON.parse(atob(n)),t=t.replace(new RegExp("&?rsDiscovery="+n),""),t.length>0&&(e.state=t)}else e[decodeURIComponent(r[0])]=decodeURIComponent(r[1]);return e},r):r}class d{static authorize(e,t){if((0,s.default)("[Authorize] authURL = ",t.authURL,"scope = ",t.scope,"redirectUri = ",t.redirectUri,"clientId = ",t.clientId,"response_type =",t.response_type),!t.scope)throw new Error("Cannot authorize due to undefined or empty scope; did you forget to access.claim()?");if("function"==typeof e._rememberPendingScope&&e._rememberPendingScope(t.scope),!(0,a.localStorageAvailable)()&&"remotestorage"===e.backend){t.redirectUri+=t.redirectUri.indexOf("#")>0?"&":"#";const r={userAddress:e.remote.userAddress,href:e.remote.href,storageApi:e.remote.storageApi,properties:e.remote.properties};t.redirectUri+="rsDiscovery="+(0,a.toBase64)(JSON.stringify(r))}const r=function(e){const t=new URL(e.redirectUri);e.state||(e.state=t.hash?t.hash.substring(1):""),e.response_type||(e.response_type="token");const r=new URL(e.authURL);r.searchParams.set("redirect_uri",e.redirectUri.replace(/#.*$/,"")),r.searchParams.set("scope",e.scope),r.searchParams.set("client_id",e.clientId);for(const n of["state","response_type","code_challenge","code_challenge_method","token_access_type"]){const t=e[n];t&&r.searchParams.set(n,t)}return r.href}(t);a.globalContext.cordova&&(d.getLocation().href.startsWith("file:")||"string"==typeof o.default.cordovaRedirectUri&&t.redirectUri===o.default.cordovaRedirectUri)?d.openWindow(r,t.redirectUri,"location=yes,clearsessioncache=yes,clearcache=yes").then(r=>{e.remote.configure({token:r.access_token}),"function"==typeof e._completeAuthorization&&e._completeAuthorization(r.scope||t.scope)}):d.setLocation(r)}static refreshAccessToken(e,t,r){return n(this,void 0,void 0,function*(){var e,n,i;yield t.configure({token:null,tokenType:null});const o=new URLSearchParams({grant_type:"refresh_token",client_id:t.clientId,refresh_token:r}),a=yield(Line truncated
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
Object.defineProperty(e,"__esModule",{value:!0}),e.bytes=e.stringToBytes=e.str=e.bytesToString=e.hex=e.utf8=e.bech32m=e.bech32=e.base58check=e.base58xmr=e.base58xrp=e.base58flickr=e.base58=e.base64url=e.base64=e.base32crockford=e.base32hex=e.base32=e.base16=e.utils=e.assertNumber=void 0,e.assertNumber=t;const l=(e,t)=>t?l(t,e%t):e,c=(e,t)=>e+(t-l(e,t));function u(e,r,n,i){if(!Array.isArray(e))throw new Error("convertRadix2: data should be array");if(r<=0||r>32)throw new Error(`convertRadix2: wrong from=${r}`);if(n<=0||n>32)throw new Error(`convertRadix2: wrong to=${n}`);if(c(r,n)>32)throw new Error(`convertRadix2: carry overflow from=${r} to=${n} carryBits=${c(r,n)}`);let o=0,s=0;const a=2**n-1,l=[];for(const c of e){if(t(c),c>=2**r)throw new Error(`convertRadix2: invalid data word=${c} from=${r}`);if(o=o<<r|c,s+r>32)throw new Error(`convertRadix2: carry overflow pos=${s} from=${r}`);for(s+=r;s>=n;s-=n)l.push((o>>s-n&a)>>>0);o&=2**s-1}if(o=o<<n-s&a,!i&&s>=r)throw new Error("Excess padding");if(!i&&o)throw new Error(`Non-zero padding: ${o}`);return i&&s>0&&l.push(o>>>0),l}function h(e){return t(e),{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix.encode input should be Uint8Array");return a(Array.from(t),256,e)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix.decode input should be array of strings");return Uint8Array.from(a(t,e,256))}}}function d(e,r=!1){if(t(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(c(8,e)>32||c(e,8)>32)throw new Error("radix2: carry overflow");return{encode:t=>{if(!(t instanceof Uint8Array))throw new Error("radix2.encode input should be Uint8Array");return u(Array.from(t),8,e,!r)},decode:t=>{if(!Array.isArray(t)||t.length&&"number"!=typeof t[0])throw new Error("radix2.decode input should be array of strings");return Uint8Array.from(u(t,e,8,r))}}}function p(e){if("function"!=typeof e)throw new Error("unsafeWrapper fn should be function");return function(...t){try{return e.apply(null,t)}catch(r){}}}function f(e,r){if(t(e),"function"!=typeof r)throw new Error("checksum fn should be function");return{encode(t){if(!(t instanceof Uint8Array))throw new Error("checksum.encode: input should be Uint8Array");const n=r(t).slice(0,e),i=new Uint8Array(t.length+e);return i.set(t),i.set(n,t.length),i},decode(t){if(!(t instanceof Uint8Array))throw new Error("checksum.decode: input should be Uint8Array");const n=t.slice(0,-e),i=r(n).slice(0,e),o=t.slice(-e);for(let r=0;r<e;r++)if(i[r]!==o[r])throw new Error("Invalid checksum");return n}}}e.utils={alphabet:n,chain:r,checksum:f,radix:h,radix2:d,join:i,padding:o},e.base16=r(d(4),n("0123456789ABCDEF"),i("")),e.base32=r(d(5),n("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"),o(5),i("")),e.base32hex=r(d(5),n("0123456789ABCDEFGHIJKLMNOPQRSTUV"),o(5),i("")),e.base32crockford=r(d(5),n("0123456789ABCDEFGHJKMNPQRSTVWXYZ"),i(""),s(e=>e.toUpperCase().replace(/O/g,"0").replace(/[IL]/g,"1"))),e.base64=r(d(6),n("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),o(6),i("")),e.base64url=r(d(6),n("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"),o(6),i(""));const g=e=>r(h(58),n(e),i(""));e.base58=g("123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"),e.base58flickr=g("123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"),e.base58xrp=g("rpshnaf39wBUDNEGHJKLM4PQRST7VWXYZ2bcdeCg65jkm8oFqi1tuvAxyz");const m=[0,2,3,5,6,7,9,10,11];e.base58xmr={encode(t){let r="";for(let n=0;n<t.length;n+=8){const i=t.subarray(n,n+8);r+=e.base58.encode(i).padStart(m[i.length],"1")}return r},decode(t){let r=[];for(let n=0;n<t.length;n+=11){const i=t.slice(n,n+11),o=m.indexOf(i.length),s=e.base58.decode(i);for(let e=0;e<s.length-o;e++)if(0!==s[e])throw new Error("base58xmr: wrong padding");r=r.concat(Array.from(s.slice(s.length-o)))}return Uint8Array.from(r)}},e.base58check=t=>r(f(4,e=>t(t(e))),e.base58);const y=r(n("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),i("")),b=[996825010,642813549,513874426,1027748829,705979059];function v(e){const t=e>>25;let r=(33554431&e)<<5;for(let n=0;n<b.length;n++)1==(t>>n&1)&&(r^=b[n]);return r}function _(e,t,r=1){const n=e.length;let i=1;for(let o=0;o<n;o++){const t=e.charCodeAt(o);if(t<33||t>126)throw new Error(`Invalid prefix (${e})`);i=v(i)^t>>5}i=v(i);for(let o=0;o<n;o++)i=v(i)^31&e.charCodeAt(o);for(let o of t)i=v(i)^o;for(let o=0;o<6;o++)i=v(i);return i^=r,y.encode(u([i%2**30],30,5,!1))}function E(e){const t="bech32"===e?1:734539939,r=d(5),n=r.decode,i=r.encode,o=p(n);function s(e,r=90){if("string"!=typeof e)throw new Error("bech32.decode input should be string, not "+typeof e);if(e.length<8||!1!==r&&e.length>r)throw new TypeError(`Wrong string length: ${e.length} (${e}). Expected (8..${r})`);const n=e.toLowerCase();if(e!==n&&e!==e.toUpperCase())throw new Error("String must be lowercase or uppercase");const i=(e=n).lastIndexOf("1");if(0===i||-1===i)throw new Error('Letter "1" must be present between prefix and data only');const o=e.slice(0,i),s=e.slLine truncated
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */function isBytes$2(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&"Uint8Array"===e.constructor.name}function anumber$2(e,t=""){if(!Number.isSafeInteger(e)||e<0)throw new Error(`${t&&`"${t}" `}expected integer >= 0, got ${e}`)}function abytes$2(e,t,r=""){const n=isBytes$2(e),i=e?.length,o=void 0!==t;if(!n||o&&i!==t)throw new Error((r&&`"${r}" `)+"expected Uint8Array"+(o?` of length ${t}`:"")+", got "+(n?`length=${i}`:"type="+typeof e));return e}function ahash(e){if("function"!=typeof e||"function"!=typeof e.create)throw new Error("Hash must wrapped by utils.createHasher");anumber$2(e.outputLen),anumber$2(e.blockLen)}function aexists(e,t=!0){if(e.destroyed)throw new Error("Hash instance has been destroyed");if(t&&e.finished)throw new Error("Hash#digest() has already been called")}function aoutput(e,t){abytes$2(e,void 0,"digestInto() output");const r=t.outputLen;if(e.length<r)throw new Error('"digestInto() output" expected to be of length >='+r)}function clean$1(...e){for(let t=0;t<e.length;t++)e[t].fill(0)}function createView(e){return new DataView(e.buffer,e.byteOffset,e.byteLength)}function rotr(e,t){return e<<32-t|e>>>t}const hasHexBuiltin=(()=>"function"==typeof Uint8Array.from([]).toHex&&"function"==typeof Uint8Array.fromHex)(),hexes=Array.from({length:256},(e,t)=>t.toString(16).padStart(2,"0"));function bytesToHex(e){if(abytes$2(e),hasHexBuiltin)return e.toHex();let t="";for(let r=0;r<e.length;r++)t+=hexes[e[r]];return t}const asciis={_0:48,_9:57,A:65,F:70,a:97,f:102};function asciiToBase16(e){return e>=asciis._0&&e<=asciis._9?e-asciis._0:e>=asciis.A&&e<=asciis.F?e-(asciis.A-10):e>=asciis.a&&e<=asciis.f?e-(asciis.a-10):void 0}function hexToBytes(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);if(hasHexBuiltin)return Uint8Array.fromHex(e);const t=e.length,r=t/2;if(t%2)throw new Error("hex string expected, got unpadded hex of length "+t);const n=new Uint8Array(r);for(let i=0,o=0;i<r;i++,o+=2){const t=asciiToBase16(e.charCodeAt(o)),r=asciiToBase16(e.charCodeAt(o+1));if(void 0===t||void 0===r){const t=e[o]+e[o+1];throw new Error('hex string expected, got non-hex character "'+t+'" at index '+o)}n[i]=16*t+r}return n}function concatBytes(...e){let t=0;for(let n=0;n<e.length;n++){const r=e[n];abytes$2(r),t+=r.length}const r=new Uint8Array(t);for(let n=0,i=0;n<e.length;n++){const t=e[n];r.set(t,i),i+=t.length}return r}function createHasher(e,t={}){const r=(t,r)=>e(r).update(t).digest(),n=e(void 0);return r.outputLen=n.outputLen,r.blockLen=n.blockLen,r.create=t=>e(t),Object.assign(r,t),Object.freeze(r)}function randomBytes(e=32){const t="object"==typeof globalThis?globalThis.crypto:null;if("function"!=typeof t?.getRandomValues)throw new Error("crypto.getRandomValues must be defined");return t.getRandomValues(new Uint8Array(e))}const oidNist=e=>({oid:Uint8Array.from([6,9,96,134,72,1,101,3,4,2,e])});function Chi(e,t,r){return e&t^~e&r}function Maj(e,t,r){return e&t^e&r^t&r}class HashMD{blockLen;outputLen;padOffset;isLE;buffer;view;finished=!1;length=0;pos=0;destroyed=!1;constructor(e,t,r,n){this.blockLen=e,this.outputLen=t,this.padOffset=r,this.isLE=n,this.buffer=new Uint8Array(e),this.view=createView(this.buffer)}update(e){aexists(this),abytes$2(e);const{view:t,buffer:r,blockLen:n}=this,i=e.length;for(let o=0;o<i;){const s=Math.min(n-this.pos,i-o);if(s===n){const t=createView(e);for(;n<=i-o;o+=n)this.process(t,o);continue}r.set(e.subarray(o,o+s),this.pos),this.pos+=s,o+=s,this.pos===n&&(this.process(t,0),this.pos=0)}return this.length+=e.length,this.roundClean(),this}digestInto(e){aexists(this),aoutput(e,this),this.finished=!0;const{buffer:t,view:r,blockLen:n,isLE:i}=this;let{pos:o}=this;t[o++]=128,clean$1(this.buffer.subarray(o)),this.padOffset>n-o&&(this.process(r,0),o=0);for(let u=o;u<n;u++)t[u]=0;r.setBigUint64(n-8,BigInt(8*this.length),i),this.process(r,0);const s=createView(e),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen must be aligned to 32bit");const l=a/4,c=this.get();if(l>c.length)throw new Error("_sha2: outputLen bigger than state");for(let u=0;u<l;u++)s.setUint32(4*u,c[u],i)}digest(){const{buffer:e,outputLen:t}=this;this.digestInto(e);const r=e.slice(0,t);return this.destroy(),r}_cloneInto(e){e||=new this.constructor,e.set(...this.get());const{blockLen:t,buffer:r,length:n,finished:i,destroyed:o,pos:s}=this;return e.destroyed=o,e.finished=i,e.length=n,e.pos=s,n%t&&e.buffer.set(r),e}clone(){return this._cloneInto()}}const SHA256_IV=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),SHA256_K=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,358452871Line truncated
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function abool$1(e,t=""){if("boolean"!=typeof e)throw new Error((t&&`"${t}" `)+"expected boolean, got type="+typeof e);return e}function abignumber(e){if("bigint"==typeof e){if(!isPosBig(e))throw new Error("positive bigint expected, got "+e)}else anumber$2(e);return e}function numberToHexUnpadded(e){const t=abignumber(e).toString(16);return 1&t.length?"0"+t:t}function hexToNumber(e){if("string"!=typeof e)throw new Error("hex string expected, got "+typeof e);return""===e?_0n$4:BigInt("0x"+e)}function bytesToNumberBE(e){return hexToNumber(bytesToHex(e))}function bytesToNumberLE(e){return hexToNumber(bytesToHex(copyBytes$1(abytes$2(e)).reverse()))}function numberToBytesBE(e,t){anumber$2(t);const r=hexToBytes((e=abignumber(e)).toString(16).padStart(2*t,"0"));if(r.length!==t)throw new Error("number too large");return r}function numberToBytesLE(e,t){return numberToBytesBE(e,t).reverse()}function copyBytes$1(e){return Uint8Array.from(e)}function asciiToBytes(e){return Uint8Array.from(e,(t,r)=>{const n=t.charCodeAt(0);if(1!==t.length||n>127)throw new Error(`string contains non-ASCII character "${e[r]}" with code ${n} at position ${r}`);return n})}const isPosBig=e=>"bigint"==typeof e&&_0n$4<=e;function inRange(e,t,r){return isPosBig(e)&&isPosBig(t)&&isPosBig(r)&&t<=e&&e<r}function aInRange(e,t,r,n){if(!inRange(t,r,n))throw new Error("expected valid "+e+": "+r+" <= n < "+n+", got "+t)}function bitLen(e){let t;for(t=0;e>_0n$4;e>>=_1n$3,t+=1);return t}const bitMask=e=>(_1n$3<<BigInt(e))-_1n$3;function createHmacDrbg(e,t,r){if(anumber$2(e,"hashLen"),anumber$2(t,"qByteLen"),"function"!=typeof r)throw new Error("hmacFn must be a function");const n=e=>new Uint8Array(e),i=Uint8Array.of(),o=Uint8Array.of(0),s=Uint8Array.of(1);let a=n(e),l=n(e),c=0;const u=()=>{a.fill(1),l.fill(0),c=0},h=(...e)=>r(l,concatBytes(a,...e)),d=(e=i)=>{l=h(o,e),a=h(),0!==e.length&&(l=h(s,e),a=h())},p=()=>{if(c++>=1e3)throw new Error("drbg: tried max amount of iterations");let e=0;const r=[];for(;e<t;){a=h();const t=a.slice();r.push(t),e+=a.length}return concatBytes(...r)};return(e,t)=>{let r;for(u(),d(e);!(r=t(p()));)d();return u(),r}}function validateObject(e,t={},r={}){if(!e||"object"!=typeof e)throw new Error("expected valid options object");const n=(t,r)=>Object.entries(t).forEach(([t,n])=>function(t,r,n){const i=e[t];if(n&&void 0===i)return;const o=typeof i;if(o!==r||null===i)throw new Error(`param "${t}" is invalid: expected ${r}, got ${o}`)}(t,n,r));n(t,!1),n(r,!0)}function memoized(e){const t=new WeakMap;return(r,...n)=>{const i=t.get(r);if(void 0!==i)return i;const o=e(r,...n);return t.set(r,o),o}}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const _0n$3=BigInt(0),_1n$2=BigInt(1),_2n$2=BigInt(2),_3n$1=BigInt(3),_4n$1=BigInt(4),_5n=BigInt(5),_7n=BigInt(7),_8n=BigInt(8),_9n=BigInt(9),_16n=BigInt(16);function mod(e,t){const r=e%t;return r>=_0n$3?r:t+r}function pow2(e,t,r){let n=e;for(;t-- >_0n$3;)n*=n,n%=r;return n}function invert(e,t){if(e===_0n$3)throw new Error("invert: expected non-zero number");if(t<=_0n$3)throw new Error("invert: expected positive modulus, got "+t);let r=mod(e,t),n=t,i=_0n$3,o=_1n$2;for(;r!==_0n$3;){const e=n%r,t=i-o*(n/r);n=r,r=e,i=o,o=t}if(n!==_1n$2)throw new Error("invert: does not exist");return mod(i,t)}function assertIsSquare(e,t,r){if(!e.eql(e.sqr(t),r))throw new Error("Cannot find square root")}function sqrt3mod4(e,t){const r=(e.ORDER+_1n$2)/_4n$1,n=e.pow(t,r);return assertIsSquare(e,n,t),n}function sqrt5mod8(e,t){const r=(e.ORDER-_5n)/_8n,n=e.mul(t,_2n$2),i=e.pow(n,r),o=e.mul(t,i),s=e.mul(e.mul(o,_2n$2),i),a=e.mul(o,e.sub(s,e.ONE));return assertIsSquare(e,a,t),a}function sqrt9mod16(e){const t=Field(e),r=tonelliShanks(e),n=r(t,t.neg(t.ONE)),i=r(t,n),o=r(t,t.neg(n)),s=(e+_7n)/_16n;return(e,t)=>{let r=e.pow(t,s),a=e.mul(r,n);const l=e.mul(r,i),c=e.mul(r,o),u=e.eql(e.sqr(a),t),h=e.eql(e.sqr(l),t);r=e.cmov(r,a,u),a=e.cmov(c,l,h);const d=e.eql(e.sqr(a),t),p=e.cmov(r,a,d);return assertIsSquare(e,p,t),p}}function tonelliShanks(e){if(e<_3n$1)throw new Error("sqrt is not defined for small field");let t=e-_1n$2,r=0;for(;t%_2n$2===_0n$3;)t/=_2n$2,r++;let n=_2n$2;const i=Field(e);for(;1===FpLegendre(i,n);)if(n++>1e3)throw new Error("Cannot find square root: probably non-prime P");if(1===r)return sqrt3mod4;let o=i.pow(n,t);const s=(t+_1n$2)/_2n$2;return function(e,n){if(e.is0(n))return n;if(1!==FpLegendre(e,n))throw new Error("Cannot find square root");let i=r,a=e.mul(e.ONE,o),l=e.pow(n,t),c=e.pow(n,s);for(;!e.eql(l,e.ONE);){if(e.is0(l))return e.ZERO;let t=1,r=e.sqr(l);for(;!e.eql(r,e.ONE);)if(t++,r=e.sqr(r),t===i)throw new Error("Cannot find square root");const n=_1n$2<<BigInt(i-t-1),o=e.pow(a,n);i=t,a=e.sqr(o),l=e.mul(l,a),c=e.mul(c,o)}return c}}function FpSqrt(e){return e%_4n$1===_3n$1?sqrt3mod4:e%_8n===_5n?sqrt5mod8:e%_16n===_9n?sqrt9mod16(e):tonelliShanks(e)}const FIELD_FIELDS=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function validateField(e){const t=FIELD_FIELDS.reduce((e,t)=>(e[t]="function",e),{ORDER:"bigint",BYTES:"number",BITS:"number"});return validateObject(e,t),e}function FpPow(e,t,r){if(r<_0n$3)throw new Error("invalid exponent, negatives unsupported");if(r===_0n$3)return e.ONE;if(r===_1n$2)return t;let n=e.ONE,i=t;for(;r>_0n$3;)r&_1n$2&&(n=e.mul(n,i)),i=e.sqr(i),r>>=_1n$2;return n}function FpInvertBatch(e,t,r=!1){const n=new Array(t.length).fill(r?e.ZERO:void 0),i=t.reduce((t,r,i)=>e.is0(r)?t:(n[i]=t,e.mul(t,r)),e.ONE),o=e.inv(i);return t.reduceRight((t,r,i)=>e.is0(r)?t:(n[i]=e.mul(t,n[i]),e.mul(t,r)),o),n}function FpLegendre(e,t){const r=(e.ORDER-_1n$2)/_2n$2,n=e.pow(t,r),i=e.eql(n,e.ONE),o=e.eql(n,e.ZERO),s=e.eql(n,e.neg(e.ONE));if(!i&&!o&&!s)throw new Error("invalid Legendre symbol result");return i?1:o?0:-1}function nLength(e,t){void 0!==t&&anumber$2(t);const r=void 0!==t?t:e.toString(2).length;return{nBitLength:r,nByteLength:Math.ceil(r/8)}}class _Field{ORDER;BITS;BYTES;isLE;ZERO=_0n$3;ONE=_1n$2;_lengths;_sqrt;_mod;constructor(e,t={}){if(e<=_0n$3)throw new Error("invalid field: expected ORDER > 0, got "+e);let r;this.isLE=!1,null!=t&&"object"==typeof t&&("number"==typeof t.BITS&&(r=t.BITS),"function"==typeof t.sqrt&&(this.sqrt=t.sqrt),"boolean"==typeof t.isLE&&(this.isLE=t.isLE),t.allowedLengths&&(this._lengths=t.allowedLengths?.slice()),"boolean"==typeof t.modFromBytes&&(this._mod=t.modFromBytes));const{nBitLength:n,nByteLength:i}=nLength(e,r);if(i>2048)throw new Error("invalid field: expected ORDER of <= 2048 bytes");this.ORDER=e,this.BITS=n,this.BYTES=i,this._sqrt=void 0,Object.preventExtensions(this)}create(e){return mod(e,this.ORDER)}isValid(e){if("bigint"!=typeof e)throw new Error("invalid field element: expected bigint, got "+typeof e);return _0n$3<=e&&e<this.ORDER}is0(e){return e===_0n$3}isValidNot0(e){return!this.is0(e)&&this.isValid(e)}isOdd(e){return(e&_1n$2)===_1n$2}neg(e){return mod(-e,this.ORDER)}eql(e,t){return e===t}sqr(e){return mod(e*e,this.ORDER)}add(e,t){return mod(e+t,this.ORDER)}sub(e,t){return mod(e-t,this.ORDER)}mul(e,t){return mod(e*t,this.ORDER)}pow(e,t){return FpPow(this,e,t)}div(e,t){return mod(e*invert(t,this.ORDER),this.ORDER)}sqrN(e){return e*e}addN(e,t){return e+t}subN(e,t){return e-t}mulN(e,t){return e*t}inv(e){return invert(e,this.ORDER)}sqrt(e){return this._sqrt||(this._sqrt=FpSqrt(this.ORDER)),this._sqrt(this,e)}toBytes(e){return this.isLE?numberToBytesLE(e,this.BYTES):numberToBytesBE(e,this.BYTES)}fromBytes(e,t=!1){abytes$2(e);const{_lengths:r,BYTES:n,isLE:i,ORDER:o,_mod:s}=this;if(r){if(!r.includes(e.length)||e.length>n)throw new Line truncated
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const _0n$2=BigInt(0),_1n$1=BigInt(1);function negateCt(e,t){const r=t.negate();return e?r:t}function normalizeZ(e,t){const r=FpInvertBatch(e.Fp,t.map(e=>e.Z));return t.map((t,n)=>e.fromAffine(t.toAffine(r[n])))}function validateW(e,t){if(!Number.isSafeInteger(e)||e<=0||e>t)throw new Error("invalid window size, expected [1.."+t+"], got W="+e)}function calcWOpts(e,t){validateW(e,t);const r=2**e;return{windows:Math.ceil(t/e)+1,windowSize:2**(e-1),mask:bitMask(e),maxNumber:r,shiftBy:BigInt(e)}}function calcOffsets(e,t,r){const{windowSize:n,mask:i,maxNumber:o,shiftBy:s}=r;let a=Number(e&i),l=e>>s;a>n&&(a-=o,l+=_1n$1);const c=t*n;return{nextN:l,offset:c+Math.abs(a)-1,isZero:0===a,isNeg:a<0,isNegF:t%2!=0,offsetF:c}}const pointPrecomputes=new WeakMap,pointWindowSizes=new WeakMap;function getW(e){return pointWindowSizes.get(e)||1}function assert0(e){if(e!==_0n$2)throw new Error("invalid wNAF")}class wNAF{BASE;ZERO;Fn;bits;constructor(e,t){this.BASE=e.BASE,this.ZERO=e.ZERO,this.Fn=e.Fn,this.bits=t}_unsafeLadder(e,t,r=this.ZERO){let n=e;for(;t>_0n$2;)t&_1n$1&&(r=r.add(n)),n=n.double(),t>>=_1n$1;return r}precomputeWindow(e,t){const{windows:r,windowSize:n}=calcWOpts(t,this.bits),i=[];let o=e,s=o;for(let a=0;a<r;a++){s=o,i.push(s);for(let e=1;e<n;e++)s=s.add(o),i.push(s);o=s.double()}return i}wNAF(e,t,r){if(!this.Fn.isValid(r))throw new Error("invalid scalar");let n=this.ZERO,i=this.BASE;const o=calcWOpts(e,this.bits);for(let s=0;s<o.windows;s++){const{nextN:e,offset:a,isZero:l,isNeg:c,isNegF:u,offsetF:h}=calcOffsets(r,s,o);r=e,l?i=i.add(negateCt(u,t[h])):n=n.add(negateCt(c,t[a]))}return assert0(r),{p:n,f:i}}wNAFUnsafe(e,t,r,n=this.ZERO){const i=calcWOpts(e,this.bits);for(let o=0;o<i.windows&&r!==_0n$2;o++){const{nextN:e,offset:s,isZero:a,isNeg:l}=calcOffsets(r,o,i);if(r=e,!a){const e=t[s];n=n.add(l?e.negate():e)}}return assert0(r),n}getPrecomputes(e,t,r){let n=pointPrecomputes.get(t);return n||(n=this.precomputeWindow(t,e),1!==e&&("function"==typeof r&&(n=r(n)),pointPrecomputes.set(t,n))),n}cached(e,t,r){const n=getW(e);return this.wNAF(n,this.getPrecomputes(n,e,r),t)}unsafe(e,t,r,n){const i=getW(e);return 1===i?this._unsafeLadder(e,t,n):this.wNAFUnsafe(i,this.getPrecomputes(i,e,r),t,n)}createCache(e,t){validateW(t,this.bits),pointWindowSizes.set(e,t),pointPrecomputes.delete(e)}hasCache(e){return 1!==getW(e)}}function mulEndoUnsafe(e,t,r,n){let i=t,o=e.ZERO,s=e.ZERO;for(;r>_0n$2||n>_0n$2;)r&_1n$1&&(o=o.add(i)),n&_1n$1&&(s=s.add(i)),i=i.double(),r>>=_1n$1,n>>=_1n$1;return{p1:o,p2:s}}function createField(e,t,r){if(t){if(t.ORDER!==e)throw new Error("Field.ORDER must match order: Fp == p, Fn == n");return validateField(t),t}return Field(e,{isLE:r})}function createCurveFields(e,t,r={},n){if(void 0===n&&(n="edwards"===e),!t||"object"!=typeof t)throw new Error(`expected valid ${e} CURVE object`);for(const a of["p","n","h"]){const e=t[a];if(!("bigint"==typeof e&&e>_0n$2))throw new Error(`CURVE.${a} must be positive bigint`)}const i=createField(t.p,r.Fp,n),o=createField(t.n,r.Fn,n),s=["Gx","Gy","a","b"];for(const a of s)if(!i.isValid(t[a]))throw new Error(`CURVE.${a} must be valid field element of CURVE.Fp`);return{CURVE:t=Object.freeze(Object.assign({},t)),Fp:i,Fn:o}}function createKeygen(e,t){return function(r){const n=e(r);return{secretKey:n,publicKey:t(n)}}}class _HMAC{oHash;iHash;blockLen;outputLen;finished=!1;destroyed=!1;constructor(e,t){if(ahash(e),abytes$2(t,void 0,"key"),this.iHash=e.create(),"function"!=typeof this.iHash.update)throw new Error("Expected instance of class which extends utils.Hash");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;const r=this.blockLen,n=new Uint8Array(r);n.set(t.length>r?e.create().update(t).digest():t);for(let i=0;i<n.length;i++)n[i]^=54;this.iHash.update(n),this.oHash=e.create();for(let i=0;i<n.length;i++)n[i]^=106;this.oHash.update(n),clean$1(n)}update(e){return aexists(this),this.iHash.update(e),this}digestInto(e){aexists(this),abytes$2(e,this.outputLen,"output"),this.finished=!0,this.iHash.digestInto(e),this.oHash.update(e),this.oHash.digestInto(e),this.destroy()}digest(){const e=new Uint8Array(this.oHash.outputLen);return this.digestInto(e),e}_cloneInto(e){e||=Object.create(Object.getPrototypeOf(this),{});const{oHash:t,iHash:r,finished:n,destroyed:i,blockLen:o,outputLen:s}=this;return e.finished=n,e.destroyed=i,e.blockLen=o,e.outputLen=s,e.oHash=t._cloneInto(e.oHash),e.iHash=r._cloneInto(e.iHash),e}clone(){return this._cloneInto()}destroy(){this.destroyed=!0,this.oHash.destroy(),this.iHash.destroy()}}const hmac=(e,t,r)=>new _HMAC(e,t).update(r).digest();hmac.create=(e,t)=>new _HMAC(e,t);
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
const divNearest=(e,t)=>(e+(e>=0?t:-t)/_2n$1)/t;function _splitEndoScalar(e,t,r){const[[n,i],[o,s]]=t,a=divNearest(s*e,r),l=divNearest(-i*e,r);let c=e-a*n-l*o,u=-a*i-l*s;const h=c<_0n$1,d=u<_0n$1;h&&(c=-c),d&&(u=-u);const p=bitMask(Math.ceil(bitLen(r)/2))+_1n;if(c<_0n$1||c>=p||u<_0n$1||u>=p)throw new Error("splitScalar (endomorphism): failed, k="+e);return{k1neg:h,k1:c,k2neg:d,k2:u}}function validateSigFormat(e){if(!["compact","recovered","der"].includes(e))throw new Error('Signature format must be "compact", "recovered", or "der"');return e}function validateSigOpts(e,t){const r={};for(let n of Object.keys(t))r[n]=void 0===e[n]?t[n]:e[n];return abool$1(r.lowS,"lowS"),abool$1(r.prehash,"prehash"),void 0!==r.format&&validateSigFormat(r.format),r}class DERErr extends Error{constructor(e=""){super(e)}}const DER={Err:DERErr,_tlv:{encode:(e,t)=>{const{Err:r}=DER;if(e<0||e>256)throw new r("tlv.encode: wrong tag");if(1&t.length)throw new r("tlv.encode: unpadded data");const n=t.length/2,i=numberToHexUnpadded(n);if(i.length/2&128)throw new r("tlv.encode: long form length too big");const o=n>127?numberToHexUnpadded(i.length/2|128):"";return numberToHexUnpadded(e)+o+i+t},decode(e,t){const{Err:r}=DER;let n=0;if(e<0||e>256)throw new r("tlv.encode: wrong tag");if(t.length<2||t[n++]!==e)throw new r("tlv.decode: wrong tlv");const i=t[n++];let o=0;if(128&i){const e=127&i;if(!e)throw new r("tlv.decode(long): indefinite length not supported");if(e>4)throw new r("tlv.decode(long): byte length is too big");const s=t.subarray(n,n+e);if(s.length!==e)throw new r("tlv.decode: length bytes not complete");if(0===s[0])throw new r("tlv.decode(long): zero leftmost byte");for(const t of s)o=o<<8|t;if(n+=e,o<128)throw new r("tlv.decode(long): not minimal encoding")}else o=i;const s=t.subarray(n,n+o);if(s.length!==o)throw new r("tlv.decode: wrong value length");return{v:s,l:t.subarray(n+o)}}},_int:{encode(e){const{Err:t}=DER;if(e<_0n$1)throw new t("integer: negative integers are not allowed");let r=numberToHexUnpadded(e);if(8&Number.parseInt(r[0],16)&&(r="00"+r),1&r.length)throw new t("unexpected DER parsing assertion: unpadded hex");return r},decode(e){const{Err:t}=DER;if(128&e[0])throw new t("invalid signature integer: negative");if(0===e[0]&&!(128&e[1]))throw new t("invalid signature integer: unnecessary leading zero");return bytesToNumberBE(e)}},toSig(e){const{Err:t,_int:r,_tlv:n}=DER,i=abytes$2(e,void 0,"signature"),{v:o,l:s}=n.decode(48,i);if(s.length)throw new t("invalid signature: left bytes after parsing");const{v:a,l:l}=n.decode(2,o),{v:c,l:u}=n.decode(2,l);if(u.length)throw new t("invalid signature: left bytes after parsing");return{r:r.decode(a),s:r.decode(c)}},hexFromSig(e){const{_tlv:t,_int:r}=DER,n=t.encode(2,r.encode(e.r))+t.encode(2,r.encode(e.s));return t.encode(48,n)}},_0n$1=BigInt(0),_1n=BigInt(1),_2n$1=BigInt(2),_3n=BigInt(3),_4n=BigInt(4);function weierstrass(e,t={}){const r=createCurveFields("weierstrass",e,t),{Fp:n,Fn:i}=r;let o=r.CURVE;const{h:s,n:a}=o;validateObject(t,{},{allowInfinityPoint:"boolean",clearCofactor:"function",isTorsionFree:"function",fromBytes:"function",toBytes:"function",endo:"object"});const{endo:l}=t;if(l&&(!n.is0(o.a)||"bigint"!=typeof l.beta||!Array.isArray(l.basises)))throw new Error('invalid endo: expected "beta": bigint and "basises": array');const c=getWLengths(n,i);function u(){if(!n.isOdd)throw new Error("compression is not supported: Field does not have .isOdd()")}const h=t.toBytes||function(e,t,r){const{x:i,y:o}=t.toAffine(),s=n.toBytes(i);return abool$1(r,"isCompressed"),r?(u(),concatBytes(pprefix(!n.isOdd(o)),s)):concatBytes(Uint8Array.of(4),s,n.toBytes(o))},d=t.fromBytes||function(e){abytes$2(e,void 0,"Point");const{publicKey:t,publicKeyUncompressed:r}=c,i=e.length,o=e[0],s=e.subarray(1);if(i!==t||2!==o&&3!==o){if(i===r&&4===o){const e=n.BYTES,t=n.fromBytes(s.subarray(0,e)),r=n.fromBytes(s.subarray(e,2*e));if(!f(t,r))throw new Error("bad point: is not on curve");return{x:t,y:r}}throw new Error(`bad point: got length ${i}, expected compressed=${t} or uncompressed=${r}`)}{const e=n.fromBytes(s);if(!n.isValid(e))throw new Error("bad point: is not on curve, wrong x");const t=p(e);let r;try{r=n.sqrt(t)}catch(a){const e=a instanceof Error?": "+a.message:"";throw new Error("bad point: is not on curve, sqrt error"+e)}return u(),!(1&~o)!==n.isOdd(r)&&(r=n.neg(r)),{x:e,y:r}}};function p(e){const t=n.sqr(e),r=n.mul(t,e);return n.add(n.add(r,n.mul(e,o.a)),o.b)}function f(e,t){const r=n.sqr(t),i=p(e);return n.eql(r,i)}if(!f(o.Gx,o.Gy))throw new Error("bad curve params: generator point");const g=n.mul(n.pow(o.a,_3n),_4n),m=n.mul(n.sqr(o.b),BigInt(27));if(n.is0(n.add(g,m)))throw new Error("bad curve params: a or b");function y(e,t,r=!1){if(!n.isValid(t)||r&&n.is0(t))throw new Error(`bad point coordinate ${e}`);return t}function b(e){if(!(e instanceof C))throw new Error("Weierstrass Point expected")}function v(e){if(!l||!l.basises)throw new Error("no endo");return _splitEndoScalar(e,l.basises,i.OLine truncated
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const secp256k1_CURVE={p:BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),n:BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),h:BigInt(1),a:BigInt(0),b:BigInt(7),Gx:BigInt("0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798"),Gy:BigInt("0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8")},secp256k1_ENDO={beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),basises:[[BigInt("0x3086d221a7d46bcde86c90e49284eb15"),-BigInt("0xe4437ed6010e88286f547fa90abfe4c3")],[BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),BigInt("0x3086d221a7d46bcde86c90e49284eb15")]]},_0n=BigInt(0),_2n=BigInt(2);function sqrtMod(e){const t=secp256k1_CURVE.p,r=BigInt(3),n=BigInt(6),i=BigInt(11),o=BigInt(22),s=BigInt(23),a=BigInt(44),l=BigInt(88),c=e*e*e%t,u=c*c*e%t,h=pow2(u,r,t)*u%t,d=pow2(h,r,t)*u%t,p=pow2(d,_2n,t)*c%t,f=pow2(p,i,t)*p%t,g=pow2(f,o,t)*f%t,m=pow2(g,a,t)*g%t,y=pow2(m,l,t)*m%t,b=pow2(y,a,t)*g%t,v=pow2(b,r,t)*u%t,_=pow2(v,s,t)*f%t,E=pow2(_,n,t)*c%t,w=pow2(E,_2n,t);if(!Fpk1.eql(Fpk1.sqr(w),e))throw new Error("Cannot find square root");return w}const Fpk1=Field(secp256k1_CURVE.p,{sqrt:sqrtMod}),Pointk1=weierstrass(secp256k1_CURVE,{Fp:Fpk1,endo:secp256k1_ENDO}),secp256k1=ecdsa(Pointk1,sha256),TAGGED_HASH_PREFIXES={};function taggedHash(e,...t){let r=TAGGED_HASH_PREFIXES[e];if(void 0===r){const t=sha256(asciiToBytes(e));r=concatBytes(t,t),TAGGED_HASH_PREFIXES[e]=r}return sha256(concatBytes(r,...t))}const pointToBytes=e=>e.toBytes(!0).slice(1),hasEven=e=>e%_2n===_0n;function schnorrGetExtPubKey(e){const{Fn:t,BASE:r}=Pointk1,n=t.fromBytes(e),i=r.multiply(n);return{scalar:hasEven(i.y)?n:t.neg(n),bytes:pointToBytes(i)}}function lift_x(e){const t=Fpk1;if(!t.isValidNot0(e))throw new Error("invalid x: Fail if x ≥ p");const r=t.create(e*e),n=t.create(r*e+BigInt(7));let i=t.sqrt(n);hasEven(i)||(i=t.neg(i));const o=Pointk1.fromAffine({x:e,y:i});return o.assertValidity(),o}const num=bytesToNumberBE;function challenge(...e){return Pointk1.Fn.create(num(taggedHash("BIP0340/challenge",...e)))}function schnorrGetPublicKey(e){return schnorrGetExtPubKey(e).bytes}function schnorrSign(e,t,r=randomBytes(32)){const{Fn:n}=Pointk1,i=abytes$2(e,void 0,"message"),{bytes:o,scalar:s}=schnorrGetExtPubKey(t),a=abytes$2(r,32,"auxRand"),l=n.toBytes(s^num(taggedHash("BIP0340/aux",a))),c=taggedHash("BIP0340/nonce",l,o,i),{bytes:u,scalar:h}=schnorrGetExtPubKey(c),d=challenge(u,o,i),p=new Uint8Array(64);if(p.set(u,0),p.set(n.toBytes(n.create(h+d*s)),32),!schnorrVerify(p,i,o))throw new Error("sign: Invalid signature produced");return p}function schnorrVerify(e,t,r){const{Fp:n,Fn:i,BASE:o}=Pointk1,s=abytes$2(e,64,"signature"),a=abytes$2(t,void 0,"message"),l=abytes$2(r,32,"publicKey");try{const e=lift_x(num(l)),t=num(s.subarray(0,32));if(!n.isValidNot0(t))return!1;const r=num(s.subarray(32,64));if(!i.isValidNot0(r))return!1;const c=challenge(i.toBytes(t),pointToBytes(e),a),u=o.multiplyUnsafe(r).add(e.multiplyUnsafe(i.neg(c))),{x:h,y:d}=u.toAffine();return!(u.is0()||!hasEven(d)||h!==t)}catch(error){return!1}}const schnorr=(()=>{const e=(e=randomBytes(48))=>mapHashToField(e,secp256k1_CURVE.n);return{keygen:createKeygen(e,schnorrGetPublicKey),getPublicKey:schnorrGetPublicKey,sign:schnorrSign,verify:schnorrVerify,Point:Pointk1,utils:{randomSecretKey:e,taggedHash:taggedHash,lift_x:lift_x,pointToBytes:pointToBytes},lengths:{secretKey:32,publicKey:32,publicKeyHasPrefix:!1,signature:64,seed:48}}})();
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */function isBytes$1(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&"Uint8Array"===e.constructor.name}function abytes$1(e){if(!isBytes$1(e))throw new Error("Uint8Array expected")}function isArrayOf(e,t){return!!Array.isArray(t)&&(0===t.length||(e?t.every(e=>"string"==typeof e):t.every(e=>Number.isSafeInteger(e))))}function afn(e){if("function"!=typeof e)throw new Error("function expected");return!0}function astr(e,t){if("string"!=typeof t)throw new Error(`${e}: string expected`);return!0}function anumber$1(e){if(!Number.isSafeInteger(e))throw new Error(`invalid integer: ${e}`)}function aArr(e){if(!Array.isArray(e))throw new Error("array expected")}function astrArr(e,t){if(!isArrayOf(!0,t))throw new Error(`${e}: array of strings expected`)}function anumArr(e,t){if(!isArrayOf(!1,t))throw new Error(`${e}: array of numbers expected`)}function chain(...e){const t=e=>e,r=(e,t)=>r=>e(t(r)),n=e.map(e=>e.encode).reduceRight(r,t),i=e.map(e=>e.decode).reduce(r,t);return{encode:n,decode:i}}function alphabet(e){const t="string"==typeof e?e.split(""):e,r=t.length;astrArr("alphabet",t);const n=new Map(t.map((e,t)=>[e,t]));return{encode:n=>(aArr(n),n.map(n=>{if(!Number.isSafeInteger(n)||n<0||n>=r)throw new Error(`alphabet.encode: digit index outside alphabet "${n}". Allowed: ${e}`);return t[n]})),decode:t=>(aArr(t),t.map(t=>{astr("alphabet.decode",t);const r=n.get(t);if(void 0===r)throw new Error(`Unknown letter: "${t}". Allowed: ${e}`);return r}))}}function join(e=""){return astr("join",e),{encode:t=>(astrArr("join.decode",t),t.join(e)),decode:t=>(astr("join.decode",t),t.split(e))}}function padding(e,t="="){return anumber$1(e),astr("padding",t),{encode(r){for(astrArr("padding.encode",r);r.length*e%8;)r.push(t);return r},decode(r){astrArr("padding.decode",r);let n=r.length;if(n*e%8)throw new Error("padding: invalid, string should have whole number of bytes");for(;n>0&&r[n-1]===t;n--)if((n-1)*e%8==0)throw new Error("padding: invalid, string has too much padding");return r.slice(0,n)}}}const gcd=(e,t)=>0===t?e:gcd(t,e%t),radix2carry=(e,t)=>e+(t-gcd(e,t)),powers=(()=>{let e=[];for(let t=0;t<40;t++)e.push(2**t);return e})();function convertRadix2(e,t,r,n){if(aArr(e),t<=0||t>32)throw new Error(`convertRadix2: wrong from=${t}`);if(r<=0||r>32)throw new Error(`convertRadix2: wrong to=${r}`);if(radix2carry(t,r)>32)throw new Error(`convertRadix2: carry overflow from=${t} to=${r} carryBits=${radix2carry(t,r)}`);let i=0,o=0;const s=powers[t],a=powers[r]-1,l=[];for(const c of e){if(anumber$1(c),c>=s)throw new Error(`convertRadix2: invalid data word=${c} from=${t}`);if(i=i<<t|c,o+t>32)throw new Error(`convertRadix2: carry overflow pos=${o} from=${t}`);for(o+=t;o>=r;o-=r)l.push((i>>o-r&a)>>>0);const e=powers[o];if(void 0===e)throw new Error("invalid carry");i&=e-1}if(i=i<<r-o&a,!n&&o>=t)throw new Error("Excess padding");if(!n&&i>0)throw new Error(`Non-zero padding: ${i}`);return n&&o>0&&l.push(i>>>0),l}function radix2(e,t=!1){if(anumber$1(e),e<=0||e>32)throw new Error("radix2: bits should be in (0..32]");if(radix2carry(8,e)>32||radix2carry(e,8)>32)throw new Error("radix2: carry overflow");return{encode:r=>{if(!isBytes$1(r))throw new Error("radix2.encode input should be Uint8Array");return convertRadix2(Array.from(r),8,e,!t)},decode:r=>(anumArr("radix2.decode",r),Uint8Array.from(convertRadix2(r,e,8,t)))}}function unsafeWrapper(e){return afn(e),function(...t){try{return e.apply(null,t)}catch(r){}}}const hasBase64Builtin=(()=>"function"==typeof Uint8Array.from([]).toBase64&&"function"==typeof Uint8Array.fromBase64)(),decodeBase64Builtin=e=>{if(astr("base64",e),e.length>0&&!/^[A-Za-z0-9=+/]+$/.test(e))throw new Error("invalid base64");return Uint8Array.fromBase64(e,{alphabet:"base64",lastChunkHandling:"strict"})},base64=hasBase64Builtin?{encode:e=>(abytes$1(e),e.toBase64()),decode:e=>decodeBase64Builtin(e)}:chain(radix2(6),alphabet("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),padding(6),join("")),BECH_ALPHABET=chain(alphabet("qpzry9x8gf2tvdw0s3jn54khce6mua7l"),join("")),POLYMOD_GENERATORS=[996825010,642813549,513874426,1027748829,705979059];function bech32Polymod(e){const t=e>>25;let r=(33554431&e)<<5;for(let n=0;n<POLYMOD_GENERATORS.length;n++)1==(t>>n&1)&&(r^=POLYMOD_GENERATORS[n]);return r}function bechChecksum(e,t,r=1){const n=e.length;let i=1;for(let o=0;o<n;o++){const t=e.charCodeAt(o);if(t<33||t>126)throw new Error(`Invalid prefix (${e})`);i=bech32Polymod(i)^t>>5}i=bech32Polymod(i);for(let o=0;o<n;o++)i=bech32Polymod(i)^31&e.charCodeAt(o);for(let o of t)i=bech32Polymod(i)^o;for(let o=0;o<6;o++)i=bech32Polymod(i);return i^=r,BECH_ALPHABET.encode(convertRadix2([i%powers[30]],30,5,!1))}function genBech32(e){const t="bech32"===e?1:734539939,r=radix2(5),n=r.decode,i=r.encode,o=unsafeWrapper(n);function s(e,r,n=90){astr("bech32.encode prefix",e),isBytes$1(r)&&(r=Array.from(r)),anumArr("bech32.encode",r);const i=e.length;if(0===i)throw new TyLine truncated
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */function isBytes(e){return e instanceof Uint8Array||ArrayBuffer.isView(e)&&"Uint8Array"===e.constructor.name}function abool(e){if("boolean"!=typeof e)throw new Error(`boolean expected, not ${e}`)}function anumber(e){if(!Number.isSafeInteger(e)||e<0)throw new Error("positive integer expected, got "+e)}function abytes(e,t,r=""){const n=isBytes(e),i=e?.length,o=void 0!==t;if(!n||o&&i!==t)throw new Error((r&&`"${r}" `)+"expected Uint8Array"+(o?` of length ${t}`:"")+", got "+(n?`length=${i}`:"type="+typeof e));return e}function u32(e){return new Uint32Array(e.buffer,e.byteOffset,Math.floor(e.byteLength/4))}function clean(...e){for(let t=0;t<e.length;t++)e[t].fill(0)}const isLE=(()=>68===new Uint8Array(new Uint32Array([287454020]).buffer)[0])();function overlapBytes(e,t){return e.buffer===t.buffer&&e.byteOffset<t.byteOffset+t.byteLength&&t.byteOffset<e.byteOffset+e.byteLength}function complexOverlapBytes(e,t){if(overlapBytes(e,t)&&e.byteOffset<t.byteOffset)throw new Error("complex overlap of input and output is not supported")}function checkOpts(e,t){if(null==t||"object"!=typeof t)throw new Error("options must be defined");return Object.assign(e,t)}function equalBytes(e,t){if(e.length!==t.length)return!1;let r=0;for(let n=0;n<e.length;n++)r|=e[n]^t[n];return 0===r}const wrapCipher=(e,t)=>{function r(r,...n){if(abytes(r,void 0,"key"),!isLE)throw new Error("Non little-endian hardware is not yet supported");void 0!==e.nonceLength&&abytes(n[0],e.varSizeNonce?void 0:e.nonceLength,"nonce");const i=e.tagLength;i&&void 0!==n[1]&&abytes(n[1],void 0,"AAD");const o=t(r,...n),s=(e,t)=>{if(void 0!==t){if(2!==e)throw new Error("cipher output not supported");abytes(t,void 0,"output")}};let a=!1;const l={encrypt(e,t){if(a)throw new Error("cannot encrypt() twice with same key + nonce");return a=!0,abytes(e),s(o.encrypt.length,t),o.encrypt(e,t)},decrypt(e,t){if(abytes(e),i&&e.length<i)throw new Error('"ciphertext" expected length bigger than tagLength='+i);return s(o.decrypt.length,t),o.decrypt(e,t)}};return l}return Object.assign(r,e),r};function getOutput(e,t,r=!0){if(void 0===t)return new Uint8Array(e);if(t.length!==e)throw new Error('"output" expected Uint8Array of length '+e+", got: "+t.length);if(r&&!isAligned32$1(t))throw new Error("invalid output, must be aligned");return t}function isAligned32$1(e){return e.byteOffset%4==0}function copyBytes(e){return Uint8Array.from(e)}const BLOCK_SIZE=16,POLY=283;function validateKeyLength(e){if(![16,24,32].includes(e.length))throw new Error('"aes key" expected Uint8Array of length 16/24/32, got length='+e.length)}function mul2(e){return e<<1^POLY&-(e>>7)}function mul(e,t){let r=0;for(;t>0;t>>=1)r^=e&-(1&t),e=mul2(e);return r}const sbox=(()=>{const e=new Uint8Array(256);for(let r=0,n=1;r<256;r++,n^=mul2(n))e[r]=n;const t=new Uint8Array(256);t[0]=99;for(let r=0;r<255;r++){let n=e[255-r];n|=n<<8,t[e[r]]=255&(n^n>>4^n>>5^n>>6^n>>7^99)}return clean(e),t})(),invSbox=sbox.map((e,t)=>sbox.indexOf(t)),rotr32_8=e=>e<<24|e>>>8,rotl32_8=e=>e<<8|e>>>24;function genTtable(e,t){if(256!==e.length)throw new Error("Wrong sbox length");const r=new Uint32Array(256).map((r,n)=>t(e[n])),n=r.map(rotl32_8),i=n.map(rotl32_8),o=i.map(rotl32_8),s=new Uint32Array(65536),a=new Uint32Array(65536),l=new Uint16Array(65536);for(let c=0;c<256;c++)for(let t=0;t<256;t++){const u=256*c+t;s[u]=r[c]^n[t],a[u]=i[c]^o[t],l[u]=e[c]<<8|e[t]}return{sbox:e,sbox2:l,T0:r,T1:n,T2:i,T3:o,T01:s,T23:a}}const tableEncoding=genTtable(sbox,e=>mul(e,3)<<24|e<<16|e<<8|mul(e,2)),tableDecoding=genTtable(invSbox,e=>mul(e,11)<<24|mul(e,13)<<16|mul(e,9)<<8|mul(e,14)),xPowers=(()=>{const e=new Uint8Array(16);for(let t=0,r=1;t<16;t++,r=mul2(r))e[t]=r;return e})();function expandKeyLE(e){abytes(e);const t=e.length;validateKeyLength(e);const{sbox2:r}=tableEncoding,n=[];isAligned32$1(e)||n.push(e=copyBytes(e));const i=u32(e),o=i.length,s=e=>applySbox(r,e,e,e,e),a=new Uint32Array(t+28);a.set(i);for(let l=o;l<a.length;l++){let e=a[l-1];l%o===0?e=s(rotr32_8(e))^xPowers[l/o-1]:o>6&&l%o===4&&(e=s(e)),a[l]=a[l-o]^e}return clean(...n),a}function expandKeyDecLE(e){const t=expandKeyLE(e),r=t.slice(),n=t.length,{sbox2:i}=tableEncoding,{T0:o,T1:s,T2:a,T3:l}=tableDecoding;for(let c=0;c<n;c+=4)for(let e=0;e<4;e++)r[c+e]=t[n-c-4+e];clean(t);for(let c=4;c<n-4;c++){const e=r[c],t=applySbox(i,e,e,e,e);r[c]=o[255&t]^s[t>>>8&255]^a[t>>>16&255]^l[t>>>24]}return r}function apply0123(e,t,r,n,i,o){return e[r<<8&65280|n>>>8&255]^t[i>>>8&65280|o>>>24&255]}function applySbox(e,t,r,n,i){return e[255&t|65280&r]|e[n>>>16&255|i>>>16&65280]<<16}function encrypt$1(e,t,r,n,i){const{sbox2:o,T01:s,T23:a}=tableEncoding;let l=0;t^=e[l++],r^=e[l++],n^=e[l++],i^=e[l++];const c=e.length/4-2;for(let u=0;u<c;u++){const o=e[l++]^apply0123(s,a,t,r,n,i),c=e[l++]^apply0123(s,a,r,n,i,t),u=e[l++]^apply0123(s,a,n,i,t,r),h=e[l++]^apply0123(s,a,i,t,r,n);t=o,r=c,n=u,i=h}return{s0:e[l++]^applySbox(o,t,r,n,i),s1:e[l++]^applySbox(o,rLine truncated
+1
View File
@@ -0,0 +1 @@
:root{--default-list-color: #fc3;--hover-bg: #f8f9fa;--sidebar-width: 350px;--link-color: #2a7fff;--link-color-visited: #6a4fbf;--marker-color-primary: #ea4335;--marker-color-dark: #b31412;--danger-color: var(--marker-color-primary);--danger-color-dark: var(--marker-color-dark)}html,body{height:100%;overscroll-behavior:none;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}button{-webkit-tap-highlight-color:transparent}body{margin:0;font-family:Noto Sans,sans-serif;font-size:16px;font-weight:400;color:#333}#root,#ember-testing{height:100%}.map-container{background:#f8f9fa;-webkit-tap-highlight-color:transparent;outline:none;position:absolute;inset:0}#rs-widget-container{position:absolute;top:60px;right:10px;z-index:4000;display:none}#rs-widget-container.visible{display:block}.rs-backdrop{position:fixed;inset:0;z-index:3999}.app-header{position:absolute;top:0;left:0;right:0;height:60px;padding:.5rem 1rem;z-index:3000;pointer-events:none;display:grid;grid-template-columns:1fr auto 1fr;grid-template-areas:"search chips user";align-items:center;gap:1rem}@media(max-width:768px){.app-header{padding:.5rem .5rem 0;height:auto;grid-template-columns:1fr auto;grid-template-areas:"search user" "chips chips";row-gap:8px}}.header-left,.header-right,.header-center{pointer-events:auto}.header-left{display:flex;align-items:center;grid-area:search;justify-self:start;width:100%}@media not (max-width:768px){.header-left{min-width:300px;max-width:400px}}@media not (max-width:768px){.header-left{min-width:300px;max-width:350px}}.header-right{grid-area:user;justify-self:end}.header-center{grid-area:chips;display:flex;justify-content:center;min-width:0}@media not (max-width:768px){.header-center .category-chips-scroll{width:auto;max-width:100%}}@media(max-width:768px){.header-center{width:100%;overflow:hidden;justify-content:start}.header-center.searching{display:none}}.btn-press{transition:transform .1s}.btn-press:active{transform:scale(.95)}.user-btn{background:none;border:none;cursor:pointer;padding:0;display:flex;align-items:center;justify-content:center}.user-avatar-placeholder{width:40px;height:40px;background:#2a3743;border-radius:50%;display:flex;align-items:center;justify-content:center;flex-shrink:0}.user-avatar{width:40px;height:40px;border-radius:50%;object-fit:cover;flex-shrink:0;display:block}.photo-preview-img{max-width:100%;height:auto}.dropzone{border:2px dashed #ccc;border-radius:8px;text-align:center;transition:all .2s ease;margin:1.5rem 0 1rem;background-color:#ffffff05;cursor:pointer;aspect-ratio:4 / 3}.dropzone.is-dragging{border-color:#61afef;background-color:#61afef0d}.dropzone-label{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:1rem;cursor:pointer;color:#898989;width:100%;height:100%}.dropzone-label p{margin:0}.file-input-hidden{display:none}.photo-grid{display:flex;flex-direction:column;gap:12px;margin-bottom:20px}.photo-upload-item{position:relative;aspect-ratio:4 / 3;border-radius:6px;overflow:hidden;background:#1e262e;width:100%}.photo-upload-item img{position:absolute;top:0;left:0;width:100%;height:100%;object-fit:contain;display:block;z-index:1}.photo-upload-item .overlay,.photo-upload-item .btn-remove-photo{z-index:2}.photo-upload-item .overlay{position:absolute;inset:0;background:#0009;display:flex;flex-direction:column;align-items:center;justify-content:center}.upload-status-text{color:#fff;margin-top:1rem;font-size:.9rem;text-shadow:0 1px 3px rgb(0 0 0 / 80%);text-align:center;padding:0 1rem}.photo-upload-item .error-overlay{background:#e06c75cc;cursor:pointer;border:none;padding:0;margin:0;width:100%}.photo-upload-item .btn-remove-photo{position:absolute;top:4px;right:4px;background:#000000b3;border:none;border-radius:50%;width:24px;height:24px;display:flex;align-items:center;justify-content:center;cursor:pointer;color:#fff;padding:0}.photo-upload-item .btn-remove-photo:hover{background:var(--danger-color)}.spin-animation{animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.btn-publish{width:100%}.user-menu-container{position:relative}.user-menu-popover{position:absolute;top:100%;right:0;margin-top:10px;width:280px;background:#fff;border-radius:8px;box-shadow:0 4px 12px #00000026;padding:1rem;z-index:3001}.menu-backdrop{position:fixed;inset:0;z-index:3000}.account-list{list-style:none;padding:0;margin:0}.account-item{display:flex;flex-direction:column;padding:1rem 0;border-bottom:1px solid #eee}.account-item:last-child{border-bottom:none;padding-bottom:0}.account-item:first-child{padding-top:0}.account-header{display:flex;justify-content:space-between;align-items:center}.account-status{font-size:.85rem;color:#898989;margin-top:.35rem;margin-left:calc(18px + .75rem);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.account-status strong{font-weight:600}.account-item.disabled{opacity:.5;pointer-events:none}.account-iLine truncated
-16
View File
@@ -1,16 +0,0 @@
const t=Object.freeze(Object.defineProperty({__proto__:null,get default(){return O7}},Symbol.toStringTag,{value:"Module"}));!function(){const t=document.createElement("link").relList;if(!(t&&t.supports&&t.supports("modulepreload"))){for(const t of document.querySelectorAll('link[rel="modulepreload"]'))e(t);new MutationObserver(t=>{for(const n of t)if("childList"===n.type)for(const t of n.addedNodes)"LINK"===t.tagName&&"modulepreload"===t.rel&&e(t)}).observe(document,{childList:!0,subtree:!0})}function e(t){if(t.ep)return;t.ep=!0;const e=function(t){const e={};return t.integrity&&(e.integrity=t.integrity),t.referrerPolicy&&(e.referrerPolicy=t.referrerPolicy),"use-credentials"===t.crossOrigin?e.credentials="include":"anonymous"===t.crossOrigin?e.credentials="omit":e.credentials="same-origin",e}(t);fetch(t.href,e)}}();const e="object"==typeof self&&null!==self&&self.Object===Object&&"undefined"!=typeof Window&&self.constructor===Window&&"object"==typeof document&&null!==document&&self.document===document&&"object"==typeof location&&null!==location&&self.location===location&&"object"==typeof history&&null!==history&&self.history===history&&"object"==typeof navigator&&null!==navigator&&self.navigator===navigator&&"string"==typeof navigator.userAgent,n=e?self:null,r=e?self.location:null,i=e?self.history:null,s=e?self.navigator.userAgent:"Lynx (textmode)",o=!(!e||"object"!=typeof chrome||"object"==typeof opera),a=!!e&&/Firefox|FxiOS/.test(s),l=Object.freeze(Object.defineProperty({__proto__:null,hasDOM:e,history:i,isChrome:o,isFirefox:a,location:r,userAgent:s,window:n},Symbol.toStringTag,{value:"Module"}));function c(t){return t&&t.Object===Object?t:void 0}const u=c((h="object"==typeof global&&global)&&void 0===h.nodeType?h:void 0)||c("object"==typeof self&&self)||c("object"==typeof window&&window)||"undefined"!=typeof mainContext&&mainContext||new Function("return this")();var h;const d=function(t,e){return void 0===e?{imports:t,exports:t,lookup:t}:{imports:e.imports||t,exports:e.exports||t,lookup:e.lookup||t}}(u,u.Ember);function p(){return d.lookup}function f(t){d.lookup=t}const g={ENABLE_OPTIONAL_FEATURES:!1,EXTEND_PROTOTYPES:{Array:!1},LOG_STACKTRACE_ON_DEPRECATION:!0,LOG_VERSION:!0,RAISE_ON_DEPRECATION:!1,STRUCTURED_PROFILE:!1,_DEBUG_RENDER_TREE:!1,_ALL_DEPRECATIONS_ENABLED:!1,_OVERRIDE_DEPRECATION_VERSION:null,_DEFAULT_ASYNC_OBSERVERS:!1,_RERENDER_LOOP_LIMIT:1e3,EMBER_LOAD_HOOKS:{},FEATURES:{}};function m(){return g}(t=>{if("object"!=typeof t||null===t)return;for(let r in t){if(!Object.prototype.hasOwnProperty.call(t,r)||"EXTEND_PROTOTYPES"===r||"EMBER_LOAD_HOOKS"===r)continue;let e=g[r];g[r]=!0===e?!1!==t[r]:!1===e?!0===t[r]:t[r]}let{EMBER_LOAD_HOOKS:e}=t;if("object"==typeof e&&null!==e)for(let r in e){if(!Object.prototype.hasOwnProperty.call(e,r))continue;let t=e[r];Array.isArray(t)&&(g.EMBER_LOAD_HOOKS[r]=t.filter(t=>"function"==typeof t))}let{FEATURES:n}=t;if("object"==typeof n&&null!==n)for(let r in n)Object.prototype.hasOwnProperty.call(n,r)&&(g.FEATURES[r]=!0===n[r])})(u.EmberENV);let y=()=>{},v=()=>{},b=!1;function w(){return b}function _(t){b=Boolean(t)}let x=()=>{};const{toString:C}=Object.prototype,{toString:S}=Function.prototype,{isArray:k}=Array,{keys:E}=Object,{stringify:T}=JSON,R=100,M=/^[\w$]+$/;function L(t){return"number"==typeof t&&2===arguments.length?this:I(t,0)}function I(t,e,n){let r=!1;switch(typeof t){case"undefined":return"undefined";case"object":if(null===t)return"null";if(k(t)){r=!0;break}if(t.toString===C||void 0===t.toString)break;return t.toString();case"function":return t.toString===S?t.name?`[Function:${t.name}]`:"[Function]":t.toString();case"string":return T(t);default:return t.toString()}if(void 0===n)n=new WeakSet;else if(n.has(t))return"[Circular]";return n.add(t),r?function(t,e,n){if(e>4)return"[Array]";let r="[";for(let i=0;i<t.length;i++){if(r+=0===i?" ":", ",i>=R){r+=`... ${t.length-R} more items`;break}r+=I(t[i],e,n)}return r+=" ]",r}(t,e+1,n):function(t,e,n){if(e>4)return"[Object]";let r="{",i=E(t);for(let s=0;s<i.length;s++){if(r+=0===s?" ":", ",s>=R){r+=`... ${i.length-R} more keys`;break}let o=i[s];r+=`${P(String(o))}: ${I(t[o],e,n)}`}return r+=" }",r}(t,e+1,n)}function P(t){return M.test(t)?t:T(t)}function A(t){let e=t.lookup("renderer:-dom");if(!e)throw new Error("BUG: owner is missing renderer");return e.debugRenderTree.capture()}const O=()=>{};let F=O,z=O,D=O,N=O,j=O,U=O,B=O,$=O,q=function(){return arguments[arguments.length-1]};function V(){}const W=Object.freeze(Object.defineProperty({__proto__:null,_warnIfUsingStrippedFeatureFlags:void 0,assert:y,captureRenderTree:A,debug:D,debugFreeze:j,debugSeal:N,deprecate:V,deprecateFunc:q,getDebugFunction:$,info:F,inspect:L,isTesting:w,registerDeprecationHandler:v,registerWarnHandler:x,runInDebug:U,setDebugFunction:B,setTesting:_,warn:z},Symbol.toStringTag,{value:"Module"}));function H(t){let e=Object.create(null);e[t]=1;for(let n in e)if(n===t)return n;return t}function G(t){return null!==t&&("object"==typeof t||"fLine truncated
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */if(68!==new Uint8Array(new Uint32Array([287454020]).buffer)[0])throw new Error("Non little-endian hardware is not supported");function zx(t){if("string"==typeof t&&(t=function(t){if("string"!=typeof t)throw new Error("utf8ToBytes expected string, got "+typeof t);return new Uint8Array((new TextEncoder).encode(t))}(t)),!Ax(t))throw new Error("expected Uint8Array, got "+typeof t);return t}let Dx=class{clone(){return this._cloneInto()}};function Nx(t){const e=e=>t().update(zx(e)).digest(),n=t();return e.outputLen=n.outputLen,e.blockLen=n.blockLen,e.create=()=>t(),e}function jx(t=32){if(Px&&"function"==typeof Px.getRandomValues)return Px.getRandomValues(new Uint8Array(t));throw new Error("crypto.getRandomValues must be defined")}let Ux=class extends Dx{constructor(t,e,n,r){super(),this.blockLen=t,this.outputLen=e,this.padOffset=n,this.isLE=r,this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.buffer=new Uint8Array(t),this.view=Ox(this.buffer)}update(t){Ix(this);const{view:e,buffer:n,blockLen:r}=this,i=(t=zx(t)).length;for(let s=0;s<i;){const o=Math.min(r-this.pos,i-s);if(o===r){const e=Ox(t);for(;r<=i-s;s+=r)this.process(e,s);continue}n.set(t.subarray(s,s+o),this.pos),this.pos+=o,s+=o,this.pos===r&&(this.process(e,0),this.pos=0)}return this.length+=t.length,this.roundClean(),this}digestInto(t){Ix(this),function(t,e){Lx(t);const n=e.outputLen;if(t.length<n)throw new Error(`digestInto() expects output buffer of length at least ${n}`)}(t,this),this.finished=!0;const{buffer:e,view:n,blockLen:r,isLE:i}=this;let{pos:s}=this;e[s++]=128,this.buffer.subarray(s).fill(0),this.padOffset>r-s&&(this.process(n,0),s=0);for(let u=s;u<r;u++)e[u]=0;!function(t,e,n,r){if("function"==typeof t.setBigUint64)return t.setBigUint64(e,n,r);const i=BigInt(32),s=BigInt(4294967295),o=Number(n>>i&s),a=Number(n&s),l=r?4:0,c=r?0:4;t.setUint32(e+l,o,r),t.setUint32(e+c,a,r)}(n,r-8,BigInt(8*this.length),i),this.process(n,0);const o=Ox(t),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const l=a/4,c=this.get();if(l>c.length)throw new Error("_sha2: outputLen bigger than state");for(let u=0;u<l;u++)o.setUint32(4*u,c[u],i)}digest(){const{buffer:t,outputLen:e}=this;this.digestInto(t);const n=t.slice(0,e);return this.destroy(),n}_cloneInto(t){t||(t=new this.constructor),t.set(...this.get());const{blockLen:e,buffer:n,length:r,finished:i,destroyed:s,pos:o}=this;return t.length=r,t.pos=o,t.finished=i,t.destroyed=s,r%e&&t.buffer.set(n),t}};const Bx=(t,e,n)=>t&e^~t&n,$x=(t,e,n)=>t&e^t&n^e&n,qx=new Uint32Array([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),Vx=new Uint32Array([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),Wx=new Uint32Array(64);let Hx=class extends Ux{constructor(){super(64,32,8,!1),this.A=0|Vx[0],this.B=0|Vx[1],this.C=0|Vx[2],this.D=0|Vx[3],this.E=0|Vx[4],this.F=0|Vx[5],this.G=0|Vx[6],this.H=0|Vx[7]}get(){const{A:t,B:e,C:n,D:r,E:i,F:s,G:o,H:a}=this;return[t,e,n,r,i,s,o,a]}set(t,e,n,r,i,s,o,a){this.A=0|t,this.B=0|e,this.C=0|n,this.D=0|r,this.E=0|i,this.F=0|s,this.G=0|o,this.H=0|a}process(t,e){for(let u=0;u<16;u++,e+=4)Wx[u]=t.getUint32(e,!1);for(let u=16;u<64;u++){const t=Wx[u-15],e=Wx[u-2],n=Fx(t,7)^Fx(t,18)^t>>>3,r=Fx(e,17)^Fx(e,19)^e>>>10;Wx[u]=r+Wx[u-7]+n+Wx[u-16]|0}let{A:n,B:r,C:i,D:s,E:o,F:a,G:l,H:c}=this;for(let u=0;u<64;u++){const t=c+(Fx(o,6)^Fx(o,11)^Fx(o,25))+Bx(o,a,l)+qx[u]+Wx[u]|0,e=(Fx(n,2)^Fx(n,13)^Fx(n,22))+$x(n,r,i)|0;c=l,l=a,a=o,o=s+t|0,s=i,i=r,r=n,n=t+e|0}n=n+this.A|0,r=r+this.B|0,i=i+this.C|0,s=s+this.D|0,o=o+this.E|0,a=a+this.F|0,l=l+this.G|0,c=c+this.H|0,this.set(n,r,i,s,o,a,l,c)}roundClean(){Wx.fill(0)}destroy(){this.set(0,0,0,0,0,0,0,0),this.buffer.fill(0)}};const Gx=Nx(()=>new Hx),Kx=BigInt(0),Zx=BigInt(1),Yx=BigInt(2),Xx=t=>t instanceof Uint8Array,Jx=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,"0"));
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function Qx(t){if(!Xx(t))throw new Error("Uint8Array expected");let e="";for(let n=0;n<t.length;n++)e+=Jx[t[n]];return e}function tC(t){const e=t.toString(16);return 1&e.length?`0${e}`:e}function eC(t){if("string"!=typeof t)throw new Error("hex string expected, got "+typeof t);return BigInt(""===t?"0":`0x${t}`)}function nC(t){if("string"!=typeof t)throw new Error("hex string expected, got "+typeof t);const e=t.length;if(e%2)throw new Error("padded hex string expected, got unpadded hex of length "+e);const n=new Uint8Array(e/2);for(let r=0;r<n.length;r++){const e=2*r,i=t.slice(e,e+2),s=Number.parseInt(i,16);if(Number.isNaN(s)||s<0)throw new Error("Invalid byte sequence");n[r]=s}return n}function rC(t){return eC(Qx(t))}function iC(t){if(!Xx(t))throw new Error("Uint8Array expected");return eC(Qx(Uint8Array.from(t).reverse()))}function sC(t,e){return nC(t.toString(16).padStart(2*e,"0"))}function oC(t,e){return sC(t,e).reverse()}function aC(t,e,n){let r;if("string"==typeof e)try{r=nC(e)}catch(ej){throw new Error(`${t} must be valid hex string, got "${e}". Cause: ${ej}`)}else{if(!Xx(e))throw new Error(`${t} must be hex string or Uint8Array`);r=Uint8Array.from(e)}const i=r.length;if("number"==typeof n&&i!==n)throw new Error(`${t} expected ${n} bytes, got ${i}`);return r}function lC(...t){const e=new Uint8Array(t.reduce((t,e)=>t+e.length,0));let n=0;return t.forEach(t=>{if(!Xx(t))throw new Error("Uint8Array expected");e.set(t,n),n+=t.length}),e}const cC=t=>(Yx<<BigInt(t-1))-Zx,uC=t=>new Uint8Array(t),hC=t=>Uint8Array.from(t);function dC(t,e,n){if("number"!=typeof t||t<2)throw new Error("hashLen must be a number");if("number"!=typeof e||e<2)throw new Error("qByteLen must be a number");if("function"!=typeof n)throw new Error("hmacFn must be a function");let r=uC(t),i=uC(t),s=0;const o=()=>{r.fill(1),i.fill(0),s=0},a=(...t)=>n(i,r,...t),l=(t=uC())=>{i=a(hC([0]),t),r=a(),0!==t.length&&(i=a(hC([1]),t),r=a())},c=()=>{if(s++>=1e3)throw new Error("drbg: tried 1000 values");let t=0;const n=[];for(;t<e;){r=a();const e=r.slice();n.push(e),t+=r.length}return lC(...n)};return(t,e)=>{let n;for(o(),l(t);!(n=e(c()));)l();return o(),n}}const pC={bigint:t=>"bigint"==typeof t,function:t=>"function"==typeof t,boolean:t=>"boolean"==typeof t,string:t=>"string"==typeof t,stringOrUint8Array:t=>"string"==typeof t||t instanceof Uint8Array,isSafeInteger:t=>Number.isSafeInteger(t),array:t=>Array.isArray(t),field:(t,e)=>e.Fp.isValid(t),hash:t=>"function"==typeof t&&Number.isSafeInteger(t.outputLen)};function fC(t,e,n={}){const r=(e,n,r)=>{const i=pC[n];if("function"!=typeof i)throw new Error(`Invalid validator "${n}", expected function`);const s=t[e];if(!(r&&void 0===s||i(s,t)))throw new Error(`Invalid param ${String(e)}=${s} (${typeof s}), expected ${n}`)};for(const[i,s]of Object.entries(e))r(i,s,!1);for(const[i,s]of Object.entries(n))r(i,s,!0);return t}const gC=Object.freeze(Object.defineProperty({__proto__:null,bitGet:function(t,e){return t>>BigInt(e)&Zx},bitLen:function(t){let e;for(e=0;t>Kx;t>>=Zx,e+=1);return e},bitMask:cC,bitSet:(t,e,n)=>t|(n?Zx:Kx)<<BigInt(e),bytesToHex:Qx,bytesToNumberBE:rC,bytesToNumberLE:iC,concatBytes:lC,createHmacDrbg:dC,ensureBytes:aC,equalBytes:function(t,e){if(t.length!==e.length)return!1;for(let n=0;n<t.length;n++)if(t[n]!==e[n])return!1;return!0},hexToBytes:nC,hexToNumber:eC,numberToBytesBE:sC,numberToBytesLE:oC,numberToHexUnpadded:tC,numberToVarBytesBE:function(t){return nC(tC(t))},utf8ToBytes:function(t){if("string"!=typeof t)throw new Error("utf8ToBytes expected string, got "+typeof t);return new Uint8Array((new TextEncoder).encode(t))},validateObject:fC},Symbol.toStringTag,{value:"Module"})),mC=BigInt(0),yC=BigInt(1),vC=BigInt(2),bC=BigInt(3),wC=BigInt(4),_C=BigInt(5),xC=BigInt(8);
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */function CC(t,e){const n=t%e;return n>=mC?n:e+n}function SC(t,e,n){if(n<=mC||e<mC)throw new Error("Expected power/modulo > 0");if(n===yC)return mC;let r=yC;for(;e>mC;)e&yC&&(r=r*t%n),t=t*t%n,e>>=yC;return r}function kC(t,e,n){let r=t;for(;e-- >mC;)r*=r,r%=n;return r}function EC(t,e){if(t===mC||e<=mC)throw new Error(`invert: expected positive integers, got n=${t} mod=${e}`);let n=CC(t,e),r=e,i=mC,s=yC;for(;n!==mC;){const t=r%n,e=i-s*(r/n);r=n,n=t,i=s,s=e}if(r!==yC)throw new Error("invert: does not exist");return CC(i,e)}BigInt(9),BigInt(16);const TC=["create","isValid","is0","neg","inv","sqrt","sqr","eql","add","sub","mul","pow","div","addN","subN","mulN","sqrN"];function RC(t,e){const n=void 0!==e?e:t.toString(2).length;return{nBitLength:n,nByteLength:Math.ceil(n/8)}}function MC(t){if("bigint"!=typeof t)throw new Error("field order must be bigint");const e=t.toString(2).length;return Math.ceil(e/8)}function LC(t){const e=MC(t);return e+Math.ceil(e/2)}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
const IC=BigInt(0),PC=BigInt(1);function AC(t){return fC(t.Fp,TC.reduce((t,e)=>(t[e]="function",t),{ORDER:"bigint",MASK:"bigint",BYTES:"isSafeInteger",BITS:"isSafeInteger"})),fC(t,{n:"bigint",h:"bigint",Gx:"field",Gy:"field"},{nBitLength:"isSafeInteger",nByteLength:"isSafeInteger"}),Object.freeze({...RC(t.n,t.nBitLength),...t,p:t.Fp.ORDER})}
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */const{bytesToNumberBE:OC,hexToBytes:FC}=gC,zC={Err:class extends Error{constructor(t=""){super(t)}},_parseInt(t){const{Err:e}=zC;if(t.length<2||2!==t[0])throw new e("Invalid signature integer tag");const n=t[1],r=t.subarray(2,n+2);if(!n||r.length!==n)throw new e("Invalid signature integer: wrong length");if(128&r[0])throw new e("Invalid signature integer: negative");if(0===r[0]&&!(128&r[1]))throw new e("Invalid signature integer: unnecessary leading zero");return{d:OC(r),l:t.subarray(n+2)}},toSig(t){const{Err:e}=zC,n="string"==typeof t?FC(t):t;if(!(n instanceof Uint8Array))throw new Error("ui8a expected");let r=n.length;if(r<2||48!=n[0])throw new e("Invalid signature tag");if(n[1]!==r-2)throw new e("Invalid signature: incorrect length");const{d:i,l:s}=zC._parseInt(n.subarray(2)),{d:o,l:a}=zC._parseInt(s);if(a.length)throw new e("Invalid signature: left bytes after parsing");return{r:i,s:o}},hexFromSig(t){const e=t=>8&Number.parseInt(t[0],16)?"00"+t:t,n=t=>{const e=t.toString(16);return 1&e.length?`0${e}`:e},r=e(n(t.s)),i=e(n(t.r)),s=r.length/2,o=i.length/2,a=n(s),l=n(o);return`30${n(o+s+4)}02${l}${i}02${a}${r}`}},DC=BigInt(0),NC=BigInt(1);BigInt(2);const jC=BigInt(3);BigInt(4);let UC=class extends Dx{constructor(t,e){super(),this.finished=!1,this.destroyed=!1,function(t){if("function"!=typeof t||"function"!=typeof t.create)throw new Error("Hash should be wrapped by utils.wrapConstructor");Mx(t.outputLen),Mx(t.blockLen)}(t);const n=zx(e);if(this.iHash=t.create(),"function"!=typeof this.iHash.update)throw new Error("Expected instance of class which extends utils.Hash");this.blockLen=this.iHash.blockLen,this.outputLen=this.iHash.outputLen;const r=this.blockLen,i=new Uint8Array(r);i.set(n.length>r?t.create().update(n).digest():n);for(let s=0;s<i.length;s++)i[s]^=54;this.iHash.update(i),this.oHash=t.create();for(let s=0;s<i.length;s++)i[s]^=106;this.oHash.update(i),i.fill(0)}update(t){return Ix(this),this.iHash.update(t),this}digestInto(t){Ix(this),Lx(t,this.outputLen),this.finished=!0,this.iHash.digestInto(t),this.oHash.update(t),this.oHash.digestInto(t),this.destroy()}digest(){const t=new Uint8Array(this.oHash.outputLen);return this.digestInto(t),t}_cloneInto(t){t||(t=Object.create(Object.getPrototypeOf(this),{}));const{oHash:e,iHash:n,finished:r,destroyed:i,blockLen:s,outputLen:o}=this;return t.finished=r,t.destroyed=i,t.blockLen=s,t.outputLen=o,t.oHash=e._cloneInto(t.oHash),t.iHash=n._cloneInto(t.iHash),t}destroy(){this.destroyed=!0,this.oHash.destroy(),this.iHash.destroy()}};const BC=(t,e,n)=>new UC(t,e).update(n).digest();
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
function $C(t){return{hash:t,hmac:(e,...n)=>BC(t,e,function(...t){const e=new Uint8Array(t.reduce((t,e)=>t+e.length,0));let n=0;return t.forEach(t=>{if(!Ax(t))throw new Error("Uint8Array expected");e.set(t,n),n+=t.length}),e}(...n)),randomBytes:jx}}BC.create=(t,e)=>new UC(t,e);
/*! noble-curves - MIT License (c) 2022 Paul Miller (paulmillr.com) */
const qC=BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),VC=BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),WC=BigInt(1),HC=BigInt(2),GC=(t,e)=>(t+e/HC)/e;function KC(t){const e=qC,n=BigInt(3),r=BigInt(6),i=BigInt(11),s=BigInt(22),o=BigInt(23),a=BigInt(44),l=BigInt(88),c=t*t*t%e,u=c*c*t%e,h=kC(u,n,e)*u%e,d=kC(h,n,e)*u%e,p=kC(d,HC,e)*c%e,f=kC(p,i,e)*p%e,g=kC(f,s,e)*f%e,m=kC(g,a,e)*g%e,y=kC(m,l,e)*m%e,v=kC(y,a,e)*g%e,b=kC(v,n,e)*u%e,w=kC(b,o,e)*f%e,_=kC(w,r,e)*c%e,x=kC(_,HC,e);if(!ZC.eql(ZC.sqr(x),t))throw new Error("Cannot find square root");return x}const ZC=function(t,e,n=!1,r={}){if(t<=mC)throw new Error(`Expected Field ORDER > 0, got ${t}`);const{nBitLength:i,nByteLength:s}=RC(t,e);if(s>2048)throw new Error("Field lengths over 2048 bytes are not supported");const o=function(t){if(t%wC===bC){const e=(t+yC)/wC;return function(t,n){const r=t.pow(n,e);if(!t.eql(t.sqr(r),n))throw new Error("Cannot find square root");return r}}if(t%xC===_C){const e=(t-_C)/xC;return function(t,n){const r=t.mul(n,vC),i=t.pow(r,e),s=t.mul(n,i),o=t.mul(t.mul(s,vC),i),a=t.mul(s,t.sub(o,t.ONE));if(!t.eql(t.sqr(a),n))throw new Error("Cannot find square root");return a}}return function(t){const e=(t-yC)/vC;let n,r,i;for(n=t-yC,r=0;n%vC===mC;n/=vC,r++);for(i=vC;i<t&&SC(i,e,t)!==t-yC;i++);if(1===r){const e=(t+yC)/wC;return function(t,n){const r=t.pow(n,e);if(!t.eql(t.sqr(r),n))throw new Error("Cannot find square root");return r}}const s=(n+yC)/vC;return function(t,o){if(t.pow(o,e)===t.neg(t.ONE))throw new Error("Cannot find square root");let a=r,l=t.pow(t.mul(t.ONE,i),n),c=t.pow(o,s),u=t.pow(o,n);for(;!t.eql(u,t.ONE);){if(t.eql(u,t.ZERO))return t.ZERO;let e=1;for(let r=t.sqr(u);e<a&&!t.eql(r,t.ONE);e++)r=t.sqr(r);const n=t.pow(l,yC<<BigInt(a-e-1));l=t.sqr(n),c=t.mul(c,n),u=t.mul(u,l),a=e}return c}}(t)}(t),a=Object.freeze({ORDER:t,BITS:i,BYTES:s,MASK:cC(i),ZERO:mC,ONE:yC,create:e=>CC(e,t),isValid:e=>{if("bigint"!=typeof e)throw new Error("Invalid field element: expected bigint, got "+typeof e);return mC<=e&&e<t},is0:t=>t===mC,isOdd:t=>(t&yC)===yC,neg:e=>CC(-e,t),eql:(t,e)=>t===e,sqr:e=>CC(e*e,t),add:(e,n)=>CC(e+n,t),sub:(e,n)=>CC(e-n,t),mul:(e,n)=>CC(e*n,t),pow:(t,e)=>function(t,e,n){if(n<mC)throw new Error("Expected power > 0");if(n===mC)return t.ONE;if(n===yC)return e;let r=t.ONE,i=e;for(;n>mC;)n&yC&&(r=t.mul(r,i)),i=t.sqr(i),n>>=yC;return r}(a,t,e),div:(e,n)=>CC(e*EC(n,t),t),sqrN:t=>t*t,addN:(t,e)=>t+e,subN:(t,e)=>t-e,mulN:(t,e)=>t*e,inv:e=>EC(e,t),sqrt:r.sqrt||(t=>o(a,t)),invertBatch:t=>function(t,e){const n=new Array(e.length),r=e.reduce((e,r,i)=>t.is0(r)?e:(n[i]=e,t.mul(e,r)),t.ONE),i=t.inv(r);return e.reduceRight((e,r,i)=>t.is0(r)?e:(n[i]=t.mul(e,n[i]),t.mul(e,r)),i),n}(a,t),cmov:(t,e,n)=>n?e:t,toBytes:t=>n?oC(t,s):sC(t,s),fromBytes:t=>{if(t.length!==s)throw new Error(`Fp.fromBytes: expected ${s}, got ${t.length}`);return n?iC(t):rC(t)}});return Object.freeze(a)}(qC,void 0,void 0,{sqrt:KC}),YC=function(t,e){const n=e=>function(t){const e=function(t){const e=AC(t);return fC(e,{hash:"hash",hmac:"function",randomBytes:"function"},{bits2int:"function",bits2int_modN:"function",lowS:"boolean"}),Object.freeze({lowS:!0,...e})}(t),{Fp:n,n:r}=e,i=n.BYTES+1,s=2*n.BYTES+1;function o(t){return CC(t,r)}function a(t){return EC(t,r)}const{ProjectivePoint:l,normPrivateKeyToScalar:c,weierstrassEquation:u,isWithinCurveOrder:h}=function(t){const e=function(t){const e=AC(t);fC(e,{a:"field",b:"field"},{allowedPrivateKeyLengths:"array",wrapPrivateKey:"boolean",isTorsionFree:"function",clearCofactor:"function",allowInfinityPoint:"boolean",fromBytes:"function",toBytes:"function"});const{endo:n,Fp:r,a:i}=e;if(n){if(!r.eql(i,r.ZERO))throw new Error("Endomorphism can only be defined for Koblitz curves that have a=0");if("object"!=typeof n||"bigint"!=typeof n.beta||"function"!=typeof n.splitScalar)throw new Error("Expected endomorphism with beta: bigint and splitScalar: function")}return Object.freeze({...e})}(t),{Fp:n}=e,r=e.toBytes||((t,e)=>{const r=e.toAffine();return lC(Uint8Array.from([4]),n.toBytes(r.x),n.toBytes(r.y))}),i=e.fromBytes||(t=>{const e=t.subarray(1);return{x:n.fromBytes(e.subarray(0,n.BYTES)),y:n.fromBytes(e.subarray(n.BYTES,2*n.BYTES))}});function s(t){const{a:r,b:i}=e,s=n.sqr(t),o=n.mul(s,t);return n.add(n.add(o,n.mul(t,r)),i)}if(!n.eql(n.sqr(e.Gy),s(e.Gx)))throw new Error("bad generator point: equation left != right");function o(t){return"bigint"==typeof t&&DC<t&&t<e.n}function a(t){if(!o(t))throw new Error("Expected valid bigint: 0 < bigint < curve.n")}function l(t){const{allowedPrivateKeyLengths:n,nByteLength:r,wrapPrivateKey:i,n:s}=e;if(n&&"bigint"!=typeof t){if(t instanceof Uint8Array&&(t=Qx(t)),"string"!=typeof t||!n.includes(t.length))throw new Error("Invalid key");t=t.padStart(2*r,"0")}let o;try{o="bigint"==typeof t?t:rC(aC("private key",t,r))}catch(v5){throw new Error(`private key must be ${r} bytes, hex or bigint, not ${typeof t}`)}return i&&(o=CC(o,s)),a(o),o}const c=neLine truncated
/*! scure-base - MIT License (c) 2022 Paul Miller (paulmillr.com) */
function DS(t){if(!Number.isSafeInteger(t))throw new Error(`Wrong integer: ${t}`)}function NS(...t){const e=(t,e)=>n=>t(e(n)),n=Array.from(t).reverse().reduce((t,n)=>t?e(t,n.encode):n.encode,void 0),r=t.reduce((t,n)=>t?e(t,n.decode):n.decode,void 0);return{encode:n,decode:r}}function jS(t){return{encode:e=>{if(!Array.isArray(e)||e.length&&"number"!=typeof e[0])throw new Error("alphabet.encode input should be an array of numbers");return e.map(e=>{if(DS(e),e<0||e>=t.length)throw new Error(`Digit index outside alphabet: ${e} (alphabet: ${t.length})`);return t[e]})},decode:e=>{if(!Array.isArray(e)||e.length&&"string"!=typeof e[0])throw new Error("alphabet.decode input should be array of strings");return e.map(e=>{if("string"!=typeof e)throw new Error(`alphabet.decode: not string element=${e}`);const n=t.indexOf(e);if(-1===n)throw new Error(`Unknown letter: "${e}". Allowed: ${t}`);return n})}}}function US(t=""){if("string"!=typeof t)throw new Error("join separator should be string");return{encode:e=>{if(!Array.isArray(e)||e.length&&"string"!=typeof e[0])throw new Error("join.encode input should be array of strings");for(let t of e)if("string"!=typeof t)throw new Error(`join.encode: non-string input=${t}`);return e.join(t)},decode:e=>{if("string"!=typeof e)throw new Error("join.decode input should be string");return e.split(t)}}}function BS(t,e="="){if(DS(t),"string"!=typeof e)throw new Error("padding chr should be string");return{encode(n){if(!Array.isArray(n)||n.length&&"string"!=typeof n[0])throw new Error("padding.encode input should be array of strings");for(let t of n)if("string"!=typeof t)throw new Error(`padding.encode: non-string input=${t}`);for(;n.length*t%8;)n.push(e);return n},decode(n){if(!Array.isArray(n)||n.length&&"string"!=typeof n[0])throw new Error("padding.encode input should be array of strings");for(let t of n)if("string"!=typeof t)throw new Error(`padding.decode: non-string input=${t}`);let r=n.length;if(r*t%8)throw new Error("Invalid padding: string should have whole number of bytes");for(;r>0&&n[r-1]===e;r--)if(!((r-1)*t%8))throw new Error("Invalid padding: string has too much padding");return n.slice(0,r)}}}function $S(t){if("function"!=typeof t)throw new Error("normalize fn should be function");return{encode:t=>t,decode:e=>t(e)}}function qS(t,e,n){if(e<2)throw new Error(`convertRadix: wrong from=${e}, base cannot be less than 2`);if(n<2)throw new Error(`convertRadix: wrong to=${n}, base cannot be less than 2`);if(!Array.isArray(t))throw new Error("convertRadix: data should be array");if(!t.length)return[];let r=0;const i=[],s=Array.from(t);for(s.forEach(t=>{if(DS(t),t<0||t>=e)throw new Error(`Wrong integer: ${t}`)});;){let t=0,o=!0;for(let i=r;i<s.length;i++){const a=s[i],l=e*t+a;if(!Number.isSafeInteger(l)||e*t/e!==t||l-a!==e*t)throw new Error("convertRadix: carry overflow");if(t=l%n,s[i]=Math.floor(l/n),!Number.isSafeInteger(s[i])||s[i]*n+t!==l)throw new Error("convertRadix: carry overflow");o&&(s[i]?o=!1:r=i)}if(i.push(t),o)break}for(let o=0;o<t.length-1&&0===t[o];o++)i.push(0);return i.reverse()}CS(()=>new FS);const VS=(t,e)=>e?VS(e,t%e):t,WS=(t,e)=>t+(e-VS(t,e));function HS(t,e,n,r){if(!Array.isArray(t))throw new Error("convertRadix2: data should be array");if(e<=0||e>32)throw new Error(`convertRadix2: wrong from=${e}`);if(n<=0||n>32)throw new Error(`convertRadix2: wrong to=${n}`);if(WS(e,n)>32)throw new Error(`convertRadix2: carry overflow from=${e} to=${n} carryBits=${WS(e,n)}`);let i=0,s=0;const o=2**n-1,a=[];for(const l of t){if(DS(l),l>=2**e)throw new Error(`convertRadix2: invalid data word=${l} from=${e}`);if(i=i<<e|l,s+e>32)throw new Error(`convertRadix2: carry overflow pos=${s} from=${e}`);for(s+=e;s>=n;s-=n)a.push((i>>s-n&o)>>>0);i&=2**s-1}if(i=i<<n-s&o,!r&&s>=e)throw new Error("Excess padding");if(!r&&i)throw new Error(`Non-zero padding: ${i}`);return r&&s>0&&a.push(i>>>0),a}function GS(t,e=!1){if(DS(t),t<=0||t>32)throw new Error("radix2: bits should be in (0..32]");if(WS(8,t)>32||WS(t,8)>32)throw new Error("radix2: carry overflow");return{encode:n=>{if(!(n instanceof Uint8Array))throw new Error("radix2.encode input should be Uint8Array");return HS(Array.from(n),8,t,!e)},decode:n=>{if(!Array.isArray(n)||n.length&&"number"!=typeof n[0])throw new Error("radix2.decode input should be array of strings");return Uint8Array.from(HS(n,t,8,e))}}}function KS(t){if("function"!=typeof t)throw new Error("unsafeWrapper fn should be function");return function(...e){try{return t.apply(null,e)}catch(ej){}}}const ZS=NS(GS(4),jS("0123456789ABCDEF"),US("")),YS=NS(GS(5),jS("ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"),BS(5),US(""));NS(GS(5),jS("0123456789ABCDEFGHIJKLMNOPQRSTUV"),BS(5),US("")),NS(GS(5),jS("0123456789ABCDEFGHJKMNPQRSTVWXYZ"),US(""),$S(t=>t.toUpperCase().replace(/O/g,"0").replace(/[IL]/g,"1")));const XS=NS(GS(6),jS("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"),BS(6),US("")),JS=NS(GS(6),jS("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"),BS(6),USLine truncated
/*! noble-ciphers - MIT License (c) 2023 Paul Miller (paulmillr.com) */Object.keys(ck).join(", ");const pk=t=>new Uint32Array(t.buffer,t.byteOffset,Math.floor(t.byteLength/4));if(68!==new Uint8Array(new Uint32Array([287454020]).buffer)[0])throw new Error("Non little-endian hardware is not supported");const fk=Array.from({length:256},(t,e)=>e.toString(16).padStart(2,"0"));function gk(t){dk(t);let e="";for(let n=0;n<t.length;n++)e+=fk[t[n]];return e}function mk(t){return t>=48&&t<=57?t-48:t>=65&&t<=70?t-55:t>=97&&t<=102?t-87:void 0}function yk(t){if("string"!=typeof t)throw new Error("hex string expected, got "+typeof t);const e=t.length,n=e/2;if(e%2)throw new Error("padded hex string expected, got unpadded hex of length "+e);const r=new Uint8Array(n);for(let i=0,s=0;i<n;i++,s+=2){const e=mk(t.charCodeAt(s)),n=mk(t.charCodeAt(s+1));if(void 0===e||void 0===n){const e=t[s]+t[s+1];throw new Error('hex string expected, got non-hex character "'+e+'" at index '+s)}r[i]=16*e+n}return r}const vk=16;function bk(t){return t<<1^283&-(t>>7)}function wk(t,e){let n=0;for(;e>0;e>>=1)n^=t&-(1&e),t=bk(t);return n}const _k=(()=>{let t=new Uint8Array(256);for(let n=0,r=1;n<256;n++,r^=bk(r))t[n]=r;const e=new Uint8Array(256);e[0]=99;for(let n=0;n<255;n++){let r=t[255-n];r|=r<<8,e[t[n]]=255&(r^r>>4^r>>5^r>>6^r>>7^99)}return e})(),xk=_k.map((t,e)=>_k.indexOf(e)),Ck=t=>t<<24|t>>>8,Sk=t=>t<<8|t>>>24;function kk(t,e){if(256!==t.length)throw new Error("Wrong sbox length");const n=new Uint32Array(256).map((n,r)=>e(t[r])),r=n.map(Sk),i=r.map(Sk),s=i.map(Sk),o=new Uint32Array(65536),a=new Uint32Array(65536),l=new Uint16Array(65536);for(let c=0;c<256;c++)for(let e=0;e<256;e++){const u=256*c+e;o[u]=n[c]^r[e],a[u]=i[c]^s[e],l[u]=t[c]<<8|t[e]}return{sbox:t,sbox2:l,T0:n,T1:r,T2:i,T3:s,T01:o,T23:a}}const Ek=kk(_k,t=>wk(t,3)<<24|t<<16|t<<8|wk(t,2)),Tk=kk(xk,t=>wk(t,11)<<24|wk(t,13)<<16|wk(t,9)<<8|wk(t,14)),Rk=(()=>{const t=new Uint8Array(16);for(let e=0,n=1;e<16;e++,n=bk(n))t[e]=n;return t})();function Mk(t){dk(t);const e=t.length;if(![16,24,32].includes(e))throw new Error(`aes: wrong key size: should be 16, 24 or 32, got: ${e}`);const{sbox2:n}=Ek,r=pk(t),i=r.length,s=t=>Ik(n,t,t,t,t),o=new Uint32Array(e+28);o.set(r);for(let a=i;a<o.length;a++){let t=o[a-1];a%i===0?t=s(Ck(t))^Rk[a/i-1]:i>6&&a%i===4&&(t=s(t)),o[a]=o[a-i]^t}return o}function Lk(t,e,n,r,i,s){return t[n<<8&65280|r>>>8&255]^e[i>>>8&65280|s>>>24&255]}function Ik(t,e,n,r,i){return t[255&e|65280&n]|t[r>>>16&255|i>>>16&65280]<<16}function Pk(t,e,n,r,i){const{sbox2:s,T01:o,T23:a}=Ek;let l=0;e^=t[l++],n^=t[l++],r^=t[l++],i^=t[l++];const c=t.length/4-2;for(let u=0;u<c;u++){const s=t[l++]^Lk(o,a,e,n,r,i),c=t[l++]^Lk(o,a,n,r,i,e),u=t[l++]^Lk(o,a,r,i,e,n),h=t[l++]^Lk(o,a,i,e,n,r);e=s,n=c,r=u,i=h}return{s0:t[l++]^Ik(s,e,n,r,i),s1:t[l++]^Ik(s,n,r,i,e),s2:t[l++]^Ik(s,r,i,e,n),s3:t[l++]^Ik(s,i,e,n,r)}}function Ak(t,e,n,r,i){const{sbox2:s,T01:o,T23:a}=Tk;let l=0;e^=t[l++],n^=t[l++],r^=t[l++],i^=t[l++];const c=t.length/4-2;for(let u=0;u<c;u++){const s=t[l++]^Lk(o,a,e,i,r,n),c=t[l++]^Lk(o,a,n,e,i,r),u=t[l++]^Lk(o,a,r,n,e,i),h=t[l++]^Lk(o,a,i,r,n,e);e=s,n=c,r=u,i=h}return{s0:t[l++]^Ik(s,e,i,r,n),s1:t[l++]^Ik(s,n,e,i,r),s2:t[l++]^Ik(s,r,n,e,i),s3:t[l++]^Ik(s,i,r,n,e)}}function Ok(t,e){if(!e)return new Uint8Array(t);if(dk(e),e.length<t)throw new Error(`aes: wrong destination length, expected at least ${t}, got: ${e.length}`);return e}const Fk=(zk={blockSize:16,nonceLength:16},Dk=function(t,e,n={}){dk(t),dk(e,16);const r=!n.disablePadding;return{encrypt:(n,i)=>{const s=Mk(t),{b:o,o:a,out:l}=function(t,e,n){let r=t.length;const i=r%vk;if(!e&&0!==i)throw new Error("aec/(cbc-ecb): unpadded plaintext with disabled padding");const s=pk(t);if(e){let t=vk-i;t||(t=vk),r+=t}const o=Ok(r,n);return{b:s,o:pk(o),out:o}}(n,r,i),c=pk(e);let u=c[0],h=c[1],d=c[2],p=c[3],f=0;for(;f+4<=o.length;)u^=o[f+0],h^=o[f+1],d^=o[f+2],p^=o[f+3],({s0:u,s1:h,s2:d,s3:p}=Pk(s,u,h,d,p)),a[f++]=u,a[f++]=h,a[f++]=d,a[f++]=p;if(r){const t=function(t){const e=new Uint8Array(16),n=pk(e);e.set(t);const r=vk-t.length;for(let i=vk-r;i<vk;i++)e[i]=r;return n}(n.subarray(4*f));u^=t[0],h^=t[1],d^=t[2],p^=t[3],({s0:u,s1:h,s2:d,s3:p}=Pk(s,u,h,d,p)),a[f++]=u,a[f++]=h,a[f++]=d,a[f++]=p}return s.fill(0),l},decrypt:(n,i)=>{!function(t){if(dk(t),t.length%vk!==0)throw new Error("aes/(cbc-ecb).decrypt ciphertext should consist of blocks with size 16")}(n);const s=function(t){const e=Mk(t),n=e.slice(),r=e.length,{sbox2:i}=Ek,{T0:s,T1:o,T2:a,T3:l}=Tk;for(let c=0;c<r;c+=4)for(let t=0;t<4;t++)n[c+t]=e[r-c-4+t];e.fill(0);for(let c=4;c<r-4;c++){const t=n[c],e=Ik(i,t,t,t,t);n[c]=s[255&e]^o[e>>>8&255]^a[e>>>16&255]^l[e>>>24]}return n}(t),o=pk(e),a=Ok(n.length,i),l=pk(n),c=pk(a);let u=o[0],h=o[1],d=o[2],p=o[3];for(let t=0;t+4<=l.length;){const e=u,n=h,r=d,i=p;u=l[t+0],h=l[t+1],d=l[t+2],p=l[t+3];const{s0:o,s1:a,s2:f,s3:g}=Ak(s,u,h,d,p);c[t++]=o^e,c[t++]=a^n,c[t++]=f^r,c[t++]=g^i}return s.fill(0),function(t,e){if(!e)return t;const n=t.length;if(!n)throw new Error("aLine truncated
/*! noble-secp256k1 - MIT License (c) 2019 Paul Miller (paulmillr.com) */const IF=BigInt(0),PF=BigInt(1),AF=BigInt(2),OF=BigInt(3),FF=BigInt(8),zF=Object.freeze({a:IF,b:BigInt(7),P:BigInt("0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f"),n:BigInt("0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141"),h:PF,Gx:BigInt("55066263022277343669578718895168534326250603453777594175500187360389116729240"),Gy:BigInt("32670510020758816978083085130507043184471273380659243275938904335757337482424"),beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee")}),DF=(t,e)=>(t+e/AF)/e,NF={beta:BigInt("0x7ae96a2b657c07106e64479eac3434e99cf0497512f58995c1396c28719501ee"),splitScalar(t){const{n:e}=zF,n=BigInt("0x3086d221a7d46bcde86c90e49284eb15"),r=-PF*BigInt("0xe4437ed6010e88286f547fa90abfe4c3"),i=BigInt("0x114ca50f7a8e2f3f657c1108d9d44cfd8"),s=n,o=BigInt("0x100000000000000000000000000000000"),a=DF(s*t,e),l=DF(-r*t,e);let c=lz(t-a*n-l*i,e),u=lz(-a*r-l*s,e);const h=c>o,d=u>o;if(h&&(c=e-c),d&&(u=e-u),c>o||u>o)throw new Error("splitScalarEndo: Endomorphism failed, k="+t);return{k1neg:h,k1:c,k2neg:d,k2:u}}};function jF(t){const{a:e,b:n}=zF,r=lz(t*t),i=lz(r*t);return lz(i+e*t+n)}const UF=zF.a===IF;function BF(t){if(!(t instanceof $F))throw new TypeError("JacobianPoint expected")}class $F{constructor(t,e,n){this.x=t,this.y=e,this.z=n}static fromAffine(t){if(!(t instanceof WF))throw new TypeError("JacobianPoint#fromAffine: expected Point");return t.equals(WF.ZERO)?$F.ZERO:new $F(t.x,t.y,PF)}static toAffineBatch(t){const e=function(t,e=zF.P){const n=new Array(t.length),r=t.reduce((t,r,i)=>r===IF?t:(n[i]=t,lz(t*r,e)),PF),i=uz(r,e);return t.reduceRight((t,r,i)=>r===IF?t:(n[i]=lz(t*n[i],e),lz(t*r,e)),i),n}(t.map(t=>t.z));return t.map((t,n)=>t.toAffine(e[n]))}static normalizeZ(t){return $F.toAffineBatch(t).map($F.fromAffine)}equals(t){BF(t);const{x:e,y:n,z:r}=this,{x:i,y:s,z:o}=t,a=lz(r*r),l=lz(o*o),c=lz(e*l),u=lz(i*a),h=lz(lz(n*o)*l),d=lz(lz(s*r)*a);return c===u&&h===d}negate(){return new $F(this.x,lz(-this.y),this.z)}double(){const{x:t,y:e,z:n}=this,r=lz(t*t),i=lz(e*e),s=lz(i*i),o=t+i,a=lz(AF*(lz(o*o)-r-s)),l=lz(OF*r),c=lz(l*l),u=lz(c-AF*a),h=lz(l*(a-u)-FF*s),d=lz(AF*e*n);return new $F(u,h,d)}add(t){BF(t);const{x:e,y:n,z:r}=this,{x:i,y:s,z:o}=t;if(i===IF||s===IF)return this;if(e===IF||n===IF)return t;const a=lz(r*r),l=lz(o*o),c=lz(e*l),u=lz(i*a),h=lz(lz(n*o)*l),d=lz(lz(s*r)*a),p=lz(u-c),f=lz(d-h);if(p===IF)return f===IF?this.double():$F.ZERO;const g=lz(p*p),m=lz(p*g),y=lz(c*g),v=lz(f*f-m-AF*y),b=lz(f*(y-v)-h*m),w=lz(r*o*p);return new $F(v,b,w)}subtract(t){return this.add(t.negate())}multiplyUnsafe(t){const e=$F.ZERO;if("bigint"==typeof t&&t===IF)return e;let n=az(t);if(n===PF)return this;if(!UF){let t=e,r=this;for(;n>IF;)n&PF&&(t=t.add(r)),r=r.double(),n>>=PF;return t}let{k1neg:r,k1:i,k2neg:s,k2:o}=NF.splitScalar(n),a=e,l=e,c=this;for(;i>IF||o>IF;)i&PF&&(a=a.add(c)),o&PF&&(l=l.add(c)),c=c.double(),i>>=PF,o>>=PF;return r&&(a=a.negate()),s&&(l=l.negate()),l=new $F(lz(l.x*NF.beta),l.y,l.z),a.add(l)}precomputeWindow(t){const e=UF?128/t+1:256/t+1,n=[];let r=this,i=r;for(let s=0;s<e;s++){i=r,n.push(i);for(let e=1;e<2**(t-1);e++)i=i.add(r),n.push(i);r=i.double()}return n}wNAF(t,e){!e&&this.equals($F.BASE)&&(e=WF.BASE);const n=e&&e._WINDOW_SIZE||1;if(256%n)throw new Error("Point#wNAF: Invalid precomputation window, must be power of 2");let r=e&&VF.get(e);r||(r=this.precomputeWindow(n),e&&1!==n&&(r=$F.normalizeZ(r),VF.set(e,r)));let i=$F.ZERO,s=$F.BASE;const o=1+(UF?128/n:256/n),a=2**(n-1),l=BigInt(2**n-1),c=2**n,u=BigInt(n);for(let h=0;h<o;h++){const e=h*a;let n=Number(t&l);t>>=u,n>a&&(n-=c,t+=PF);const o=e,d=e+Math.abs(n)-1,p=h%2!=0,f=n<0;0===n?s=s.add(qF(p,r[o])):i=i.add(qF(f,r[d]))}return{p:i,f:s}}multiply(t,e){let n,r,i=az(t);if(UF){const{k1neg:t,k1:s,k2neg:o,k2:a}=NF.splitScalar(i);let{p:l,f:c}=this.wNAF(s,e),{p:u,f:h}=this.wNAF(a,e);l=qF(t,l),u=qF(o,u),u=new $F(lz(u.x*NF.beta),u.y,u.z),n=l.add(u),r=c.add(h)}else{const{p:t,f:s}=this.wNAF(i,e);n=t,r=s}return $F.normalizeZ([n,r])[0]}toAffine(t){const{x:e,y:n,z:r}=this,i=this.equals($F.ZERO);null==t&&(t=i?FF:uz(r));const s=t,o=lz(s*s),a=lz(o*s),l=lz(e*o),c=lz(n*a),u=lz(r*s);if(i)return WF.ZERO;if(u!==PF)throw new Error("invZ was invalid");return new WF(l,c)}}function qF(t,e){const n=e.negate();return t?n:e}$F.BASE=new $F(zF.Gx,zF.Gy,PF),$F.ZERO=new $F(IF,PF,IF);const VF=new WeakMap;let WF=class t{constructor(t,e){this.x=t,this.y=e}_setWindowSize(t){this._WINDOW_SIZE=t,VF.delete(this)}hasEvenY(){return this.y%AF===IF}static fromCompressedHex(e){const n=32===e.length,r=sz(n?e:e.subarray(1));if(!dz(r))throw new Error("Point is not on curve");let i=function(t){const{P:e}=zF,n=BigInt(6),r=BigInt(11),i=BigInt(22),s=BigInt(23),o=BigInt(44),a=BigInt(88),l=t*t*t%e,c=l*l*t%e,u=cz(c,OF)*c%e,h=cz(u,OF)*c%e,d=cz(h,AF)*l%e,p=cz(d,r)*d%e,f=cz(p,i)*p%e,g=cz(f,o)*f%e,m=cz(g,a)*g%e,y=cz(m,o)*f%e,v=cz(y,OF)*c%e,b=cz(v,s)*p%e,w=cz(b,n)*l%e,_=cz(w,AF);if(_*_Line truncated
/*! For license information please see remotestorage.js.LICENSE.txt */const WN=UA(VN),HN="0123456789ABCDEFGHJKMNPQRSTVWXYZ",GN=0xffffffffffff;var KN;!function(t){t.Base32IncorrectEncoding="B32_ENC_INVALID",t.DecodeTimeInvalidCharacter="DEC_TIME_CHAR",t.DecodeTimeValueMalformed="DEC_TIME_MALFORMED",t.EncodeTimeNegative="ENC_TIME_NEG",t.EncodeTimeSizeExceeded="ENC_TIME_SIZE_EXCEED",t.EncodeTimeValueMalformed="ENC_TIME_MALFORMED",t.PRNGDetectFailure="PRNG_DETECT",t.ULIDInvalid="ULID_INVALID",t.Unexpected="UNEXPECTED",t.UUIDInvalid="UUID_INVALID"}(KN||(KN={}));class ZN extends Error{constructor(t,e){super(`${e} (${t})`),this.name="ULIDError",this.code=t}}function YN(t){const e=Math.floor(32*t())%32;return HN.charAt(e)}const XN={type:"object",properties:{id:{type:"string"},title:{type:"string"},lat:{type:"number"},lon:{type:"number"},geohash:{type:"string"},zoom:{type:"number"},url:{type:"string"},osmId:{type:"string"},osmType:{type:"string"},osmTags:{type:"object",additionalProperties:{type:"string"}},description:{type:"string"},tags:{type:"array",items:{type:"string"},default:[]},createdAt:{type:"string",format:"date-time"},updatedAt:{type:"string",format:"date-time"}},required:["id","title","lat","lon","geohash","createdAt"]},JN={type:"object",properties:{id:{type:"string"},title:{type:"string"},color:{type:"string"},placeRefs:{type:"array",items:{type:"object",properties:{id:{type:"string"},geohash:{type:"string"}},required:["id","geohash"]},default:[]},createdAt:{type:"string",format:"date-time"},updatedAt:{type:"string",format:"date-time"}},required:["id","title","placeRefs","createdAt"]},QN={name:"places",builder:function(t){function e(t,e){return`${t.substring(0,2)}/${t.substring(2,4)}/${e}`}t.declareType("place",XN),t.declareType("list",JN);const n={async getAll(){const e=await t.getAll("_lists/");return e?Object.values(e):[]},async get(e){const n=`_lists/${e}`;return t.getObject(n)},async create(e,n,r){const i=`_lists/${e}`;let s=await t.getObject(i);const o=(new Date).toISOString();return s?(s.title=n,r&&(s.color=r),s.updatedAt=o):s={id:e,title:n,color:r,placeRefs:[],createdAt:o,updatedAt:o},await t.storeObject("list",i,s),s},async delete(e){await t.remove(`_lists/${e}`)},async addPlace(e,n,r){const i=`_lists/${e}`,s=await t.getObject(i);if(!s)throw new Error(`List not found: ${e}`);return-1===s.placeRefs.findIndex(t=>t.id===n)&&(s.placeRefs.push({id:n,geohash:r}),s.updatedAt=(new Date).toISOString(),await t.storeObject("list",i,s)),s},async removePlace(e,n){const r=`_lists/${e}`,i=await t.getObject(r);if(!i)throw new Error(`List not found: ${e}`);const s=i.placeRefs.findIndex(t=>t.id===n);return-1!==s&&(i.placeRefs.splice(s,1),i.updatedAt=(new Date).toISOString(),await t.storeObject("list",r,i)),i},async initDefaults(){const t=[{id:"to-go",title:"Want to go",color:"#2e9e4f"},{id:"to-do",title:"To do",color:"#2a7fff"}];for(const e of t)await this.get(e.id)||await this.create(e.id,e.title,e.color)}},r={lists:n,store:async function(n){const r=function(t){const e=(new Date).toISOString(),n=t.id||function(){const t=function(){const t="undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:null,e=t&&(t.crypto||t.msCrypto)||null;if("function"==typeof e?.getRandomValues)return()=>{const t=new Uint8Array(1);return e.getRandomValues(t),t[0]/256};if("function"==typeof e?.randomBytes)return()=>e.randomBytes(1).readUInt8()/256;throw new ZN(KN.PRNGDetectFailure,"Failed to find a reliable PRNG")}();return function(t,e=10){if(isNaN(t))throw new ZN(KN.EncodeTimeValueMalformed,`Time must be a number: ${t}`);if(t>GN)throw new ZN(KN.EncodeTimeSizeExceeded,`Cannot encode a time larger than ${GN}: ${t}`);if(t<0)throw new ZN(KN.EncodeTimeNegative,`Time must be positive: ${t}`);if(!1===Number.isInteger(t))throw new ZN(KN.EncodeTimeValueMalformed,`Time must be an integer: ${t}`);let n,r="";for(let i=e;i>0;i--)n=t%32,r=HN.charAt(n)+r,t=(t-n)/32;return r}(Date.now(),10)+function(t,e){let n="";for(;t>0;t--)n=YN(e)+n;return n}(16,t)}(),r="number"==typeof t.lat?t.lat:0,i="number"==typeof t.lon?t.lon:0,s=t.title||"Untitled Place",o=t.geohash||DD.encode(r,i,10),a={...t,id:n,lat:r,lon:i,geohash:o,title:s,tags:t.tags||[],createdAt:t.createdAt||e,updatedAt:t.id?e:void 0};return Object.keys(a).forEach(t=>{void 0===a[t]&&delete a[t]}),a}(n),i=e(r.geohash,r.id);return await t.storeObject("place",i,r),r},remove:async function(r,i){if(!r||!i)throw new Error("Both id and geohash are required to remove a place");const s=await n.getAll();await Promise.all(s.map(async e=>{const n=e.placeRefs.findIndex(t=>t.id===r);-1!==n&&(e.placeRefs.splice(n,1),e.updatedAt=(new Date).toISOString(),await t.storeObject("list",`_lists/${e.id}`,e))}));const o=e(i,r);return t.remove(o)},get:async function(n,r){if(!n||!r)throw new Error("Both id and geohash are required to get a place");const i=e(r,n);return t.getObject(i)},listByPreLine truncated
@@ -1 +1 @@
import{c as e,g as a,a as s,s as t,A as r,C as o,b as n,D as i,L as l,N as b,d as c,P as p,e as d,S as m,T as u,_ as g,f as y,h as _,i as O,j as S,k as f,l as C,m as P,n as h,o as v,p as j,q as E,r as M,t as D,u as N,v as V,w as A,x as w,y as T,z as I,B as x,E as R,F as k,G as F,H as L,I as z,J as G,K as U,M as B,O as W,Q as Y,R as H,U as J,V as K,W as X,X as q,Y as Q,Z,$,a0 as ee,a1 as ae,a2 as se,a3 as te,a4 as re,a5 as oe,a6 as ne,a7 as ie,a8 as le,a9 as be,aa as pe,ab as de,ac as me,ad as ue,ae as ge,af as ye,ag as _e,ah as Oe,ai as Se,aj as fe,ak as Ce,al as Pe,am as he,an as ve,ao as je,ap as Ee,aq as Me,ar as De,as as Ne,at as Ve,au as Ae,av as we,aw as Te,ax as Ie,ay as xe,az as Re,aA as ke,aB as Fe,aC as Le,aD as ze,aE as Ge,aF as Ue,aG as Be,aH as We,aI as Ye,aJ as He,aK as Je,aL as Ke,aM as Xe,aN as qe,aO as Qe,aP as Ze,aQ as $e,aR as ea,aS as aa,aT as sa,aU as ta,aV as ra,aW as oa,aX as na,aY as ia,aZ as la,a_ as ba,a$ as pa,b0 as da,b1 as ma,b2 as ua,b3 as ga,b4 as ya,b5 as _a,b6 as Oa,b7 as Sa,b8 as fa,b9 as Ca,ba as Pa,bb as ha,bc as va,bd as ja,be as Ea,bf as Ma,bg as Da,bh as Na,bi as Va,bj as Aa,bk as wa,bl as Ta,bm as Ia,bn as xa,bo as Ra,bp as ka,bq as Fa,br as La,bs as za,bt as Ga,bu as Ua,bv as Ba,bw as Wa,bx as Ya,by as Ha,bz as Ja,bA as Ka,bB as Xa,bC as qa,bD as Qa,bE as Za,bF as $a,bG as es,bH as as,bI as ss,bJ as ts,bK as rs,bL as os,bM as ns,bN as is,bO as ls,bP as bs,bQ as cs,bR as ps,bS as ds,bT as ms,bU as us}from"./main-CfJ9up1Y.js";export{bV as Application,bW as ApplicationNamespace,bX as Array,bY as ArrayProxy,bZ as Controller,b_ as Debug,b$ as EmberDestroyable,c0 as EmberObject,c1 as EnumerableMutable,cc as GlimmerComponent,cd as GlimmerManager,ce as GlimmerReference,cf as GlimmerRuntime,cg as GlimmerUtil,ch as GlimmerValidator,c2 as Instrumentation,c0 as Object,c3 as ObjectCore,c4 as ObjectEvented,c5 as ObjectObservable,c6 as ObjectPromiseProxyMixin,c7 as ObjectProxy,ca as Owner,c8 as Runloop,c9 as Service,cb as VERSION}from"./main-CfJ9up1Y.js";const gs=new WeakMap,ys=Object.freeze(Object.defineProperty({__proto__:null,ASYNC_OBSERVERS:r,ComputedDescriptor:o,ComputedProperty:n,DEBUG_INJECTION_FUNCTIONS:i,Libraries:l,NAMESPACES:b,NAMESPACES_BY_ID:c,PROPERTY_DID_CHANGE:p,PROXY_CONTENT:d,SYNC_OBSERVERS:m,TrackedDescriptor:u,_getPath:g,_getProp:y,_setProp:_,activateObserver:O,addArrayObserver:S,addListener:f,addNamespace:C,addObserver:P,alias:h,arrayContentDidChange:v,arrayContentWillChange:j,autoComputed:E,beginPropertyChanges:M,cached:(...s)=>{const[t,r,o]=s,n=new WeakMap,i=o.get;o.get=function(){return n.has(this)||n.set(this,e(i.bind(this))),a(n.get(this))}},changeProperties:D,computed:N,createCache:e,defineDecorator:V,defineProperty:A,defineValue:w,deprecateProperty:function(e,a,r){Object.defineProperty(e,a,{configurable:!0,enumerable:!1,set(e){t(this,r,e)},get(){return s(this,r)}})},descriptorForDecorator:T,descriptorForProperty:I,eachProxyArrayDidChange:function(e,a,s,t){let r=gs.get(e);void 0!==r&&r.arrayDidChange(e,a,s,t)},eachProxyArrayWillChange:function(e,a,s,t){let r=gs.get(e);void 0!==r&&r.arrayWillChange(e,a,s,t)},endPropertyChanges:x,expandProperties:R,findNamespace:k,findNamespaces:F,flushAsyncObservers:L,get:s,getCachedValueFor:z,getProperties:G,getValue:a,hasListeners:U,hasUnknownProperty:B,inject:W,isClassicDecorator:Y,isComputed:H,isConst:J,isElementDescriptor:K,isNamespaceSearchDisabled:X,libraries:q,makeComputedDecorator:Q,markObjectAsDirty:Z,nativeDescDecorator:$,notifyPropertyChange:ee,objectAt:ae,on:se,processAllNamespaces:te,processNamespace:re,removeArrayObserver:oe,removeListener:ne,removeNamespace:ie,removeObserver:le,replace:be,replaceInNativeArray:pe,revalidateObservers:de,sendEvent:me,set:t,setClassicDecorator:ue,setNamespaceSearchDisabled:ge,setProperties:ye,setUnprocessedMixins:_e,tagForObject:Oe,tagForProperty:Se,tracked:fe,trySet:Ce},Symbol.toStringTag,{value:"Module"})),_s=Object.freeze(Object.defineProperty({__proto__:null,Input:Pe,Textarea:he,capabilities:ve,default:je,getComponentTemplate:Ee,setComponentManager:Me,setComponentTemplate:De},Symbol.toStringTag,{value:"Module"})),Os=Object.freeze(Object.defineProperty({__proto__:null,EventTarget:Ne,Promise:Ve,all:Ae,allSettled:we,asap:Te,async:Ie,cast:xe,configure:Re,default:ke,defer:Fe,denodeify:Le,filter:ze,hash:Ge,hashSettled:Ue,map:Be,off:We,on:Ye,race:He,reject:Je,resolve:Ke,rethrow:Xe},Symbol.toStringTag,{value:"Module"})),Ss=Object.freeze(Object.defineProperty({__proto__:null,cacheFor:z,guidFor:qe},Symbol.toStringTag,{value:"Module"})),fs=Object.freeze(Object.defineProperty({__proto__:null,default:Qe},Symbol.toStringTag,{value:"Module"})),Cs=Object.freeze(Object.defineProperty({__proto__:null,ENV:Ze,context:$e,getENV:ea,getLookup:aa,global:sa,setLookup:ta},Symbol.toStringTag,{value:"Module"})),Ps=Object.freeze(Object.defineProperty({__proto__:null,Meta:ra,UNDEFINED:oa,counters:na,meta:ia,peekMeta:la,setMeta:ba},Symbol.toStringTag,{value:"Module"})),hs=Object.freeze(Object.definePropLine truncated import{c as e,g as a,a as s,s as t,A as r,C as o,b as n,D as i,L as l,N as b,d as c,P as p,e as d,S as m,T as u,_ as g,f as y,h as _,i as O,j as S,k as f,l as C,m as P,n as h,o as v,p as j,q as E,r as M,t as D,u as N,v as V,w as A,x as w,y as T,z as I,B as x,E as R,F as k,G as F,H as L,I as z,J as G,K as U,M as B,O as W,Q as Y,R as H,U as J,V as K,W as X,X as q,Y as Q,Z,$,a0 as ee,a1 as ae,a2 as se,a3 as te,a4 as re,a5 as oe,a6 as ne,a7 as ie,a8 as le,a9 as be,aa as pe,ab as de,ac as me,ad as ue,ae as ge,af as ye,ag as _e,ah as Oe,ai as Se,aj as fe,ak as Ce,al as Pe,am as he,an as ve,ao as je,ap as Ee,aq as Me,ar as De,as as Ne,at as Ve,au as Ae,av as we,aw as Te,ax as Ie,ay as xe,az as Re,aA as ke,aB as Fe,aC as Le,aD as ze,aE as Ge,aF as Ue,aG as Be,aH as We,aI as Ye,aJ as He,aK as Je,aL as Ke,aM as Xe,aN as qe,aO as Qe,aP as Ze,aQ as $e,aR as ea,aS as aa,aT as sa,aU as ta,aV as ra,aW as oa,aX as na,aY as ia,aZ as la,a_ as ba,a$ as pa,b0 as da,b1 as ma,b2 as ua,b3 as ga,b4 as ya,b5 as _a,b6 as Oa,b7 as Sa,b8 as fa,b9 as Ca,ba as Pa,bb as ha,bc as va,bd as ja,be as Ea,bf as Ma,bg as Da,bh as Na,bi as Va,bj as Aa,bk as wa,bl as Ta,bm as Ia,bn as xa,bo as Ra,bp as ka,bq as Fa,br as La,bs as za,bt as Ga,bu as Ua,bv as Ba,bw as Wa,bx as Ya,by as Ha,bz as Ja,bA as Ka,bB as Xa,bC as qa,bD as Qa,bE as Za,bF as $a,bG as es,bH as as,bI as ss,bJ as ts,bK as rs,bL as os,bM as ns,bN as is,bO as ls,bP as bs,bQ as cs,bR as ps,bS as ds,bT as ms,bU as us}from"./main-CLZV93ov.js";export{bV as Application,bW as ApplicationNamespace,bX as Array,bY as ArrayProxy,bZ as Controller,b_ as Debug,b$ as EmberDestroyable,c0 as EmberObject,c1 as EnumerableMutable,cc as GlimmerComponent,cd as GlimmerManager,ce as GlimmerReference,cf as GlimmerRuntime,cg as GlimmerUtil,ch as GlimmerValidator,c2 as Instrumentation,c0 as Object,c3 as ObjectCore,c4 as ObjectEvented,c5 as ObjectObservable,c6 as ObjectPromiseProxyMixin,c7 as ObjectProxy,ca as Owner,c8 as Runloop,c9 as Service,cb as VERSION}from"./main-CLZV93ov.js";const gs=new WeakMap,ys=Object.freeze(Object.defineProperty({__proto__:null,ASYNC_OBSERVERS:r,ComputedDescriptor:o,ComputedProperty:n,DEBUG_INJECTION_FUNCTIONS:i,Libraries:l,NAMESPACES:b,NAMESPACES_BY_ID:c,PROPERTY_DID_CHANGE:p,PROXY_CONTENT:d,SYNC_OBSERVERS:m,TrackedDescriptor:u,_getPath:g,_getProp:y,_setProp:_,activateObserver:O,addArrayObserver:S,addListener:f,addNamespace:C,addObserver:P,alias:h,arrayContentDidChange:v,arrayContentWillChange:j,autoComputed:E,beginPropertyChanges:M,cached:(...s)=>{const[t,r,o]=s,n=new WeakMap,i=o.get;o.get=function(){return n.has(this)||n.set(this,e(i.bind(this))),a(n.get(this))}},changeProperties:D,computed:N,createCache:e,defineDecorator:V,defineProperty:A,defineValue:w,deprecateProperty:function(e,a,r){Object.defineProperty(e,a,{configurable:!0,enumerable:!1,set(e){t(this,r,e)},get(){return s(this,r)}})},descriptorForDecorator:T,descriptorForProperty:I,eachProxyArrayDidChange:function(e,a,s,t){let r=gs.get(e);void 0!==r&&r.arrayDidChange(e,a,s,t)},eachProxyArrayWillChange:function(e,a,s,t){let r=gs.get(e);void 0!==r&&r.arrayWillChange(e,a,s,t)},endPropertyChanges:x,expandProperties:R,findNamespace:k,findNamespaces:F,flushAsyncObservers:L,get:s,getCachedValueFor:z,getProperties:G,getValue:a,hasListeners:U,hasUnknownProperty:B,inject:W,isClassicDecorator:Y,isComputed:H,isConst:J,isElementDescriptor:K,isNamespaceSearchDisabled:X,libraries:q,makeComputedDecorator:Q,markObjectAsDirty:Z,nativeDescDecorator:$,notifyPropertyChange:ee,objectAt:ae,on:se,processAllNamespaces:te,processNamespace:re,removeArrayObserver:oe,removeListener:ne,removeNamespace:ie,removeObserver:le,replace:be,replaceInNativeArray:pe,revalidateObservers:de,sendEvent:me,set:t,setClassicDecorator:ue,setNamespaceSearchDisabled:ge,setProperties:ye,setUnprocessedMixins:_e,tagForObject:Oe,tagForProperty:Se,tracked:fe,trySet:Ce},Symbol.toStringTag,{value:"Module"})),_s=Object.freeze(Object.defineProperty({__proto__:null,Input:Pe,Textarea:he,capabilities:ve,default:je,getComponentTemplate:Ee,setComponentManager:Me,setComponentTemplate:De},Symbol.toStringTag,{value:"Module"})),Os=Object.freeze(Object.defineProperty({__proto__:null,EventTarget:Ne,Promise:Ve,all:Ae,allSettled:we,asap:Te,async:Ie,cast:xe,configure:Re,default:ke,defer:Fe,denodeify:Le,filter:ze,hash:Ge,hashSettled:Ue,map:Be,off:We,on:Ye,race:He,reject:Je,resolve:Ke,rethrow:Xe},Symbol.toStringTag,{value:"Module"})),Ss=Object.freeze(Object.defineProperty({__proto__:null,cacheFor:z,guidFor:qe},Symbol.toStringTag,{value:"Module"})),fs=Object.freeze(Object.defineProperty({__proto__:null,default:Qe},Symbol.toStringTag,{value:"Module"})),Cs=Object.freeze(Object.defineProperty({__proto__:null,ENV:Ze,context:$e,getENV:ea,getLookup:aa,global:sa,setLookup:ta},Symbol.toStringTag,{value:"Module"})),Ps=Object.freeze(Object.defineProperty({__proto__:null,Meta:ra,UNDEFINED:oa,counters:na,meta:ia,peekMeta:la,setMeta:ba},Symbol.toStringTag,{value:"Module"})),hs=Object.freeze(Object.definePropLine truncated
@@ -1,2 +1,2 @@
import{ci as t,cj as e,ck as i,cl as r,cm as n,cn as s,co as o,cp as a}from"./main-CfJ9up1Y.js";var h=Array.isArray;function u(){for(var r,n=[],s=0;s<arguments.length;s++)n[s]=arguments[s];return 1===(n=1===(r=n).length&&h(r[0])?r[0]:r).length?t(n[0]):new e(function(e){return function(r){for(var n=[],s=function(s){n.push(t(e[s]).subscribe(i(r,function(t){if(n){for(var e=0;e<n.length;e++)e!==s&&n[e].unsubscribe();n=null}r.next(t)})))},o=0;n&&!r.closed&&o<e.length;o++)s(o)}} import{ci as t,cj as e,ck as i,cl as r,cm as n,cn as s,co as o,cp as a}from"./main-CLZV93ov.js";var h=Array.isArray;function u(){for(var r,n=[],s=0;s<arguments.length;s++)n[s]=arguments[s];return 1===(n=1===(r=n).length&&h(r[0])?r[0]:r).length?t(n[0]):new e(function(e){return function(r){for(var n=[],s=function(s){n.push(t(e[s]).subscribe(i(r,function(t){if(n){for(var e=0;e<n.length;e++)e!==s&&n[e].unsubscribe();n=null}r.next(t)})))},o=0;n&&!r.closed&&o<e.length;o++)s(o)}}
/*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */(n))}function l(t,...e){if(!((i=t)instanceof Uint8Array||ArrayBuffer.isView(i)&&"Uint8Array"===i.constructor.name))throw new Error("Uint8Array expected");var i;if(e.length>0&&!e.includes(t.length))throw new Error("Uint8Array expected of length "+e+", got length="+t.length)}function c(t,e=!0){if(t.destroyed)throw new Error("Hash instance has been destroyed");if(e&&t.finished)throw new Error("Hash#digest() has already been called")}function d(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function f(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function g(t,e){return t<<32-e|t>>>e}function p(t){return"string"==typeof t&&(t=function(t){if("string"!=typeof t)throw new Error("string expected");return new Uint8Array((new TextEncoder).encode(t))}(t)),l(t),t}class m{}function w(t){const e=e=>t().update(p(e)).digest(),i=t();return e.outputLen=i.outputLen,e.blockLen=i.blockLen,e.create=()=>t(),e}function b(t,e,i){return t&e^~t&i}function y(t,e,i){return t&e^t&i^e&i}class E extends m{constructor(t,e,i,r){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=t,this.outputLen=e,this.padOffset=i,this.isLE=r,this.buffer=new Uint8Array(t),this.view=f(this.buffer)}update(t){c(this),l(t=p(t));const{view:e,buffer:i,blockLen:r}=this,n=t.length;for(let s=0;s<n;){const o=Math.min(r-this.pos,n-s);if(o===r){const e=f(t);for(;r<=n-s;s+=r)this.process(e,s);continue}i.set(t.subarray(s,s+o),this.pos),this.pos+=o,s+=o,this.pos===r&&(this.process(e,0),this.pos=0)}return this.length+=t.length,this.roundClean(),this}digestInto(t){c(this),function(t,e){l(t);const i=e.outputLen;if(t.length<i)throw new Error("digestInto() expects output buffer of length at least "+i)}(t,this),this.finished=!0;const{buffer:e,view:i,blockLen:r,isLE:n}=this;let{pos:s}=this;e[s++]=128,d(this.buffer.subarray(s)),this.padOffset>r-s&&(this.process(i,0),s=0);for(let l=s;l<r;l++)e[l]=0;!function(t,e,i,r){if("function"==typeof t.setBigUint64)return t.setBigUint64(e,i,r);const n=BigInt(32),s=BigInt(4294967295),o=Number(i>>n&s),a=Number(i&s),h=r?4:0,u=r?0:4;t.setUint32(e+h,o,r),t.setUint32(e+u,a,r)}(i,r-8,BigInt(8*this.length),n),this.process(i,0);const o=f(t),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const h=a/4,u=this.get();if(h>u.length)throw new Error("_sha2: outputLen bigger than state");for(let l=0;l<h;l++)o.setUint32(4*l,u[l],n)}digest(){const{buffer:t,outputLen:e}=this;this.digestInto(t);const i=t.slice(0,e);return this.destroy(),i}_cloneInto(t){t||(t=new this.constructor),t.set(...this.get());const{blockLen:e,buffer:i,length:r,finished:n,destroyed:s,pos:o}=this;return t.destroyed=s,t.finished=n,t.length=r,t.pos=o,r%e&&t.buffer.set(i),t}clone(){return this._cloneInto()}}const _=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),x=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),A=new Uint32Array(64);class L extends E{constructor(t=32){super(64,t,8,!1),this.A=0|_[0],this.B=0|_[1],this.C=0|_[2],this.D=0|_[3],this.E=0|_[4],this.F=0|_[5],this.G=0|_[6],this.H=0|_[7]}get(){const{A:t,B:e,C:i,D:r,E:n,F:s,G:o,H:a}=this;return[t,e,i,r,n,s,o,a]}set(t,e,i,r,n,s,o,a){this.A=0|t,this.B=0|e,this.C=0|i,this.D=0|r,this.E=0|n,this.F=0|s,this.G=0|o,this.H=0|a}process(t,e){for(let l=0;l<16;l++,e+=4)A[l]=t.getUint32(e,!1);for(let l=16;l<64;l++){const t=A[l-15],e=A[l-2],i=g(t,7)^g(t,18)^t>>>3,r=g(e,17)^g(e,19)^e>>>10;A[l]=r+A[l-7]+i+A[l-16]|0}let{A:i,B:r,C:n,D:s,E:o,F:a,G:h,H:u}=this;for(let l=0;l<64;l++){const t=u+(g(o,6)^g(o,11)^g(o,25))+b(o,a,h)+x[l]+A[l]|0,e=(g(i,2)^g(i,13)^g(i,22))+y(i,r,n)|0;u=h,h=a,a=o,o=s+t|0,s=n,n=r,r=i,i=t+e|0}i=i+this.A|0,r=r+this.B|0,n=n+this.C|0,s=s+this.D|0,o=o+this.E|0,a=a+this.F|0,h=h+this.G|0,u=u+this.H|0,this.set(i,r,n,s,o,a,h,u)}roundClean(){d(A)}destroy(){this.set(0,0,0,0,0,0,0,0),d(this.buffer)}}const U=w(()=>new L),I=32;class B{_raw;length;constructor(t){this._raw=new Uint8Array(t||512),this.length=t?t.length:0}unwrap(){return this._raw.subarray(0,this.length)}get capacity(){return this._raw.byteLength}extend(t){if(t instanceof B&&(t=t.unwrap()),"number"!=typeof t.length)throw Error("bad length");const e=t.length+this.length;if(this.capacity<e){const t=this._raw,i=Math.max(2*this.capacity,e)Line truncated /*! noble-hashes - MIT License (c) 2022 Paul Miller (paulmillr.com) */(n))}function l(t,...e){if(!((i=t)instanceof Uint8Array||ArrayBuffer.isView(i)&&"Uint8Array"===i.constructor.name))throw new Error("Uint8Array expected");var i;if(e.length>0&&!e.includes(t.length))throw new Error("Uint8Array expected of length "+e+", got length="+t.length)}function c(t,e=!0){if(t.destroyed)throw new Error("Hash instance has been destroyed");if(e&&t.finished)throw new Error("Hash#digest() has already been called")}function d(...t){for(let e=0;e<t.length;e++)t[e].fill(0)}function f(t){return new DataView(t.buffer,t.byteOffset,t.byteLength)}function g(t,e){return t<<32-e|t>>>e}function p(t){return"string"==typeof t&&(t=function(t){if("string"!=typeof t)throw new Error("string expected");return new Uint8Array((new TextEncoder).encode(t))}(t)),l(t),t}class m{}function w(t){const e=e=>t().update(p(e)).digest(),i=t();return e.outputLen=i.outputLen,e.blockLen=i.blockLen,e.create=()=>t(),e}function b(t,e,i){return t&e^~t&i}function y(t,e,i){return t&e^t&i^e&i}class E extends m{constructor(t,e,i,r){super(),this.finished=!1,this.length=0,this.pos=0,this.destroyed=!1,this.blockLen=t,this.outputLen=e,this.padOffset=i,this.isLE=r,this.buffer=new Uint8Array(t),this.view=f(this.buffer)}update(t){c(this),l(t=p(t));const{view:e,buffer:i,blockLen:r}=this,n=t.length;for(let s=0;s<n;){const o=Math.min(r-this.pos,n-s);if(o===r){const e=f(t);for(;r<=n-s;s+=r)this.process(e,s);continue}i.set(t.subarray(s,s+o),this.pos),this.pos+=o,s+=o,this.pos===r&&(this.process(e,0),this.pos=0)}return this.length+=t.length,this.roundClean(),this}digestInto(t){c(this),function(t,e){l(t);const i=e.outputLen;if(t.length<i)throw new Error("digestInto() expects output buffer of length at least "+i)}(t,this),this.finished=!0;const{buffer:e,view:i,blockLen:r,isLE:n}=this;let{pos:s}=this;e[s++]=128,d(this.buffer.subarray(s)),this.padOffset>r-s&&(this.process(i,0),s=0);for(let l=s;l<r;l++)e[l]=0;!function(t,e,i,r){if("function"==typeof t.setBigUint64)return t.setBigUint64(e,i,r);const n=BigInt(32),s=BigInt(4294967295),o=Number(i>>n&s),a=Number(i&s),h=r?4:0,u=r?0:4;t.setUint32(e+h,o,r),t.setUint32(e+u,a,r)}(i,r-8,BigInt(8*this.length),n),this.process(i,0);const o=f(t),a=this.outputLen;if(a%4)throw new Error("_sha2: outputLen should be aligned to 32bit");const h=a/4,u=this.get();if(h>u.length)throw new Error("_sha2: outputLen bigger than state");for(let l=0;l<h;l++)o.setUint32(4*l,u[l],n)}digest(){const{buffer:t,outputLen:e}=this;this.digestInto(t);const i=t.slice(0,e);return this.destroy(),i}_cloneInto(t){t||(t=new this.constructor),t.set(...this.get());const{blockLen:e,buffer:i,length:r,finished:n,destroyed:s,pos:o}=this;return t.destroyed=s,t.finished=n,t.length=r,t.pos=o,r%e&&t.buffer.set(i),t}clone(){return this._cloneInto()}}const _=Uint32Array.from([1779033703,3144134277,1013904242,2773480762,1359893119,2600822924,528734635,1541459225]),x=Uint32Array.from([1116352408,1899447441,3049323471,3921009573,961987163,1508970993,2453635748,2870763221,3624381080,310598401,607225278,1426881987,1925078388,2162078206,2614888103,3248222580,3835390401,4022224774,264347078,604807628,770255983,1249150122,1555081692,1996064986,2554220882,2821834349,2952996808,3210313671,3336571891,3584528711,113926993,338241895,666307205,773529912,1294757372,1396182291,1695183700,1986661051,2177026350,2456956037,2730485921,2820302411,3259730800,3345764771,3516065817,3600352804,4094571909,275423344,430227734,506948616,659060556,883997877,958139571,1322822218,1537002063,1747873779,1955562222,2024104815,2227730452,2361852424,2428436474,2756734187,3204031479,3329325298]),A=new Uint32Array(64);class L extends E{constructor(t=32){super(64,t,8,!1),this.A=0|_[0],this.B=0|_[1],this.C=0|_[2],this.D=0|_[3],this.E=0|_[4],this.F=0|_[5],this.G=0|_[6],this.H=0|_[7]}get(){const{A:t,B:e,C:i,D:r,E:n,F:s,G:o,H:a}=this;return[t,e,i,r,n,s,o,a]}set(t,e,i,r,n,s,o,a){this.A=0|t,this.B=0|e,this.C=0|i,this.D=0|r,this.E=0|n,this.F=0|s,this.G=0|o,this.H=0|a}process(t,e){for(let l=0;l<16;l++,e+=4)A[l]=t.getUint32(e,!1);for(let l=16;l<64;l++){const t=A[l-15],e=A[l-2],i=g(t,7)^g(t,18)^t>>>3,r=g(e,17)^g(e,19)^e>>>10;A[l]=r+A[l-7]+i+A[l-16]|0}let{A:i,B:r,C:n,D:s,E:o,F:a,G:h,H:u}=this;for(let l=0;l<64;l++){const t=u+(g(o,6)^g(o,11)^g(o,25))+b(o,a,h)+x[l]+A[l]|0,e=(g(i,2)^g(i,13)^g(i,22))+y(i,r,n)|0;u=h,h=a,a=o,o=s+t|0,s=n,n=r,r=i,i=t+e|0}i=i+this.A|0,r=r+this.B|0,n=n+this.C|0,s=s+this.D|0,o=o+this.E|0,a=a+this.F|0,h=h+this.G|0,u=u+this.H|0,this.set(i,r,n,s,o,a,h,u)}roundClean(){d(A)}destroy(){this.set(0,0,0,0,0,0,0,0),d(this.buffer)}}const U=w(()=>new L),I=32;class B{_raw;length;constructor(t){this._raw=new Uint8Array(t||512),this.length=t?t.length:0}unwrap(){return this._raw.subarray(0,this.length)}get capacity(){return this._raw.byteLength}extend(t){if(t instanceof B&&(t=t.unwrap()),"number"!=typeof t.length)throw Error("bad length");const e=t.length+this.length;if(this.capacity<e){const t=this._raw,i=Math.max(2*this.capacity,e)Line truncated
+3 -2
View File
@@ -39,9 +39,10 @@
<meta name="msapplication-TileColor" content="#F6E9A6"> <meta name="msapplication-TileColor" content="#F6E9A6">
<meta name="msapplication-TileImage" content="/icons/icon-144.png"> <meta name="msapplication-TileImage" content="/icons/icon-144.png">
<script type="module" crossorigin src="/assets/main-CfJ9up1Y.js"></script> <script type="module" crossorigin src="/assets/main-CLZV93ov.js"></script>
<link rel="stylesheet" crossorigin href="/assets/main-CHuW_yI-.css"> <link rel="stylesheet" crossorigin href="/assets/main-COnSXoPt.css">
</head> </head>
<body> <body>
<div id="modal-portal"></div>
</body> </body>
</html> </html>
+7 -54
View File
@@ -2,7 +2,6 @@ import { module, test } from 'qunit';
import { visit, currentURL, waitFor, triggerEvent } from '@ember/test-helpers'; import { visit, currentURL, waitFor, triggerEvent } from '@ember/test-helpers';
import { setupApplicationTest } from 'marco/tests/helpers'; import { setupApplicationTest } from 'marco/tests/helpers';
import Service from '@ember/service'; import Service from '@ember/service';
import sinon from 'sinon';
module('Acceptance | map search reset', function (hooks) { module('Acceptance | map search reset', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
@@ -17,58 +16,10 @@ module('Acceptance | map search reset', function (hooks) {
'marco:map-view', 'marco:map-view',
JSON.stringify(highZoomState) JSON.stringify(highZoomState)
); );
// Stub window.fetch using Sinon
// We want to intercept map style requests and let everything else through
this.fetchStub = sinon.stub(window, 'fetch');
this.fetchStub.callsFake(async (input, init) => {
let url = input;
if (typeof input === 'object' && input !== null && 'url' in input) {
url = input.url;
}
if (
typeof url === 'string' &&
url.includes('tiles.openfreemap.org/styles/liberty')
) {
return {
ok: true,
status: 200,
json: async () => ({
version: 8,
name: 'Liberty',
sources: {
openmaptiles: {
type: 'vector',
url: 'https://tiles.openfreemap.org/planet',
},
},
layers: [
{
id: 'background',
type: 'background',
paint: {
'background-color': '#123456',
},
},
],
glyphs:
'https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf',
sprite: 'https://tiles.openfreemap.org/sprites/liberty',
}),
};
}
// Pass through to the original implementation
return this.fetchStub.wrappedMethod(input, init);
});
}); });
hooks.afterEach(function () { hooks.afterEach(function () {
window.localStorage.removeItem('marco:map-view'); window.localStorage.removeItem('marco:map-view');
// Restore the original fetch
this.fetchStub.restore();
}); });
test('clicking the map clears the category search parameter', async function (assert) { test('clicking the map clears the category search parameter', async function (assert) {
@@ -140,12 +91,14 @@ module('Acceptance | map search reset', function (hooks) {
bubbles: true, bubbles: true,
}); });
// Wait for transition to index // Wait for transition or UI update
await new Promise((r) => setTimeout(r, 500)); await new Promise((r) => setTimeout(r, 500));
assert.strictEqual(
currentURL(), // Sidebar should be hidden, but we should stay on the search route
'/', assert.dom('.sidebar').doesNotExist('Sidebar should be closed');
'Should have transitioned to index (closed sidebar)' assert.ok(
currentURL().includes('category=coffee'),
'Should have stayed on the search route with markers intact'
); );
// Second Click (Start new search) // Second Click (Start new search)
+3 -2
View File
@@ -38,6 +38,7 @@ class MockOsmService extends Service {
} }
class MockStorageService extends Service { class MockStorageService extends Service {
initialSyncDone = true;
savedPlaces = []; savedPlaces = [];
findPlaceById() { findPlaceById() {
return null; return null;
@@ -95,8 +96,8 @@ module('Acceptance | navigation', function (hooks) {
// Click the Close (X) button // Click the Close (X) button
await click('.close-btn'); await click('.close-btn');
assert.strictEqual(currentURL(), '/', 'Returned to index'); assert.dom('.sidebar').doesNotExist('Sidebar should be closed');
assert.false(mapUi.returnToSearch, 'Flag is reset after closing sidebar'); assert.ok(currentURL().includes('/place/'), 'Remains on place route');
}); });
test('navigating directly to place and back closes sidebar', async function (assert) { test('navigating directly to place and back closes sidebar', async function (assert) {
+42 -6
View File
@@ -1,17 +1,32 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { visit, click, fillIn, currentURL } from '@ember/test-helpers'; import { visit, click, fillIn, currentURL, settled } from '@ember/test-helpers';
import { setupApplicationTest } from 'marco/tests/helpers'; import { setupApplicationTest } from 'marco/tests/helpers';
import Service from '@ember/service'; import Service from '@ember/service';
import { Promise } from 'rsvp'; import { Promise } from 'rsvp';
let photonResolve;
let osmResolve;
class MockPhotonService extends Service { class MockPhotonService extends Service {
cancelAll() {} cancelAll() {}
async search(query) { async search(query) {
// Simulate network delay
await new Promise((resolve) => setTimeout(resolve, 50));
if (query === 'slow') { if (query === 'slow') {
await new Promise((resolve) => setTimeout(resolve, 200)); // Return a promise that we can manually resolve in the test
// to avoid race conditions with native setTimeout
return new Promise((resolve) => {
photonResolve = () => {
resolve([
{
title: 'Test Place',
lat: 1,
lon: 1,
osmId: '123',
osmType: 'node',
},
]);
};
});
} }
return [ return [
{ {
@@ -29,9 +44,12 @@ class MockOsmService extends Service {
cancelAll() {} cancelAll() {}
async getCategoryPois(bounds, category) { async getCategoryPois(bounds, category) {
await new Promise((resolve) => setTimeout(resolve, 50));
if (category === 'slow_category') { if (category === 'slow_category') {
await new Promise((resolve) => setTimeout(resolve, 200)); return new Promise((resolve) => {
osmResolve = () => {
resolve([]);
};
});
} }
return []; return [];
} }
@@ -44,6 +62,8 @@ module('Acceptance | search loading', function (hooks) {
setupApplicationTest(hooks); setupApplicationTest(hooks);
hooks.beforeEach(function () { hooks.beforeEach(function () {
photonResolve = null;
osmResolve = null;
this.owner.register('service:photon', MockPhotonService); this.owner.register('service:photon', MockPhotonService);
this.owner.register('service:osm', MockOsmService); this.owner.register('service:osm', MockOsmService);
}); });
@@ -66,7 +86,12 @@ module('Acceptance | search loading', function (hooks) {
'Loading state is set for text search' 'Loading state is set for text search'
); );
// Resolve the manual promise so the task can finish deterministically
photonResolve();
await searchPromise; await searchPromise;
await settled(); // Wait for ember-concurrency tasks to fully settle
assert.strictEqual( assert.strictEqual(
mapUi.loadingState, mapUi.loadingState,
null, null,
@@ -83,7 +108,12 @@ module('Acceptance | search loading', function (hooks) {
'Loading state is set for category search' 'Loading state is set for category search'
); );
// Resolve the manual promise
osmResolve();
await catPromise; await catPromise;
await settled();
assert.strictEqual( assert.strictEqual(
mapUi.loadingState, mapUi.loadingState,
null, null,
@@ -122,6 +152,7 @@ module('Acceptance | search loading', function (hooks) {
// 4. Click the clear button (should be visible since input has value) // 4. Click the clear button (should be visible since input has value)
await click('.search-clear-btn'); await click('.search-clear-btn');
// Wait for the click and transition to settle
// Verify loading state is cleared immediately // Verify loading state is cleared immediately
assert.strictEqual( assert.strictEqual(
@@ -130,6 +161,11 @@ module('Acceptance | search loading', function (hooks) {
'Loading state is cleared immediately after clicking clear' 'Loading state is cleared immediately after clicking clear'
); );
// Clean up the dangling promise
if (photonResolve) {
photonResolve();
}
// Verify we are back on index (or at least query is gone) // Verify we are back on index (or at least query is gone)
assert.strictEqual(currentURL(), '/', 'Navigated to index'); assert.strictEqual(currentURL(), '/', 'Navigated to index');
}); });
+6 -1
View File
@@ -16,7 +16,12 @@
</div> </div>
</div> </div>
<script src="/testem.js" integrity="" data-embroider-ignore></script> <script
src="/testem.js"
integrity=""
data-embroider-ignore
vite-ignore
></script>
<script type="module">import "ember-testing";</script> <script type="module">import "ember-testing";</script>
@@ -1,20 +1,20 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'marco/tests/helpers'; import { setupRenderingTest } from 'marco/tests/helpers';
import { render, click } from '@ember/test-helpers'; import { render, click } from '@ember/test-helpers';
import PlacePhotosCarousel from 'marco/components/place-photos-carousel'; import PhotoCarousel from 'marco/components/photo-carousel';
module('Integration | Component | place-photos-carousel', function (hooks) { module('Integration | Component | photo-carousel', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
test('it renders gracefully with no photos', async function (assert) { test('it renders gracefully with no photos', async function (assert) {
this.photos = []; this.photos = [];
await render( await render(
<template><PlacePhotosCarousel @photos={{this.photos}} /></template> <template><PhotoCarousel @photos={{this.photos}} /></template>
); );
assert assert
.dom('.place-photos-carousel-wrapper') .dom('.photo-carousel')
.doesNotExist('it does not render the wrapper when there are no photos'); .doesNotExist('it does not render the wrapper when there are no photos');
}); });
@@ -32,14 +32,12 @@ module('Integration | Component | place-photos-carousel', function (hooks) {
await render( await render(
<template> <template>
<div class="test-container"> <div class="test-container">
<PlacePhotosCarousel @photos={{this.photos}} /> <PhotoCarousel @photos={{this.photos}} />
</div> </div>
</template> </template>
); );
assert assert.dom('.photo-carousel').exists('it renders the wrapper');
.dom('.place-photos-carousel-wrapper')
.exists('it renders the wrapper');
assert assert
.dom('.carousel-slide:not(.carousel-placeholder)') .dom('.carousel-slide:not(.carousel-placeholder)')
.exists({ count: 1 }, 'it renders one real photo slide'); .exists({ count: 1 }, 'it renders one real photo slide');
@@ -84,7 +82,7 @@ module('Integration | Component | place-photos-carousel', function (hooks) {
await render( await render(
<template> <template>
<div class="test-container"> <div class="test-container">
<PlacePhotosCarousel @photos={{this.photos}} /> <PhotoCarousel @photos={{this.photos}} />
</div> </div>
</template> </template>
); );
@@ -0,0 +1,339 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'marco/tests/helpers';
import { render, click, triggerKeyEvent } from '@ember/test-helpers';
import Service from '@ember/service';
import PhotoGallery from 'marco/components/photo-gallery';
import { setupNostrMocks } from 'marco/tests/helpers/mock-nostr';
import sinon from 'sinon';
class MockBlossomService extends Service {
async delete() {
return true;
}
}
class MockToastService extends Service {
show() {}
}
module('Integration | Component | photo-gallery', function (hooks) {
setupRenderingTest(hooks);
setupNostrMocks(hooks);
hooks.beforeEach(function () {
this.owner.register('service:blossom', MockBlossomService);
this.owner.register('service:toast', MockToastService);
this.blossom = this.owner.lookup('service:blossom');
this.nostrAuth = this.owner.lookup('service:nostrAuth');
this.nostrData = this.owner.lookup('service:nostrData');
this.nostrRelay = this.owner.lookup('service:nostrRelay');
this.toast = this.owner.lookup('service:toast');
this.settings = this.owner.lookup('service:settings');
this.photos = [
{
eventId: 'event1',
pubkey: 'userA',
placeIdentifier: 'osm:node:12345',
url: 'https://example.com/a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1.jpg',
thumbUrl:
'https://example.com/b3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1.jpg',
},
{
eventId: 'event2',
pubkey: 'userB',
placeIdentifier: 'osm:node:12345',
url: 'photo2.jpg',
},
];
});
hooks.afterEach(function () {
sinon.restore();
localStorage.removeItem('marco:settings');
});
test('it does not show delete button if user is not creator', async function (assert) {
this.nostrAuth.pubkey = 'userB'; // Different from photo1's pubkey
this.selectedPhoto = this.photos[0];
await render(
<template>
<div id="test-container">
<div id="modal-portal"></div>
<PhotoGallery
@photos={{this.photos}}
@selectedPhoto={{this.selectedPhoto}}
/>
</div>
</template>
);
// Open dropdown
await click('.dropdown-trigger-btn');
assert.dom('.dropdown-popover').exists('Dropdown opened');
assert
.dom('.dropdown-item.text-danger')
.doesNotExist('Delete button is hidden for non-creator');
});
test('it shows delete button if user is creator and setting is enabled', async function (assert) {
this.nostrAuth.pubkey = 'userA'; // Matches photo1's pubkey
this.settings.update('experimentalEnablePhotoDeletion', true); // Enable the setting
this.selectedPhoto = this.photos[0];
await render(
<template>
<div id="test-container">
<div id="modal-portal"></div>
<PhotoGallery
@photos={{this.photos}}
@selectedPhoto={{this.selectedPhoto}}
/>
</div>
</template>
);
// Open dropdown
await click('.dropdown-trigger-btn');
assert.dom('.dropdown-popover').exists('Dropdown opened');
assert
.dom('.dropdown-item.text-danger')
.exists('Delete button is visible for creator when setting is enabled');
});
test('it handles cancellation of deletion', async function (assert) {
this.nostrAuth.pubkey = 'userA';
this.settings.update('experimentalEnablePhotoDeletion', true);
this.selectedPhoto = this.photos[0];
const confirmStub = sinon.stub(window, 'confirm').returns(false);
const blossomSpy = sinon.spy(this.blossom, 'delete');
await render(
<template>
<div id="test-container">
<div id="modal-portal"></div>
<PhotoGallery
@photos={{this.photos}}
@selectedPhoto={{this.selectedPhoto}}
/>
</div>
</template>
);
await click('.dropdown-trigger-btn');
await click('.dropdown-item.text-danger');
assert.ok(confirmStub.calledOnce, 'confirmation dialog was shown');
assert.ok(blossomSpy.notCalled, 'blossom.delete was NOT called');
});
test('it performs full deletion flow when confirmed', async function (assert) {
this.nostrAuth.pubkey = 'userA';
this.settings.update('experimentalEnablePhotoDeletion', true);
// Override the mock's getter just for this test
Object.defineProperty(this.nostrAuth, 'signer', {
configurable: true,
get: () => ({
signEvent: async (e) => ({
...e,
id: 'signed-id',
sig: 'sig',
pubkey: 'userA',
}),
getPublicKey: async () => 'userA',
}),
});
this.selectedPhoto = this.photos[0];
let closed = false;
this.handleClose = () => {
closed = true;
};
const confirmStub = sinon.stub(window, 'confirm').returns(true);
const blossomStub = sinon.stub(this.blossom, 'delete').resolves();
const publishStub = sinon.stub(this.nostrRelay, 'publish').resolves();
const storeStub = sinon.stub(this.nostrData.store, 'add');
const toastSpy = sinon.spy(this.toast, 'show');
await render(
<template>
<div id="test-container">
<div id="modal-portal"></div>
<PhotoGallery
@photos={{this.photos}}
@selectedPhoto={{this.selectedPhoto}}
@onClose={{this.handleClose}}
/>
</div>
</template>
);
await click('.dropdown-trigger-btn');
await click('.dropdown-item.text-danger');
assert.ok(confirmStub.calledOnce, 'confirmation dialog was shown');
// Check blossom deletions
assert.ok(
blossomStub.calledTwice,
'blossom.delete was called twice (main + thumb)'
);
assert.strictEqual(
blossomStub.firstCall.args[0],
'a3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
'extracted correct hash for main image'
);
assert.strictEqual(
blossomStub.secondCall.args[0],
'b3b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1',
'extracted correct hash for thumb image'
);
// Check Nostr kind 5
assert.ok(publishStub.calledOnce, 'nostrRelay.publish was called');
const publishedEvent = publishStub.firstCall.args[1];
assert.strictEqual(publishedEvent.kind, 5, 'published event is kind 5');
assert.deepEqual(
publishedEvent.tags[0],
['e', 'event1'],
'event tags reference the deleted photo'
);
assert.deepEqual(
publishedEvent.tags[1],
['i', 'osm:node:12345'],
'event tags include the place identifier'
);
// Check store update
assert.ok(storeStub.calledOnce, 'nostrData.store.add was called');
assert.strictEqual(
storeStub.firstCall.args[0].kind,
5,
'added kind 5 event to local store'
);
// Check UX
assert.ok(
toastSpy.calledWith('Photo deleted successfully'),
'success toast was shown'
);
assert.ok(closed, 'gallery was closed after deletion');
});
test('it copies event id to clipboard', async function (assert) {
this.nostrAuth.pubkey = 'userA';
this.selectedPhoto = this.photos[0];
const clipboardStub = sinon
.stub(navigator.clipboard, 'writeText')
.resolves();
const toastSpy = sinon.spy(this.toast, 'show');
await render(
<template>
<div id="test-container">
<div id="modal-portal"></div>
<PhotoGallery
@photos={{this.photos}}
@selectedPhoto={{this.selectedPhoto}}
/>
</div>
</template>
);
await click('.dropdown-trigger-btn');
// Find the copy button (it should be the first one)
const items = document.querySelectorAll('.dropdown-item');
let copyBtn;
items.forEach((item) => {
if (item.textContent.includes('Copy Photo Event ID')) {
copyBtn = item;
}
});
await click(copyBtn);
assert.ok(clipboardStub.calledWith('event1'), 'copied correct event id');
assert.ok(
toastSpy.calledWith('Event ID copied to clipboard'),
'success toast was shown'
);
});
test('keyboard navigation changes photos', async function (assert) {
this.photos = [
{ eventId: 'event1', url: 'photo1.jpg' },
{ eventId: 'event2', url: 'photo2.jpg' },
{ eventId: 'event3', url: 'photo3.jpg' },
];
this.selectedPhoto = this.photos[0];
await render(
<template>
<div id="test-container">
<div id="modal-portal"></div>
<PhotoGallery
@photos={{this.photos}}
@selectedPhoto={{this.selectedPhoto}}
/>
</div>
</template>
);
assert
.dom('.photo-gallery-content')
.hasAttribute('data-current-event-id', 'event1');
// Right Arrow
await triggerKeyEvent(document, 'keydown', 'ArrowRight');
assert
.dom('.photo-gallery-content')
.hasAttribute('data-current-event-id', 'event2');
// Right Arrow again
await triggerKeyEvent(document, 'keydown', 'ArrowRight');
assert
.dom('.photo-gallery-content')
.hasAttribute('data-current-event-id', 'event3');
// Left Arrow
await triggerKeyEvent(document, 'keydown', 'ArrowLeft');
assert
.dom('.photo-gallery-content')
.hasAttribute('data-current-event-id', 'event2');
});
test('escape key closes gallery', async function (assert) {
this.selectedPhoto = this.photos[0];
let closed = false;
this.handleClose = () => {
closed = true;
};
await render(
<template>
<div id="test-container">
<div id="modal-portal"></div>
<PhotoGallery
@photos={{this.photos}}
@selectedPhoto={{this.selectedPhoto}}
@onClose={{this.handleClose}}
/>
</div>
</template>
);
await triggerKeyEvent(document, 'keydown', 'Escape');
assert.ok(closed, 'gallery was closed on escape key');
});
});
@@ -0,0 +1,100 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'marco/tests/helpers';
import { render, click, triggerEvent } from '@ember/test-helpers';
import Service from '@ember/service';
import PlacePhotoUpload from 'marco/components/place-photo-upload';
module('Integration | Component | place-photo-upload', function (hooks) {
setupRenderingTest(hooks);
class MockNostrAuthService extends Service {
get isConnected() {
return true;
}
get signer() {
return null;
}
}
hooks.beforeEach(function () {
this.owner.register('service:nostrAuth', MockNostrAuthService);
});
async function selectFile(element, file) {
const input = element.querySelector('#photo-upload-input');
Object.defineProperty(input, 'files', {
value: [file],
configurable: true,
});
await triggerEvent(input, 'change');
}
test('it shows tag suggestions when they exist after upload selection', async function (assert) {
this.place = {
title: 'Cafe Alpha',
osmId: '123',
osmType: 'node',
osmTags: { amenity: 'cafe' },
};
await render(
<template><PlacePhotoUpload @place={{this.place}} /></template>
);
assert.dom('.photo-tag-suggestions').doesNotExist();
const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
await selectFile(this.element, file);
assert.dom('.photo-tag-suggestions').exists();
assert.dom('.photo-tag-chip').exists();
assert.dom('.photo-tag-chip').includesText('Food');
});
test('it only allows one selected tag at a time', async function (assert) {
this.place = {
title: 'Cafe Alpha',
osmId: '123',
osmType: 'node',
osmTags: { amenity: 'cafe' },
};
await render(
<template><PlacePhotoUpload @place={{this.place}} /></template>
);
const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
await selectFile(this.element, file);
const chips = this.element.querySelectorAll('.photo-tag-chip');
assert.ok(chips.length > 1, 'multiple tag chips are rendered');
await click(chips[0]);
assert.dom('.photo-tag-chip.is-selected').exists({ count: 1 });
assert.dom(chips[0]).hasClass('is-selected');
await click(chips[1]);
assert.dom('.photo-tag-chip.is-selected').exists({ count: 1 });
assert.dom(chips[1]).hasClass('is-selected');
assert.dom(chips[0]).doesNotHaveClass('is-selected');
});
test('it hides tag suggestions when no tags are suggested', async function (assert) {
this.place = {
title: 'Office Beta',
osmId: '456',
osmType: 'node',
osmTags: { office: 'lawyer' },
};
await render(
<template><PlacePhotoUpload @place={{this.place}} /></template>
);
const file = new File(['test'], 'photo.jpg', { type: 'image/jpeg' });
await selectFile(this.element, file);
assert.dom('.photo-tag-suggestions').doesNotExist();
});
});
@@ -1,6 +1,6 @@
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'marco/tests/helpers'; import { setupRenderingTest } from 'marco/tests/helpers';
import { render, fillIn, click, waitFor } from '@ember/test-helpers'; import { render, fillIn, click, waitFor, focus } from '@ember/test-helpers';
import SearchBox from 'marco/components/search-box'; import SearchBox from 'marco/components/search-box';
import Service from '@ember/service'; import Service from '@ember/service';
@@ -208,22 +208,301 @@ module('Integration | Component | search-box', function (hooks) {
); );
// Type "Resta" to trigger "Restaurants" category match // Type "Resta" to trigger "Restaurants" category match
await focus('.search-input');
await fillIn('.search-input', 'Resta'); await fillIn('.search-input', 'Resta');
// Wait for debounce (300ms) + execution await waitFor('.search-result-item');
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
await delay(400);
// The first result should be the category match const resultItems = Array.from(
assert.dom('.search-result-item').exists({ count: 1 }); this.element.querySelectorAll('.search-result-item')
assert.dom('.result-title').hasText('Restaurants'); );
const categoryResult = resultItems.find((item) =>
item.textContent.includes('Restaurants')
);
assert.ok(categoryResult, 'Restaurants category result is shown');
// Click the result // Click the result
await click('.search-result-item'); await click(categoryResult);
// Assert transition with lat/lon from map center // Assert transition with lat/lon from map center
assert.verifySteps([ assert.verifySteps([
'transitionTo: search {"queryParams":{"q":"Restaurants","category":"restaurants","selected":null,"lat":"51.5074","lon":"-0.1278"}}', 'transitionTo: search {"queryParams":{"q":"Restaurants","category":"restaurants","selected":null,"lat":"51.5074","lon":"-0.1278"}}',
]); ]);
}); });
test('it includes, deduplicates, and prioritizes saved places in search results', async function (assert) {
// Mock MapUi Service
class MockMapUiService extends Service {
currentCenter = { lat: 52.52, lon: 13.405 };
setSearchBoxFocus() {}
}
this.owner.register('service:map-ui', MockMapUiService);
// Mock Router Service
class MockRouterService extends Service {
transitionTo(routeName, id) {
assert.step(`transitionTo: ${routeName} ["${id}"]`);
}
}
this.owner.register('service:router', MockRouterService);
// Mock Storage Service
class MockStorageService extends Service {
lists = [{ id: 'favs', title: 'Favorites' }];
savedPlaces = [
{
title: 'Awesome Coffee',
lat: 52.5,
lon: 13.4,
osmId: '999',
osmType: 'node',
_listIds: ['favs'],
},
];
}
this.owner.register('service:storage', MockStorageService);
// Mock Photon Service
class MockPhotonService extends Service {
async search(query) {
if (query === 'coffee') {
return [
{
title: 'Awesome Coffee',
osmId: '999',
osmType: 'node',
description: 'Duplicate to be removed',
},
{
title: 'Other Coffee',
osmId: '888',
osmType: 'node',
description: 'A different coffee shop',
},
];
}
return [];
}
}
this.owner.register('service:photon', MockPhotonService);
this.noop = () => {};
await render(
<template><SearchBox @onToggleMenu={{this.noop}} /></template>
);
// Type "coffee" to trigger matches in Category, Saved, and Photon
await fillIn('.search-input', 'coffee');
await waitFor('.search-results-popover', { timeout: 2000 });
const resultItems = Array.from(
this.element.querySelectorAll('.search-result-item')
);
// Should be exactly 3 items:
// 1. Category (Coffee)
// 2. Saved (Awesome Coffee)
// 3. Photon (Other Coffee)
// (The Photon duplicate of "Awesome Coffee" is removed)
assert.strictEqual(resultItems.length, 3, 'Renders exactly 3 items');
// 1. Category
assert.ok(
resultItems[0].textContent.includes('Coffee'),
'First item is the category match'
);
assert
.dom(resultItems[0].querySelector('.result-icon svg'))
.hasClass('feather-search', 'Category uses search icon');
// 2. Saved Place
assert.ok(
resultItems[1].textContent.includes('Awesome Coffee'),
'Second item is the saved place match'
);
assert.ok(
resultItems[1].textContent.includes('Saved place'),
'Saved place has correct description text'
);
assert
.dom(resultItems[1].querySelector('.result-icon svg'))
.hasClass('feather-bookmark', 'Saved place uses bookmark icon');
// 3. Photon Match
assert.ok(
resultItems[2].textContent.includes('Other Coffee'),
'Third item is the unique photon result'
);
// Click the Saved Place
await click(resultItems[1]);
assert.verifySteps(['transitionTo: place ["osm:node:999"]']);
});
test('it requires 3 or more characters to match saved places', async function (assert) {
// Mock MapUi Service
class MockMapUiService extends Service {
currentCenter = { lat: 52.52, lon: 13.405 };
setSearchBoxFocus() {}
}
this.owner.register('service:map-ui', MockMapUiService);
// Mock Router Service
class MockRouterService extends Service {
transitionTo() {}
}
this.owner.register('service:router', MockRouterService);
// Mock Storage Service
class MockStorageService extends Service {
lists = [{ id: 'favs', title: 'Favorites' }];
savedPlaces = [
{
title: 'Awesome Coffee',
lat: 52.5,
lon: 13.4,
osmId: '999',
osmType: 'node',
_listIds: ['favs'],
},
];
}
this.owner.register('service:storage', MockStorageService);
// Mock Photon Service
class MockPhotonService extends Service {
async search(query) {
if (query === 'aw' || query === 'awe') {
return [
{
title: 'Aww Some Place',
osmId: '111',
osmType: 'node',
},
];
}
return [];
}
}
this.owner.register('service:photon', MockPhotonService);
this.noop = () => {};
await render(
<template><SearchBox @onToggleMenu={{this.noop}} /></template>
);
// Type "aw" (2 characters)
await fillIn('.search-input', 'aw');
await waitFor('.search-results-popover', { timeout: 2000 });
let resultItems = Array.from(
this.element.querySelectorAll('.search-result-item')
);
// Should only show Photon match since 'aw' is < 3 characters
assert.strictEqual(
resultItems.length,
1,
'Renders exactly 1 item for 2 chars'
);
assert.ok(
resultItems[0].textContent.includes('Aww Some Place'),
'Shows photon match'
);
assert.notOk(
resultItems.some((item) => item.textContent.includes('Awesome Coffee')),
'Saved place is NOT shown for 2 char query'
);
// Type "awe" (3 characters)
await fillIn('.search-input', 'awe');
await waitFor('.search-results-popover', { timeout: 2000 });
resultItems = Array.from(
this.element.querySelectorAll('.search-result-item')
);
// Should now show Saved Place and Photon match
assert.strictEqual(
resultItems.length,
2,
'Renders exactly 2 items for 3 chars'
);
assert.ok(
resultItems.some((item) => item.textContent.includes('Awesome Coffee')),
'Saved place is now shown'
);
assert.ok(
resultItems.some((item) =>
item.textContent.includes('Saved place (Favorites)')
),
'List names are appended to the description'
);
});
test('it navigates to internal ID for custom saved places without an OSM ID', async function (assert) {
// Mock MapUi Service
class MockMapUiService extends Service {
currentCenter = { lat: 52.52, lon: 13.405 };
setSearchBoxFocus() {}
}
this.owner.register('service:map-ui', MockMapUiService);
// Mock Router Service
class MockRouterService extends Service {
transitionTo(routeName, id) {
assert.step(`transitionTo: ${routeName} ["${id}"]`);
}
}
this.owner.register('service:router', MockRouterService);
// Mock Storage Service (Custom Place)
class MockStorageService extends Service {
savedPlaces = [
{
id: 'custom-1234',
title: 'My Custom Home',
lat: 52.5,
lon: 13.4,
// Notice NO osmId or osmType
},
];
}
this.owner.register('service:storage', MockStorageService);
// Mock Photon Service
class MockPhotonService extends Service {
async search() {
return [];
}
}
this.owner.register('service:photon', MockPhotonService);
this.noop = () => {};
await render(
<template><SearchBox @onToggleMenu={{this.noop}} /></template>
);
// Type 3 chars to trigger saved place match
await fillIn('.search-input', 'cus');
await waitFor('.search-results-popover', { timeout: 2000 });
const resultItems = Array.from(
this.element.querySelectorAll('.search-result-item')
);
// Ensure our custom place is rendered
const customResult = resultItems.find((item) =>
item.textContent.includes('My Custom Home')
);
assert.ok(customResult, 'Custom place is rendered');
// Click it
await click(customResult);
// Verify it navigated using the internal ID, NOT a search query
assert.verifySteps(['transitionTo: place ["custom-1234"]']);
});
}); });
+8
View File
@@ -36,6 +36,8 @@ module('Unit | Route | place', function (hooks) {
selectPlaceCalled = true; selectPlaceCalled = true;
} }
stopSearch() {} stopSearch() {}
showSidebar() {}
hideSidebar() {}
} }
this.owner.register('service:osm', OsmStub); this.owner.register('service:osm', OsmStub);
@@ -76,6 +78,8 @@ module('Unit | Route | place', function (hooks) {
class MapUiStub extends Service { class MapUiStub extends Service {
selectPlace() {} selectPlace() {}
stopSearch() {} stopSearch() {}
showSidebar() {}
hideSidebar() {}
} }
this.owner.register('service:osm', OsmStub); this.owner.register('service:osm', OsmStub);
@@ -110,6 +114,8 @@ module('Unit | Route | place', function (hooks) {
class MapUiStub extends Service { class MapUiStub extends Service {
selectPlace() {} selectPlace() {}
stopSearch() {} stopSearch() {}
showSidebar() {}
hideSidebar() {}
} }
this.owner.register('service:osm', OsmStub); this.owner.register('service:osm', OsmStub);
@@ -155,6 +161,8 @@ module('Unit | Route | place', function (hooks) {
assert.ok(options.preventZoom, 'Prevented zoom on update'); assert.ok(options.preventZoom, 'Prevented zoom on update');
} }
stopSearch() {} stopSearch() {}
showSidebar() {}
hideSidebar() {}
} }
this.owner.register('service:storage', StorageStub); this.owner.register('service:storage', StorageStub);
+144
View File
@@ -0,0 +1,144 @@
import { module, test } from 'qunit';
import { normalizeRelayUrl, parsePlacePhotos } from 'marco/utils/nostr';
module('Unit | Utility | nostr', function () {
test('normalizeRelayUrl normalizes protocol, case, and slashes', function (assert) {
assert.strictEqual(normalizeRelayUrl(null), '');
assert.strictEqual(normalizeRelayUrl(''), '');
assert.strictEqual(normalizeRelayUrl(' '), '');
assert.strictEqual(
normalizeRelayUrl('Relay.example.com'),
'wss://relay.example.com'
);
assert.strictEqual(
normalizeRelayUrl('ws://Relay.example.com/'),
'ws://relay.example.com'
);
assert.strictEqual(
normalizeRelayUrl('wss://relay.example.com///'),
'wss://relay.example.com'
);
});
test('parsePlacePhotos includes event t tags on photo objects', function (assert) {
const events = [
{
id: 'event-1',
pubkey: 'pubkey-1',
created_at: 123,
tags: [
['i', 'osm:node:123'],
['t', 'food'],
['t', 'vibe'],
['imeta', 'url https://example.com/photo.jpg', 'dim 800x600'],
],
},
];
const photos = parsePlacePhotos(events);
assert.strictEqual(photos.length, 1);
assert.deepEqual(photos[0].tags, ['food', 'vibe']);
});
test('parsePlacePhotos sorts by created_at', function (assert) {
const events = [
{
id: 'event-2',
pubkey: 'pubkey-2',
created_at: 200,
tags: [
['i', 'osm:node:456'],
['imeta', 'url https://example.com/late.jpg', 'dim 600x900'],
],
},
{
id: 'event-1',
pubkey: 'pubkey-1',
created_at: 100,
tags: [
['i', 'osm:node:123'],
['imeta', 'url https://example.com/early.jpg', 'dim 600x900'],
],
},
];
const photos = parsePlacePhotos(events);
assert.strictEqual(photos.length, 2);
assert.strictEqual(photos[0].url, 'https://example.com/early.jpg');
assert.strictEqual(photos[1].url, 'https://example.com/late.jpg');
});
test('parsePlacePhotos promotes first landscape photo to index 0', function (assert) {
const events = [
{
id: 'event-1',
pubkey: 'pubkey-1',
created_at: 100,
tags: [
['imeta', 'url https://example.com/portrait.jpg', 'dim 600x900'],
],
},
{
id: 'event-2',
pubkey: 'pubkey-2',
created_at: 200,
tags: [
['imeta', 'url https://example.com/landscape.jpg', 'dim 1200x600'],
],
},
];
const photos = parsePlacePhotos(events);
assert.strictEqual(photos.length, 2);
assert.strictEqual(photos[0].url, 'https://example.com/landscape.jpg');
assert.strictEqual(photos[1].url, 'https://example.com/portrait.jpg');
});
test('parsePlacePhotos skips imeta entries without urls', function (assert) {
const events = [
{
id: 'event-1',
pubkey: 'pubkey-1',
created_at: 100,
tags: [['imeta', 'dim 800x600']],
},
];
const photos = parsePlacePhotos(events);
assert.deepEqual(photos, []);
});
test('parsePlacePhotos returns one photo per event imeta tag', function (assert) {
const events = [
{
id: 'event-1',
pubkey: 'pubkey-1',
created_at: 100,
tags: [
['i', 'osm:node:123'],
['imeta', 'url https://example.com/photo-1.jpg', 'dim 800x600'],
],
},
{
id: 'event-2',
pubkey: 'pubkey-2',
created_at: 200,
tags: [
['i', 'osm:node:456'],
['imeta', 'url https://example.com/photo-2.jpg', 'dim 600x800'],
],
},
];
const photos = parsePlacePhotos(events);
assert.strictEqual(photos.length, 2);
assert.strictEqual(photos[0].placeIdentifier, 'osm:node:123');
assert.strictEqual(photos[1].placeIdentifier, 'osm:node:456');
});
});
@@ -0,0 +1,30 @@
import { module, test } from 'qunit';
import { POI_CATEGORIES } from 'marco/utils/poi-categories';
import { getMatchingPoiCategoryIds } from 'marco/utils/poi-category-matcher';
import {
getSuggestedPhotoTags,
CATEGORY_TAGS,
} from 'marco/utils/photo-tag-suggestions';
module('Unit | Utility | photo-tag-suggestions', function () {
test('returns tags for all matching categories with de-duplication', function (assert) {
const place = { osmTags: { amenity: 'cafe' } };
const categoryIds = getMatchingPoiCategoryIds(
place.osmTags,
POI_CATEGORIES
);
assert.ok(categoryIds.includes('restaurants'));
assert.ok(categoryIds.includes('coffee'));
const result = getSuggestedPhotoTags(place);
assert.deepEqual(result, CATEGORY_TAGS.restaurants);
});
test('returns no tags when no category matches', function (assert) {
const place = { osmTags: { office: 'lawyer' } };
const result = getSuggestedPhotoTags(place);
assert.deepEqual(result, []);
});
});
@@ -0,0 +1,38 @@
import { module, test } from 'qunit';
import { POI_CATEGORIES } from 'marco/utils/poi-categories';
import {
getMatchingPoiCategories,
getMatchingPoiCategoryIds,
} from 'marco/utils/poi-category-matcher';
module('Unit | Utility | poi-category-matcher', function () {
test('matches multiple categories from OSM tags', function (assert) {
const tags = { amenity: 'cafe' };
const categoryIds = getMatchingPoiCategoryIds(tags, POI_CATEGORIES);
assert.ok(categoryIds.includes('restaurants'));
assert.ok(categoryIds.includes('coffee'));
});
test('supports semicolon-separated values', function (assert) {
const tags = { amenity: 'cafe;bar' };
const categoryIds = getMatchingPoiCategoryIds(tags, POI_CATEGORIES);
assert.ok(categoryIds.includes('coffee'));
});
test('negative regex clause fails if any value matches', function (assert) {
const tags = { amenity: 'cafe', cuisine: 'coffee;irish' };
const categoryIds = getMatchingPoiCategoryIds(tags, POI_CATEGORIES);
assert.notOk(categoryIds.includes('restaurants'));
});
test('presence clause matches when tag exists', function (assert) {
const tags = { historic: 'castle' };
const categories = getMatchingPoiCategories(tags, POI_CATEGORIES);
const categoryIds = categories.map((category) => category.id);
assert.ok(categoryIds.includes('things-to-do'));
});
});
+1 -1
View File
@@ -4,7 +4,7 @@ import { babel } from '@rollup/plugin-babel';
export default defineConfig({ export default defineConfig({
server: { server: {
host: '127.0.0.1', host: '0.0.0.0',
}, },
plugins: [ plugins: [
ember(), ember(),