From e807e246e3a68185f09e7b397cd6e068d7f97984 Mon Sep 17 00:00:00 2001 From: David Heyman Date: Wed, 4 Nov 2026 22:32:39 +0000 Subject: [PATCH 1/7] Fix broken reference tiles and modernize the UI The label layer pointed at tile.stamen.com, which shut down in 2023, so the map showed no roads or labels. Replace it with Esri's keyless World_Transportation and World_Boundaries_and_Places overlays, which also restores the roads the checkbox always promised. Swap Mapbox.js 3.1.1 for plain Leaflet 1.9.4 (the Mapbox token is kept for place search only), skip failed elevation tiles instead of stalling the redraw, and position tiles relative to the map container. Rebuild the page chrome: top bar with search, left tool rail, sliding Style and Download panels, a phone layout, keyboard focus states and reduced-motion support. Styles move to css/contours.css. Fix the Axis Maps link and logo, update the blog link, and drop the dead Universal Analytics tag. Co-Authored-By: Claude Fable 5.1 --- README.md | 16 +- contours.js | 75 ++++-- css/contours.css | 581 +++++++++++++++++++++++++++++++++++++++++++++++ index.html | 538 +++++++++++++------------------------------ 4 files changed, 806 insertions(+), 404 deletions(-) create mode 100644 css/contours.css diff --git a/README.md b/README.md index a03fdf9..fbada43 100644 --- a/README.md +++ b/README.md @@ -2,4 +2,18 @@ A tool for drawing and exporting simple contour maps. https://contours.axismaps.com/ -Read more at https://www.axismaps.com/blog/2018/04/contours-in-browser/ +Read more at https://www.axismaps.com/blog/contour-maps-in-a-web-browser + +## Data sources + +- Elevation: [Terrain Tiles](https://registry.opendata.aws/terrain-tiles/) (Mapzen Terrarium PNGs on AWS), no key needed. +- Roads and place labels: Esri's keyless `World_Transportation` and `World_Boundaries_and_Places` reference tiles. These replaced the Stamen label tiles, whose server shut down in 2023. +- Place search: Mapbox Geocoding, using the token in `contours.js`. + +## Running locally + +It is a static site. Serve the folder with any web server, for example: + + python3 -m http.server 8765 + +then open http://localhost:8765/. diff --git a/contours.js b/contours.js index 40d2ed8..74662f0 100644 --- a/contours.js +++ b/contours.js @@ -158,13 +158,20 @@ d3.select('#shadow-width').on('keyup', function () { wait = setTimeout(function () { load(drawContours) },500); }); +function syncPanelState () { + var open = d3.select('#settings').classed('show') || d3.select('#download').classed('show'); + d3.select('#wrapper').classed('panel-open', open); + d3.select('#settings-toggle').attr('aria-expanded', d3.select('#settings').classed('show')); + d3.select('#download-toggle').attr('aria-expanded', d3.select('#download').classed('show')); +} + d3.select('#settings-toggle').on('click', function () { d3.select('#settings').classed('show', !d3.select('#settings').classed('show')); d3.select(this).classed('show', !d3.select(this).classed('show')); if (d3.select('#settings').classed('show')) { d3.selectAll('#download, #download-toggle').classed('show', false); } - d3.select('#wrapper').classed('panel-open', d3.select('#settings').classed('show') || d3.select('#download').classed('show')); + syncPanelState(); }); d3.select('#download-toggle').on('click', function () { @@ -173,7 +180,7 @@ d3.select('#download-toggle').on('click', function () { if (d3.select('#download').classed('show')) { d3.selectAll('#settings, #settings-toggle').classed('show', false); } - d3.select('#wrapper').classed('panel-open', d3.select('#settings').classed('show') || d3.select('#download').classed('show')); + syncPanelState(); }); d3.selectAll('input[name="bg"]').on('change', function () { @@ -317,18 +324,18 @@ d3.selectAll('.color-input').on('change', function () { wait = setTimeout(function () { load(drawContours) },500); }) -d3.select('input[type="checkbox"]').on('change', function () { - if (this.checked) referenceLayer.setOpacity(1); - else referenceLayer.setOpacity(0); +d3.select('#show-labels').on('change', function () { + if (this.checked) map.addLayer(referenceLayer); + else map.removeLayer(referenceLayer); }); d3.select('#download-geojson').on('click', downloadGeoJson); d3.select('#download-png').on('click', downloadPNG); d3.select('#download-svg').on('click', downloadSVG); -d3.selectAll('.icon-left-open').on('click', function () { +d3.selectAll('.panel-close').on('click', function () { d3.selectAll('.show').classed('show', false); - d3.select('#wrapper').classed('panel-open', false); + syncPanelState(); }); // short delay before searching after key press, so we don't send too many requests @@ -358,7 +365,7 @@ function search (val) { .selectAll('.search-result').remove(); d3.select('body').on('click.search', null); } - var geocodeURL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/' + encodeURIComponent(val) + '.json?language=en&types=place,locality,neighborhood,poi&access_token=' + L.mapbox.accessToken; + var geocodeURL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/' + encodeURIComponent(val) + '.json?language=en&types=place,locality,neighborhood,poi&access_token=' + mapboxAccessToken; d3.json(geocodeURL, function (error, json) { if (json && json.features && json.features.length) { var restults = d3.select('#search-results').style('display', 'block') @@ -423,11 +430,23 @@ The good stuff starts from here /* Set up the map*/ -L.mapbox.accessToken = 'pk.eyJ1IjoiYXhpc21hcHMiLCJhIjoieUlmVFRmRSJ9.CpIxovz1TUWe_ecNLFuHNg'; +// open the style panel on first load so the controls are discoverable (not on small screens, where it covers the map) +if (window.matchMedia('(min-width: 720px)').matches) { + d3.selectAll('#settings, #settings-toggle').classed('show', true); +} +syncPanelState(); + +// Mapbox token is only used for the place search (geocoding) +var mapboxAccessToken = 'pk.eyJ1IjoiYXhpc21hcHMiLCJhIjoieUlmVFRmRSJ9.CpIxovz1TUWe_ecNLFuHNg'; -var map = L.mapbox.map('map',null,{scrollWheelZoom: false}); +var map = L.map('map', { + scrollWheelZoom: false, + zoomControl: false, + maxZoom: 15 // elevation tiles only go to zoom 15 +}); +L.control.zoom({ position: 'topright' }).addTo(map); var hash = new L.Hash(map); -map.setView(map_start_location.slice(0, 3), map_start_location[2]); +map.setView(map_start_location.slice(0, 2), map_start_location[2]); map.on('moveend', function() { // on move end we redraw the contour layer, so clear some stuff @@ -456,23 +475,42 @@ var CanvasLayer = L.GridLayer.extend({ clearTimeout(wait); wait = setTimeout(getRelief,500); // only draw after a reasonable delay, so that we don't redraw on every single tile load } - img.src = 'https://elevation-tiles-prod.s3.amazonaws.com/terrarium/'+coords.z+'/'+coords.x+'/'+coords.y+'.png' + img.onerror = function () { + // a missing tile shouldn't stall the redraw + clearTimeout(wait); + wait = setTimeout(getRelief,500); + } + img.src = 'https://s3.amazonaws.com/elevation-tiles-prod/terrarium/'+coords.z+'/'+coords.x+'/'+coords.y+'.png' return tile; } }); -var demLayer = new CanvasLayer({attribution: 'Elevation tiles by Mapzen'}).addTo(map); +var demLayer = new CanvasLayer({attribution: 'Elevation: Terrain Tiles (Mapzen / AWS)'}).addTo(map); // custom map pane for the contours, above other layers var pane = map.createPane('contour'); +pane.style.zIndex = 400; pane.appendChild(contourCanvas); -// custom map pane for the labels +// custom map pane for the reference labels, above the contours var labelPane = map.createPane('labels'); -var referenceLayer = L.tileLayer('http://tile.stamen.com/toner-labels/{z}/{x}/{y}.png', { +labelPane.style.zIndex = 450; +labelPane.style.pointerEvents = 'none'; +// Stamen's tile server shut down in 2023 (and CARTO's free tiles now need a key), so the +// reference layer is Esri's keyless road + place-label overlays, which also brings roads back. +var esriAttribution = 'Roads & labels © Esri, HERE, Garmin, OpenStreetMap contributors'; +var roadsLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}', { minZoom: 0, maxZoom: 15, pane: 'labels', -}).addTo(map); + opacity: 0.5, + attribution: esriAttribution +}); +var placesLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', { + minZoom: 0, + maxZoom: 15, + pane: 'labels' +}); +var referenceLayer = L.layerGroup([roadsLayer, placesLayer]).addTo(map); reverseTransform(); // this resets our canvas back to top left of the window after panning the map @@ -497,9 +535,12 @@ function getRelief(){ reverseTransform(); // reset DEM data by drawing elevation tiles to it + var mapRect = mapNode.getBoundingClientRect(); for (var t in demLayer._tiles) { + var img = demLayer._tiles[t].el.img; + if (!img || !img.complete || !img.naturalWidth) continue; // not loaded (or failed), skip it var rect = demLayer._tiles[t].el.getBoundingClientRect(); - demContext.drawImage(demLayer._tiles[t].el.img,rect.left + buffer,rect.top + buffer); + demContext.drawImage(img, rect.left - mapRect.left + buffer, rect.top - mapRect.top + buffer); } demImageData = demContext.getImageData(0,0,width,height); demData = demImageData.data; diff --git a/css/contours.css b/css/contours.css new file mode 100644 index 0000000..0a64040 --- /dev/null +++ b/css/contours.css @@ -0,0 +1,581 @@ +/* Contours — application chrome. + The map is the hero; everything here is quiet field-notebook olive around it. */ + +:root { + --ink: #26261f; + --ink-soft: #55543f; + --muted: #7d7b68; + --chrome: #585748; /* the original header olive, kept for continuity */ + --chrome-deep: #3f3e33; + --paper: #f5f5f0; + --surface: #ffffff; + --line: #dcdbd0; + --line-strong: #c6c5b6; + --accent: #8c7556; /* the default contour color, so the UI matches the map */ + --accent-ink: #5e4c35; + --accent-wash: #efe8df; + --danger: #a35a4a; + --focus: #2f6f9f; + + --topbar-h: 48px; + --rail-w: 48px; + --panel-w: 320px; + --ease: cubic-bezier(.2, .7, .2, 1); + --dur: .32s; + + --font: 'IBM Plex Sans', 'Helvetica Neue', Arial, sans-serif; +} + +* { box-sizing: border-box; } + +html, body { + height: 100%; + margin: 0; +} + +body { + font-family: var(--font); + font-size: 14px; + line-height: 1.45; + color: var(--ink); + background: var(--surface); + -webkit-font-smoothing: antialiased; + overflow: hidden; +} + +#wrapper { + position: relative; + height: 100%; +} + +#map { + position: absolute; + inset: 0; + z-index: 0; +} + +.leaflet-container { background: #fff; font-family: var(--font); } +canvas#contours { position: absolute; top: 0; left: 0; } + +/* ---- Top bar ------------------------------------------------------------ */ + +.topbar { + position: absolute; + top: 0; left: 0; right: 0; + height: var(--topbar-h); + z-index: 1200; + display: flex; + align-items: center; + gap: 16px; + padding: 0 14px 0 0; + background: var(--chrome); + color: #fff; + box-shadow: 0 1px 0 rgba(0,0,0,.18); +} + +/* The one flourish: the wordmark is drawn as outlined letterforms, like contour lines. + On phones it lives at the foot of the rail, reading upward; on wider screens it sits in the top bar. */ +.wordmark { + margin: 0; + color: transparent; + -webkit-text-stroke: 1px #fff; + font-weight: 600; + text-transform: uppercase; + user-select: none; +} +@media (max-width: 719px) { + .wordmark { + position: fixed; + left: 0; + bottom: 14px; + z-index: 1150; + width: var(--rail-w); + display: flex; + justify-content: center; + font-size: 14px; + letter-spacing: .12em; + writing-mode: vertical-rl; + transform: rotate(180deg); + pointer-events: none; + } + .topbar { padding-left: 12px; } +} +@media (min-width: 720px) { + .wordmark { + flex: 0 0 auto; + height: var(--topbar-h); + display: flex; + align-items: center; + padding: 0 18px; + font-size: 20px; + letter-spacing: .14em; + background: var(--chrome-deep); + } +} + +.credit { + margin-left: auto; + display: inline-flex; + align-items: center; + gap: 8px; + color: rgba(255,255,255,.82); + text-decoration: none; + font-size: 13px; + white-space: nowrap; +} +.credit img { width: 20px; height: 20px; border-radius: 4px; display: block; } +.credit:hover { color: #fff; } +@media (max-width: 719px) { .credit span { display: none; } } + +/* ---- Search ------------------------------------------------------------- */ + +#search { + position: relative; + flex: 1 1 auto; + max-width: 420px; +} +.search-icon { + position: absolute; + left: 10px; top: 50%; + width: 16px; height: 16px; + transform: translateY(-50%); + color: var(--muted); + pointer-events: none; +} +#search input { + width: 100%; + height: 32px; + padding: 0 12px 0 32px; + border: 1px solid transparent; + border-radius: 6px; + background: rgba(255,255,255,.92); + color: var(--ink); + font: inherit; + font-size: 14px; + outline: none; + -webkit-appearance: none; + appearance: none; +} +#search input::-webkit-search-cancel-button { -webkit-appearance: none; } +#search input::placeholder { color: var(--muted); } +#search input:focus { background: #fff; border-color: #fff; box-shadow: 0 0 0 3px rgba(255,255,255,.35); } + +#search-results { + display: none; + position: absolute; + top: calc(100% + 6px); + left: 0; right: 0; + padding: 4px 0; + background: var(--surface); + color: var(--ink); + border-radius: 8px; + box-shadow: 0 8px 28px rgba(38,38,31,.22), 0 0 0 1px rgba(38,38,31,.08); + overflow: hidden; +} +.search-result { + padding: 8px 14px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + cursor: pointer; +} +.search-result:hover, .search-result.highlight { background: var(--accent-wash); } + +/* ---- Left rail + panels ------------------------------------------------- */ + +.rail { + position: absolute; + top: var(--topbar-h); + left: 0; + bottom: 0; + width: var(--rail-w); + z-index: 1100; + display: flex; + flex-direction: column; + background: var(--chrome); + padding-top: 8px; +} + +.toggle { + position: relative; + width: 100%; + height: 56px; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 3px; + padding: 0; + border: 0; + background: transparent; + color: rgba(255,255,255,.78); + font: inherit; + font-size: 10px; + letter-spacing: .02em; + cursor: pointer; +} +.toggle svg { width: 22px; height: 22px; } +.toggle:hover { color: #fff; background: rgba(255,255,255,.06); } +.toggle.show { + color: var(--accent-ink); + background: var(--paper); +} +/* notch that joins the active toggle to the open panel */ +.toggle.show::after { + content: ''; + position: absolute; + right: -1px; top: 0; bottom: 0; + width: 2px; + background: var(--paper); +} + +#panels { + position: absolute; + top: var(--topbar-h); + bottom: 0; + left: var(--rail-w); + width: var(--panel-w); + max-width: calc(100vw - var(--rail-w)); + z-index: 1000; + background: var(--paper); + border-right: 1px solid var(--line-strong); + box-shadow: 0 0 24px rgba(38,38,31,.14); + transform: translateX(calc(-100% - 30px)); + transition: transform var(--dur) var(--ease), visibility 0s linear var(--dur); + visibility: hidden; + overflow: hidden; +} +.panel-open #panels { + transform: none; + visibility: visible; + transition: transform var(--dur) var(--ease); +} + +.panel { + position: absolute; + inset: 0; + overflow-y: auto; + overscroll-behavior: contain; + padding: 0 0 28px; + background: var(--paper); + display: none; +} +.panel.show { display: block; } + +.panel-head { + position: sticky; + top: 0; + z-index: 2; + display: flex; + align-items: center; + justify-content: space-between; + height: 48px; + padding: 0 8px 0 20px; + background: var(--paper); + border-bottom: 1px solid var(--line); +} +.panel-head h2 { + margin: 0; + font-size: 15px; + font-weight: 600; + letter-spacing: .01em; +} +.panel-close { + width: 32px; height: 32px; + display: inline-flex; align-items: center; justify-content: center; + border: 0; border-radius: 6px; + background: transparent; + color: var(--muted); + cursor: pointer; +} +.panel-close svg { width: 18px; height: 18px; } +.panel-close:hover { color: var(--ink); background: rgba(38,38,31,.06); } + +/* ---- Settings content --------------------------------------------------- */ + +.settings-row { + padding: 18px 20px 6px; + border-bottom: 1px solid var(--line); +} +.settings-row:last-of-type { border-bottom: 0; } + +.settings-title { + margin: 0 0 10px; + font-size: 12px; + font-weight: 600; + color: var(--ink-soft); +} + +.settings-content p { margin: 0 0 10px; color: var(--ink-soft); } +.settings-content code { + font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 12px; + background: rgba(38,38,31,.06); + padding: 1px 4px; + border-radius: 3px; +} + +.settings-section { + display: flex; + align-items: center; + gap: 10px; + min-height: 36px; + margin-bottom: 8px; +} +.settings-section > span { flex: 1 1 auto; } + +.row-icon { + flex: 0 0 auto; + width: 36px; height: 20px; + display: flex; align-items: center; +} +.row-icon img { max-width: 100%; max-height: 100%; display: block; opacity: .85; } + +/* text inputs */ +.settings-section input[type="text"], +.settings-section select { + height: 30px; + padding: 0 8px; + border: 1px solid var(--line-strong); + border-radius: 6px; + background: var(--surface); + color: var(--ink); + font: inherit; + font-size: 13px; + font-variant-numeric: tabular-nums; +} +.settings-section input[type="text"] { width: 52px; text-align: center; } +.settings-section input#interval-input { width: 64px; } +.settings-section input.color-input { width: 78px; text-align: left; text-transform: lowercase; } +.settings-section input:focus-visible, .settings-section select:focus-visible, +.choice input:focus-visible + span, .segmented input:focus-visible + span, +.switch input:focus-visible + .switch-track { + outline: 2px solid var(--focus); + outline-offset: 1px; +} +.settings-section input:disabled, .settings-section select:disabled { opacity: .45; } + +.with-unit { display: inline-flex; align-items: center; gap: 6px; } +.with-unit em { font-style: normal; color: var(--muted); font-size: 12px; } + +.settings-section select { + padding-right: 26px; + -webkit-appearance: none; + appearance: none; + background-image: url("data:image/svg+xml;utf8,"); + background-repeat: no-repeat; + background-position: right 9px center; +} + +/* color swatch buttons (jscolor attaches to these) */ +button[type="color"] { + flex: 0 0 auto; + width: 36px; height: 20px; + margin: 0; + padding: 0; + border: 1px solid rgba(38,38,31,.25); + border-radius: 4px; + box-shadow: inset 0 0 0 1px rgba(255,255,255,.6); + cursor: pointer; +} +button[type="color"]:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; } + +/* radio choices as a list */ +.choice-list { display: flex; flex-direction: column; } +.choice { display: block; margin: 0 0 2px; } +.choice input[type="radio"] { + position: absolute; + opacity: 0; + width: 0; height: 0; + margin: 0; +} +.choice input[type="radio"] + span { + position: relative; + display: block; + padding: 7px 10px 7px 26px; + border-radius: 6px; + color: var(--ink-soft); + cursor: pointer; + width: auto; +} +.choice input[type="radio"] + span::before { + content: ''; + position: absolute; + left: 8px; top: 50%; + width: 10px; height: 10px; + transform: translateY(-50%); + border-radius: 50%; + border: 1.5px solid var(--line-strong); + background: var(--surface); + transition: border-color .15s, box-shadow .15s; +} +.choice input[type="radio"] + span:hover { background: rgba(38,38,31,.045); } +.choice input[type="radio"]:checked + span { + color: var(--ink); + font-weight: 500; + background: var(--accent-wash); +} +.choice input[type="radio"]:checked + span::before { + border-color: var(--accent-ink); + box-shadow: inset 0 0 0 2.5px var(--surface), inset 0 0 0 8px var(--accent-ink); +} + +/* indented color rows under a radio choice */ +.color-section { margin: 2px 0 10px 26px; } + +/* segmented unit control */ +.segmented { + display: inline-flex; + border: 1px solid var(--line-strong); + border-radius: 6px; + overflow: hidden; + background: var(--surface); +} +.segmented label { display: block; } +.segmented input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; } +.segmented input[type="radio"] + span { + display: block; + min-width: 34px; + height: 28px; + line-height: 28px; + padding: 0 8px; + text-align: center; + font-size: 13px; + color: var(--ink-soft); + cursor: pointer; +} +.segmented label + label span { border-left: 1px solid var(--line-strong); } +.segmented input[type="radio"]:checked + span { + background: var(--chrome); + color: #fff; + font-weight: 500; +} + +/* labels toggle switch */ +.switch { + display: flex; + align-items: center; + gap: 10px; + padding: 14px 20px 12px; + border-bottom: 1px solid var(--line); + cursor: pointer; +} +.switch input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; } +.switch-track { + position: relative; + flex: 0 0 auto; + width: 32px; height: 18px; + border-radius: 9px; + background: var(--line-strong); + transition: background .15s; +} +.switch-track::after { + content: ''; + position: absolute; + top: 2px; left: 2px; + width: 14px; height: 14px; + border-radius: 50%; + background: #fff; + box-shadow: 0 1px 2px rgba(0,0,0,.25); + transition: transform .15s var(--ease); +} +.switch input:checked + .switch-track { background: var(--chrome); } +.switch input:checked + .switch-track::after { transform: translateX(14px); } + +div.disabled { display: none; } + +.note { + margin: 0; + padding: 12px 20px; + font-size: 13px; + color: var(--ink-soft); + background: var(--accent-wash); + border-bottom: 1px solid var(--line); +} +.caveat { + font-size: 12px; + color: var(--danger) !important; +} + +.about { + margin: 0; + padding: 16px 20px 0; + font-size: 12px; + color: var(--muted); +} +.about a { color: var(--accent-ink); } + +/* download buttons */ +.download-button { + display: inline-flex; + align-items: center; + gap: 8px; + height: 36px; + margin: 2px 0 14px; + padding: 0 14px; + border: 0; + border-radius: 6px; + background: var(--chrome); + color: #fff; + font: inherit; + font-size: 13px; + font-weight: 500; + cursor: pointer; + -webkit-appearance: none; + appearance: none; +} +.download-button svg { width: 16px; height: 16px; } +.download-button:hover { background: var(--chrome-deep); } +.download-button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } + +/* ---- Leaflet controls --------------------------------------------------- */ + +.leaflet-top { top: calc(var(--topbar-h) + 10px); } +.leaflet-bar { border: 0 !important; box-shadow: 0 2px 10px rgba(38,38,31,.18), 0 0 0 1px rgba(38,38,31,.08) !important; border-radius: 6px !important; overflow: hidden; } +.leaflet-bar a { color: var(--ink-soft) !important; border-bottom-color: var(--line) !important; } +.leaflet-bar a:hover { color: var(--ink) !important; background: var(--paper) !important; } +.leaflet-control-attribution { + font-family: var(--font); + font-size: 11px; + color: var(--muted) !important; + background: rgba(255,255,255,.85) !important; + padding: 2px 8px !important; + border-top-left-radius: 6px; +} +.leaflet-control-attribution a { color: var(--ink-soft) !important; } + +/* ---- Loading ------------------------------------------------------------ */ + +#loading { + position: absolute; + inset: var(--topbar-h) 0 0 var(--rail-w); + z-index: 900; /* dims the map only; the panel and top bar stay usable-looking */ + display: flex; + align-items: center; + justify-content: center; + background: rgba(245,245,240,.55); + color: var(--ink); +} +.loading-card { + display: inline-flex; + align-items: center; + gap: 10px; + padding: 10px 16px; + border-radius: 999px; + background: var(--surface); + box-shadow: 0 6px 24px rgba(38,38,31,.18), 0 0 0 1px rgba(38,38,31,.08); + font-size: 14px; +} +.loading-ring { + width: 16px; height: 16px; + border-radius: 50%; + border: 2px solid var(--line-strong); + border-top-color: var(--accent-ink); + animation: spin .8s linear infinite; +} +@keyframes spin { to { transform: rotate(360deg); } } + +@media (prefers-reduced-motion: reduce) { + #panels, .toggle, .switch-track, .switch-track::after, .choice span::before { transition: none !important; } + .loading-ring { animation: none; border-top-color: var(--line-strong); } +} diff --git a/index.html b/index.html index ba9a979..627d616 100644 --- a/index.html +++ b/index.html @@ -1,19 +1,18 @@ - + + - - - - - - - + + Contours + + + + + + + + + @@ -26,357 +25,122 @@ - - -
-
CONTOURS
-
+ + +
+
+

Contours

+ + + + by Axis Maps + +
+ +
+ + +
- -
- -

Roads and labels are provided only for helping find your area of interest on this map. They will not be inlcuded in downloads.

-
-

GeoJSON

-
Download contours for use in GIS or mapping applications. Contours will be exported as overlapping MultiPolygon features, each with an elevation property.
Geodata only. Styles will not be included! - + +
+
+

Download

+ +
+

Roads and labels are only there to help you find your area. They are not included in any download.

+ +
+

GeoJSON

+
+

Contours for GIS or mapping software, as overlapping MultiPolygon features with an elevation property.

+

Geodata only. Styles are not included.

+
-
-

PNG image

-
Download a raster image of the map, including your chosen styles. - + +
+

PNG image

+
+

A raster image of the map at screen size, with your chosen styles.

+
-
-

SVG

-
Download contours as vector graphics.
This does not work well for illuminated contours! It will export polygons, but you'll have better luck achieving the effect in your favorite graphics editing software. - + +
+

SVG

+
+

Contours as vector graphics, one path per elevation.

+

Illuminated contours export as plain polygons. Recreate the lighting effect in your graphics editor.

+
-
+
-
- - -
-

Map Type

-
- - +
+
+

Style

+ +
+ + + +
+

Map type

+
+ +
-
-

Contour Interval

-
+ +
+

Contour interval

+
-
- - - - +
+ Every + +
+ + +
-
- Index line interval - @@ -385,71 +149,73 @@
-
-

Contour Style

-
+ +
+

Contour style

+
-
Contour color
-
Line weight
-
Index line weight
+
Contour color
+
Line weight
+
Index line weight
-
-

Background style

-
- - + +
+

Background

+
+ +
- Fill color + Fill color
- +
- Low color + Low color
- High color + High color
-
-

Bathymetry

-
- - +
+

Bathymetry

+
+ +
- Fill color + Fill color
- +
- Low color + Deep color
- High color + Shallow color
-
-
-
-
-
+
-
Drawing contours...
- + +
+
Drawing contours…
+
+ + - \ No newline at end of file + From a5efe94654d68e6b58b04fe597df4233c8a40387 Mon Sep 17 00:00:00 2001 From: David Heyman Date: Wed, 4 Nov 2026 23:03:25 +0000 Subject: [PATCH 2/7] Rewrite as a Next.js + TypeScript app on Untitled UI Replaces the static page (index.html, contours.js, jscolor, leaflet-hash) with a Next.js 16 App Router app, TypeScript strict, Tailwind v4 and Untitled UI React components installed with its CLI. The contouring pipeline is unchanged in behaviour but split into typed modules under src/lib/contours: Terrarium decoding, threshold selection with the junk-band filter, d3-contour, canvas and SVG rendering, GeoJSON, PNG and SVG exports, the URL hash and Mapbox geocoding. A hidden Leaflet GridLayer still fetches the elevation tiles, and use-contour-map owns the map, the canvases and the debounced redraw. The GeoJSON and SVG exports no longer carry the 5px canvas-buffer offset the old code had. The interface is rebuilt on Untitled UI's own patterns: a header with the place search, a slim icon rail with tooltips, slideout-style panels with label-above fields, React Aria Select, radio groups with hints, toggle, button group for units, and the Untitled UI color picker in an input group for every color. Brand ramp is the contour brown; grays use the warm stone scale. Reduced motion and keyboard focus are respected. The Mapbox token moves to NEXT_PUBLIC_MAPBOX_TOKEN (see .env.example). Co-Authored-By: Claude Fable 5.1 --- .env.example | 3 + .gitignore | 47 +- .prettierrc | 10 + README.md | 52 +- contours.js | 885 ------ css/animation.css | 85 - css/contours.css | 581 ---- css/fontello-codes.css | 5 - css/fontello-embedded.css | 58 - css/fontello-ie7-codes.css | 5 - css/fontello-ie7.css | 16 - css/fontello.css | 58 - download.png | Bin 3488 -> 0 bytes font/config.json | 138 - font/fontello.eot | Bin 6932 -> 0 bytes font/fontello.svg | 22 - font/fontello.ttf | Bin 6764 -> 0 bytes font/fontello.woff | Bin 4112 -> 0 bytes font/fontello.woff2 | Bin 3444 -> 0 bytes img/icon_contours.png | Bin 308 -> 0 bytes img/icon_major.png | Bin 400 -> 0 bytes img/icon_weight.png | Bin 448 -> 0 bytes img/icon_weight_major.png | Bin 487 -> 0 bytes index.html | 221 -- jscolor.js | 1844 ----------- jscolor.min.js | 10 - lib/leaflet-hash.js | 124 - next.config.mjs | 8 + package-lock.json | 2683 +++++++++++++++++ package.json | 49 + palette.png | Bin 10556 -> 0 bytes postcss.config.mjs | 5 + search.png | Bin 1505 -> 0 bytes src/app/icon.svg | 8 + src/app/layout.tsx | 32 + src/app/page.tsx | 5 + .../color-picker/color-picker-context.tsx | 90 + .../color-picker/color-picker-utils.ts | 5 + .../application/color-picker/color-picker.tsx | 452 +++ .../loading-indicator/loading-indicator.tsx | 123 + src/components/base/avatar/avatar.tsx | 174 ++ .../base-components/avatar-add-button.tsx | 34 + .../base-components/avatar-company-icon.tsx | 26 + .../avatar/base-components/avatar-count.tsx | 16 + .../avatar-online-indicator.tsx | 50 + .../base/avatar/base-components/index.tsx | 4 + .../avatar/base-components/verified-tick.tsx | 34 + .../base/button-group/button-group.tsx | 106 + src/components/base/buttons/button.tsx | 261 ++ src/components/base/buttons/close-button.tsx | 51 + src/components/base/checkbox/checkbox.tsx | 129 + src/components/base/input/hint-text.tsx | 41 + src/components/base/input/input-group.tsx | 157 + src/components/base/input/input.tsx | 306 ++ src/components/base/input/label.tsx | 68 + .../base/radio-buttons/radio-buttons.tsx | 123 + src/components/base/select/combobox.tsx | 190 ++ src/components/base/select/popover.tsx | 38 + src/components/base/select/select-item.tsx | 135 + src/components/base/select/select-shared.tsx | 51 + src/components/base/select/select.tsx | 159 + src/components/base/toggle/toggle.tsx | 139 + src/components/base/tooltip/tooltip.tsx | 109 + src/components/contours/app-header.tsx | 35 + src/components/contours/color-field.tsx | 93 + .../contours/contours-app-loader.tsx | 9 + src/components/contours/contours-app.tsx | 55 + src/components/contours/download-panel.tsx | 65 + src/components/contours/logo.tsx | 15 + src/components/contours/map-view.tsx | 24 + src/components/contours/number-field.tsx | 64 + src/components/contours/panel-section.tsx | 23 + src/components/contours/place-search.tsx | 106 + src/components/contours/side-panel.tsx | 41 + src/components/contours/style-panel.tsx | 164 + src/components/contours/tool-rail.tsx | 49 + src/hooks/use-contour-map.ts | 300 ++ src/hooks/use-resize-observer.ts | 67 + src/lib/contours/color.ts | 32 + src/lib/contours/compute.ts | 20 + src/lib/contours/constants.ts | 67 + src/lib/contours/draw.ts | 154 + src/lib/contours/elevation.ts | 22 + src/lib/contours/export.ts | 50 + src/lib/contours/geocode.ts | 28 + src/lib/contours/hash.ts | 19 + src/lib/contours/terrarium-layer.ts | 57 + src/lib/contours/thresholds.ts | 50 + src/lib/contours/types.ts | 56 + src/providers/router-provider.tsx | 17 + src/styles/globals.css | 62 + src/styles/map.css | 77 + src/styles/theme.css | 834 +++++ src/utils/cx.ts | 24 + src/utils/is-react-component.ts | 36 + tsconfig.json | 42 + 96 files changed, 8721 insertions(+), 4061 deletions(-) create mode 100644 .env.example create mode 100644 .prettierrc delete mode 100644 contours.js delete mode 100755 css/animation.css delete mode 100644 css/contours.css delete mode 100755 css/fontello-codes.css delete mode 100755 css/fontello-embedded.css delete mode 100755 css/fontello-ie7-codes.css delete mode 100755 css/fontello-ie7.css delete mode 100755 css/fontello.css delete mode 100644 download.png delete mode 100755 font/config.json delete mode 100755 font/fontello.eot delete mode 100755 font/fontello.svg delete mode 100755 font/fontello.ttf delete mode 100755 font/fontello.woff delete mode 100755 font/fontello.woff2 delete mode 100644 img/icon_contours.png delete mode 100644 img/icon_major.png delete mode 100644 img/icon_weight.png delete mode 100644 img/icon_weight_major.png delete mode 100644 index.html delete mode 100644 jscolor.js delete mode 100644 jscolor.min.js delete mode 100644 lib/leaflet-hash.js create mode 100644 next.config.mjs create mode 100644 package-lock.json create mode 100644 package.json delete mode 100644 palette.png create mode 100644 postcss.config.mjs delete mode 100644 search.png create mode 100644 src/app/icon.svg create mode 100644 src/app/layout.tsx create mode 100644 src/app/page.tsx create mode 100644 src/components/application/color-picker/color-picker-context.tsx create mode 100644 src/components/application/color-picker/color-picker-utils.ts create mode 100644 src/components/application/color-picker/color-picker.tsx create mode 100644 src/components/application/loading-indicator/loading-indicator.tsx create mode 100644 src/components/base/avatar/avatar.tsx create mode 100644 src/components/base/avatar/base-components/avatar-add-button.tsx create mode 100644 src/components/base/avatar/base-components/avatar-company-icon.tsx create mode 100644 src/components/base/avatar/base-components/avatar-count.tsx create mode 100644 src/components/base/avatar/base-components/avatar-online-indicator.tsx create mode 100644 src/components/base/avatar/base-components/index.tsx create mode 100644 src/components/base/avatar/base-components/verified-tick.tsx create mode 100644 src/components/base/button-group/button-group.tsx create mode 100644 src/components/base/buttons/button.tsx create mode 100644 src/components/base/buttons/close-button.tsx create mode 100644 src/components/base/checkbox/checkbox.tsx create mode 100644 src/components/base/input/hint-text.tsx create mode 100644 src/components/base/input/input-group.tsx create mode 100644 src/components/base/input/input.tsx create mode 100644 src/components/base/input/label.tsx create mode 100644 src/components/base/radio-buttons/radio-buttons.tsx create mode 100644 src/components/base/select/combobox.tsx create mode 100644 src/components/base/select/popover.tsx create mode 100644 src/components/base/select/select-item.tsx create mode 100644 src/components/base/select/select-shared.tsx create mode 100644 src/components/base/select/select.tsx create mode 100644 src/components/base/toggle/toggle.tsx create mode 100644 src/components/base/tooltip/tooltip.tsx create mode 100644 src/components/contours/app-header.tsx create mode 100644 src/components/contours/color-field.tsx create mode 100644 src/components/contours/contours-app-loader.tsx create mode 100644 src/components/contours/contours-app.tsx create mode 100644 src/components/contours/download-panel.tsx create mode 100644 src/components/contours/logo.tsx create mode 100644 src/components/contours/map-view.tsx create mode 100644 src/components/contours/number-field.tsx create mode 100644 src/components/contours/panel-section.tsx create mode 100644 src/components/contours/place-search.tsx create mode 100644 src/components/contours/side-panel.tsx create mode 100644 src/components/contours/style-panel.tsx create mode 100644 src/components/contours/tool-rail.tsx create mode 100644 src/hooks/use-contour-map.ts create mode 100644 src/hooks/use-resize-observer.ts create mode 100644 src/lib/contours/color.ts create mode 100644 src/lib/contours/compute.ts create mode 100644 src/lib/contours/constants.ts create mode 100644 src/lib/contours/draw.ts create mode 100644 src/lib/contours/elevation.ts create mode 100644 src/lib/contours/export.ts create mode 100644 src/lib/contours/geocode.ts create mode 100644 src/lib/contours/hash.ts create mode 100644 src/lib/contours/terrarium-layer.ts create mode 100644 src/lib/contours/thresholds.ts create mode 100644 src/lib/contours/types.ts create mode 100644 src/providers/router-provider.tsx create mode 100755 src/styles/globals.css create mode 100644 src/styles/map.css create mode 100644 src/styles/theme.css create mode 100644 src/utils/cx.ts create mode 100644 src/utils/is-react-component.ts create mode 100644 tsconfig.json diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..1394e98 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# Mapbox public token, used only for the place search (Geocoding API). +# Copy this file to .env.local; the value below is the token the original site shipped with. +NEXT_PUBLIC_MAPBOX_TOKEN=pk.eyJ1IjoiYXhpc21hcHMiLCJhIjoieUlmVFRmRSJ9.CpIxovz1TUWe_ecNLFuHNg diff --git a/.gitignore b/.gitignore index dbf0821..063022f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,46 @@ -node_modules/* \ No newline at end of file +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# env files (can opt-in for committing if needed) +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts + +# local env files +.env*.local + +# Claude Code local config and worktrees +.claude/ diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..9c0fc40 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,10 @@ +{ + "printWidth": 160, + "tabWidth": 4, + "plugins": ["@trivago/prettier-plugin-sort-imports", "prettier-plugin-tailwindcss"], + "tailwindFunctions": ["sortCx", "cx"], + "importOrder": ["^react$", "^react-dom$", "^(?!react$|react-dom$|@/|\\.).*", "^@/.*", "^\\.{1,2}/.*"], + "importOrderSeparation": false, + "importOrderSortSpecifiers": true, + "tailwindStylesheet": "./src/styles/globals.css" +} diff --git a/README.md b/README.md index fbada43..76f9784 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,55 @@ -# contours +# Contours -A tool for drawing and exporting simple contour maps. https://contours.axismaps.com/ +A tool for drawing and exporting simple contour maps of anywhere on Earth. Pick a place, tune the interval and style, then download GeoJSON, SVG or PNG. Live at https://contours.axismaps.com/. Read more at https://www.axismaps.com/blog/contour-maps-in-a-web-browser +## How it works + +Elevation comes from Mapzen's Terrarium tiles, decoded in the browser: each tile is drawn to an offscreen canvas, every pixel is turned back into metres, and [d3-contour](https://github.com/d3/d3-contour) traces contour polygons over the pixel grid at your chosen interval. The result is drawn to a canvas on top of a Leaflet map and can be exported as geodata or graphics. + +## Stack + +- [Next.js](https://nextjs.org/) (App Router) and TypeScript +- [Untitled UI React](https://www.untitledui.com/react) components on React Aria and Tailwind CSS v4 +- [Leaflet](https://leafletjs.com/) 1.9 for the map, loaded client-side only +- d3-contour, d3-geo, d3-scale and d3-interpolate for the contouring and tints + ## Data sources -- Elevation: [Terrain Tiles](https://registry.opendata.aws/terrain-tiles/) (Mapzen Terrarium PNGs on AWS), no key needed. -- Roads and place labels: Esri's keyless `World_Transportation` and `World_Boundaries_and_Places` reference tiles. These replaced the Stamen label tiles, whose server shut down in 2023. -- Place search: Mapbox Geocoding, using the token in `contours.js`. +- Elevation: [Terrain Tiles](https://registry.opendata.aws/terrain-tiles/) (Mapzen Terrarium PNGs on AWS), no key needed, max zoom 15. +- Roads and place labels: Esri's keyless `World_Transportation` and `World_Boundaries_and_Places` reference tiles. +- Place search: the Mapbox Geocoding API, with the public token in `NEXT_PUBLIC_MAPBOX_TOKEN`. ## Running locally -It is a static site. Serve the folder with any web server, for example: +Requires Node 20 or newer. + +```bash +cp .env.example .env.local +npm install +npm run dev +``` + +Then open http://localhost:3000/. Without `NEXT_PUBLIC_MAPBOX_TOKEN` the app still works, but the place search is disabled. + +## Building + +```bash +npm run build +npm start +``` + +`npm run typecheck` runs the TypeScript compiler on its own. + +## Project layout + +- `src/lib/contours/` – the contouring pipeline, framework-free: Terrarium decoding (`elevation.ts`), threshold selection (`thresholds.ts`), d3 contouring (`compute.ts`), canvas and SVG rendering (`draw.ts`), exports (`export.ts`), the URL hash (`hash.ts`), geocoding (`geocode.ts`) and the hidden Leaflet elevation layer (`terrarium-layer.ts`). +- `src/hooks/use-contour-map.ts` – owns the Leaflet map, the canvases and the debounced redraw pipeline, and exposes the download actions. +- `src/components/contours/` – the app shell: header with place search, tool rail, Style and Download panels. +- `src/components/base/`, `src/components/application/` – Untitled UI components, added with `npx untitledui@latest add `. +- `src/styles/theme.css` – design tokens. The brand ramp is the contour brown; grays use Tailwind's warm `stone` scale. - python3 -m http.server 8765 +## URL -then open http://localhost:8765/. +The view is kept in the hash as `#zoom/lat/lng`, so any view can be linked to. Without a hash the app opens at one of a few example mountains. diff --git a/contours.js b/contours.js deleted file mode 100644 index 74662f0..0000000 --- a/contours.js +++ /dev/null @@ -1,885 +0,0 @@ -if (!HTMLCanvasElement.prototype.toBlob) { - Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { - value: function (callback, type, quality) { - - var binStr = atob( this.toDataURL(type, quality).split(',')[1] ), - len = binStr.length, - arr = new Uint8Array(len); - - for (var i=0; iTerrain Tiles (Mapzen / AWS)'}).addTo(map); - -// custom map pane for the contours, above other layers -var pane = map.createPane('contour'); -pane.style.zIndex = 400; -pane.appendChild(contourCanvas); - -// custom map pane for the reference labels, above the contours -var labelPane = map.createPane('labels'); -labelPane.style.zIndex = 450; -labelPane.style.pointerEvents = 'none'; -// Stamen's tile server shut down in 2023 (and CARTO's free tiles now need a key), so the -// reference layer is Esri's keyless road + place-label overlays, which also brings roads back. -var esriAttribution = 'Roads & labels © Esri, HERE, Garmin, OpenStreetMap contributors'; -var roadsLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}', { - minZoom: 0, - maxZoom: 15, - pane: 'labels', - opacity: 0.5, - attribution: esriAttribution -}); -var placesLayer = L.tileLayer('https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}', { - minZoom: 0, - maxZoom: 15, - pane: 'labels' -}); -var referenceLayer = L.layerGroup([roadsLayer, placesLayer]).addTo(map); -reverseTransform(); - -// this resets our canvas back to top left of the window after panning the map -function reverseTransform() { - var top_left = map.containerPointToLayerPoint([-buffer, -buffer]); - L.DomUtil.setPosition(contourCanvas, top_left); -}; - -// this is to ensure the "loading" message gets a chance to show. show it then do the function (usually getRelief or drawContours) on next frame -function load (fn) { - requestAnimationFrame(function () { - d3.select('#loading').style('display', 'flex'); - requestAnimationFrame(fn); - }); -} - -// after terrain tiles are loaded, kick things off by drawing them to a canvas -function getRelief(){ - load(function() { - // reset canvases - demContext.clearRect(0,0,width,height); - reverseTransform(); - - // reset DEM data by drawing elevation tiles to it - var mapRect = mapNode.getBoundingClientRect(); - for (var t in demLayer._tiles) { - var img = demLayer._tiles[t].el.img; - if (!img || !img.complete || !img.naturalWidth) continue; // not loaded (or failed), skip it - var rect = demLayer._tiles[t].el.getBoundingClientRect(); - demContext.drawImage(img, rect.left - mapRect.left + buffer, rect.top - mapRect.top + buffer); - } - demImageData = demContext.getImageData(0,0,width,height); - demData = demImageData.data; - - getContours(); - }); -} - -// calculate contours -function getContours () { - var values = new Array(width*height); - // get elevation values for pixels - for (var y=0; y < height; y++) { - for (var x=0; x < width; x++) { - var i = getIndexForCoordinates(width, x,y); - // x + y*width is the array position expected by the contours generator - values[x + y*width] = Math.round(elev(i, demData) * (unit == 'ft' ? 3.28084 : 1)); - } - } - - var valuesCopy = values.concat().sort((a,b) => a-b); - - max = d3.max(values); - min = d3.min(values); - - interval = +d3.select('#interval-input').node().value; - - max = Math.ceil(max/interval) * interval; - min = Math.floor(min/interval) * interval; - - // throw away value ranges that seem to be junk (< 50 pixels) - var validThresholds = []; - for (var i = min; i <= max; i += interval) { - var f = valuesCopy.filter(v => { - return v >= i && v < (i + interval) - }); - if (f.length > 50) validThresholds.push(i); - } - - min = validThresholds[0]; - max = validThresholds[validThresholds.length - 1]; - - // the countour line values - thresholds = []; - for (i = min; i <= max; i += interval) { - thresholds.push(i); - } - contour.thresholds(thresholds); - - contoursGeoData = contour(values); // this gets the contours geojson - - hypsoColor.domain([min,max]); - - // update the index line options based on the current interval - d3.selectAll('#major option') - .html(function () { - if (+this.value == 0) return 'None'; - return +this.value * interval; - }); - majorInterval = +d3.select('#major').node().value * interval; - - // show bathymetry options if elevations include values below zero - d3.select('#bathymetry').style('display', min < 0 ? 'block' : 'none'); - if (min < 0) { - bathyColor.domain([min, -1]); - if (bathyColorType != 'none') { - hypsoColor.domain([0, max]); - } - } - - drawContours(); -} - -// draw the map! -function drawContours(svg) { - // svg option is for export - if (svg !== true) { // this is the normal canvas drawing - contourContext.clearRect(0,0,width,height); - contourContext.save(); - contourContext.lineJoin = 'round'; - if (type == 'illuminated') { - contourContext.lineWidth = shadowSize + 1; - contourContext.shadowBlur = shadowSize; - contourContext.shadowOffsetX = shadowSize; - contourContext.shadowOffsetY = shadowSize; - - contoursGeoData.forEach(function (c) { - contourContext.beginPath(); - if (c.value >= 0 || bathyColorType == 'none') { // for values above sea level (or if we aren't styling bahymetry) - contourContext.shadowColor = shadowColor; - contourContext.strokeStyle = highlightColor; - if (colorType == 'hypso') contourContext.fillStyle = hypsoColor(c.value); - else if (colorType == 'solid') contourContext.fillStyle = solidColor; - else contourContext.fillStyle = '#fff'; // fill can't really be transparent in this style, so "none" is actually white - } else { - // blue-ish shadow and highlight colors below sea level - // no user option for these colors because I'm lazy - contourContext.shadowColor = '#4e5c66'; - contourContext.strokeStyle = 'rgba(224, 242, 255, .5)'; - if (bathyColorType == 'bathy') contourContext.fillStyle = bathyColor(c.value); - else if (bathyColorType == 'solid') contourContext.fillStyle = oceanColor; - else contourContext.fillStyle = '#fff'; - } - path(c); // draws the shape - // draw the light stroke first, then the fill with drop shadow - // the effect is a light edge on side and dark on the other, giving the raised/illuminated contour appearance - contourContext.stroke(); - contourContext.fill(); - }); - } else { // regular contour lines - contourContext.lineWidth = lineWidth; - contourContext.strokeStyle = lineColor; - if (colorType != 'hypso' && bathyColorType == 'none') { - // no fill or solid fill. we don't have to fill/stroke individual contours, but rather can do them all at once - contourContext.beginPath(); - contoursGeoData.forEach(function (c) { - if (majorInterval == 0 || c.value % majorInterval != 0) path(c); - }); - if (colorType == 'solid') { - contourContext.fillStyle = solidColor; - contourContext.fill(); - } - contourContext.stroke(); - } else { - // for hypsometric tints or a separate bathymetric fill, we have to fill contours one at a time - contoursGeoData.forEach(function (c) { - contourContext.beginPath(); - var fill; - if (c.value >= 0 || bathyColorType == 'none') { - if (colorType == 'hypso') fill = hypsoColor(c.value); - else if (colorType == 'solid') fill = solidColor; - else if (bathyColorType != 'none') fill = '#fff'; // to mask out ocean if ocean is colored - } else { - if (bathyColorType == 'bathy') fill = bathyColor(c.value); - else if (bathyColorType == 'solid') fill = oceanColor; - } - path(c); - if (fill) { - contourContext.fillStyle = fill; - contourContext.fill(); - } - contourContext.stroke(); - }); - } - - // draw thicker index lines, if desired - if (majorInterval != 0) { - contourContext.lineWidth = lineWidthMajor; - contourContext.beginPath(); - contoursGeoData.forEach(function (c) { - if (c.value % majorInterval == 0) path(c); - }); - contourContext.stroke(); - } - - } - contourContext.restore(); - } else { - // draw contours to SVG for export - if (!contourSVG) { - contourSVG = d3.select('body').append('svg'); - } - contourSVG - .attr('width', width) - .attr('height', height) - .selectAll('path').remove(); - - contourSVG.selectAll('path.stroke') - .data(contoursGeoData) - .enter() - .append('path') - .attr('d', svgPath) - .attr('stroke', type == 'lines' ? lineColor : highlightColor) - .attr('stroke-width', function (d) { - return type == 'lines' ? (majorInterval != 0 && d.value % majorInterval == 0 ? lineWidthMajor : lineWidth) : shadowSize; - }) - .attr('fill', function (d) { - if (d.value >= 0 || bathyColorType == 'none') { - if (colorType == 'hypso') return hypsoColor(d.value); - else if (colorType == 'solid') return solidColor; - else if (bathyColorType != 'none') return '#fff'; // to mask out ocean if ocean is colored - } else { - if (bathyColorType == 'bathy') return bathyColor(d.value); - else if (bathyColorType == 'solid') return oceanColor; - } - return 'none'; - }) - .attr('id', function (d) { - return 'elev-' + d.value; - }); - } - d3.select('#loading').style('display', 'none'); -} - -function downloadGeoJson () { - var geojson = {type: 'FeatureCollection', features: []}; - contoursGeoData.forEach(function (c) { - var feature = {type:'Feature', properties:{elevation: c.value}, geometry: {type:c.type, coordinates:[]}}; - geojson.features.push(feature); - c.coordinates.forEach(function (poly) { - var polygon = []; - feature.geometry.coordinates.push(polygon); - poly.forEach(function (ring) { - var polyRing = []; - polygon.push(polyRing); - ring.forEach(function (coord) { - var ll = map.containerPointToLatLng(coord); - polyRing.push([ll.lng, ll.lat]); - }); - }); - }) - }); - download(JSON.stringify(geojson), 'countours.geojson'); -} - -function downloadPNG () { - var newCanvas = document.createElement('canvas'); - newCanvas.width = width - 2*buffer; - newCanvas.height = height - 2*buffer; - newCanvas.getContext('2d').putImageData(contourContext.getImageData(0,0,width,height), -buffer, -buffer) - // https://stackoverflow.com/questions/12796513/html5-canvas-to-png-file - var dt = newCanvas.toDataURL('image/png'); - /* Change MIME type to trick the browser to downlaod the file instead of displaying it */ - dt = dt.replace(/^data:image\/[^;]*/, 'data:application/octet-stream'); - - /* In addition to 's "download" attribute, you can define HTTP-style headers */ - dt = dt.replace(/^data:application\/octet-stream/, 'data:application/octet-stream;headers=Content-Disposition%3A%20attachment%3B%20filename=Canvas.png'); - - var tempLink = document.createElement('a'); - tempLink.style.display = 'none'; - - newCanvas.toBlob(function(blob) { - tempLink.href = window.URL.createObjectURL(blob); - tempLink.download = 'contours.png'; - if (typeof tempLink.download === 'undefined') { - tempLink.setAttribute('target', '_blank'); - } - document.body.appendChild(tempLink); - tempLink.click(); - document.body.removeChild(tempLink); - //var linkText = document.createTextNode(canvas.width + "px"); - //a.appendChild(linkText); - }, "image/png"); - - - // tempLink.href = dt; - // tempLink.setAttribute('download', 'contours.png'); - // if (typeof tempLink.download === 'undefined') { - // tempLink.setAttribute('target', '_blank'); - // } - - - - /* For later - works better for large images? -// https://stackoverflow.com/questions/36918075/is-it-possible-to-programmatically-detect-size-limit-for-data-url - -var makeButtonUsingBlob = function (canvas, a) { - canvas.toBlob(function(blob) { - a.href = window.URL.createObjectURL(blob); - a.download = "example.jpg"; - var linkText = document.createTextNode(canvas.width + "px"); - a.appendChild(linkText); - }, "image/jpeg", 0.7); -}; - -*/ -} - -function downloadSVG () { - drawContours(true); - var svgData = contourSVG.node().outerHTML; - download(svgData, 'contours.svg', 'image/svg+xml;charset=utf-8') -} - -// https://github.com/kennethjiang/js-file-download -function download(data, filename, mime) { - var blob = new Blob([data], {type: mime || 'application/octet-stream'}); - if (typeof window.navigator.msSaveBlob !== 'undefined') { - // IE workaround for "HTML7007: One or more blob URLs were - // revoked by closing the blob for which they were created. - // These URLs will no longer resolve as the data backing - // the URL has been freed." - window.navigator.msSaveBlob(blob, filename); - } - else { - var blobURL = window.URL.createObjectURL(blob); - var tempLink = document.createElement('a'); - tempLink.style.display = 'none'; - tempLink.href = blobURL; - tempLink.setAttribute('download', filename); - - // Safari thinks _blank anchor are pop ups. We only want to set _blank - // target if the browser does not support the HTML5 download attribute. - // This allows you to download files in desktop safari if pop up blocking - // is enabled. - if (typeof tempLink.download === 'undefined') { - tempLink.setAttribute('target', '_blank'); - } - - document.body.appendChild(tempLink); - tempLink.click(); - document.body.removeChild(tempLink); - window.URL.revokeObjectURL(blobURL); - } -} - -// convert elevation tile color to elevation value -function elev(index, demData) { - if (index < 0 || demData[index] === undefined) return undefined; - return (demData[index] * 256 + demData[index+1] + demData[index+2] / 256) - 32768; -} - -// helper to get imageData index for a given x/y -function getIndexForCoordinates(width, x,y) { - return width * y * 4 + 4 * x; -} - -function toRGBA (color) { - if (color.substr(0, 1) == '#') return hexToRGBA(color).replace(/\s/g, ''); - if (color.split(',').length == 4) return color.replace(/\s/g, ''); // already rgba - return 'rgba' + color.substring(3, color.indexOf(')')).replace(/\s/g, '') + ',1)'; -} - -function hexToRGBA (hex) { - var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); - return result ? 'rgba(' + parseInt(result[1], 16) + ',' + parseInt(result[2], 16) + ',' + parseInt(result[3], 16) + ',1)' : null; -} - -function toHex (rgba) { - if (rgba[0] == '#') { - var vals = rgba.slice(1); - if (vals.length == 3) { - return '#' + vals[0] + vals[0] + vals[1] + vals[1] + vals[2] + vals[2]; - } - return rgba; - } - var numbers = rgba.substr(rgba.indexOf('(') + 1, rgba.indexOf(')')).split(',').map(function (n) { return parseInt(n); }); - var r = numbers[0]; - var g = numbers[1]; - var b = numbers[2]; - return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1); -} \ No newline at end of file diff --git a/css/animation.css b/css/animation.css deleted file mode 100755 index ac5a956..0000000 --- a/css/animation.css +++ /dev/null @@ -1,85 +0,0 @@ -/* - Animation example, for spinners -*/ -.animate-spin { - -moz-animation: spin 2s infinite linear; - -o-animation: spin 2s infinite linear; - -webkit-animation: spin 2s infinite linear; - animation: spin 2s infinite linear; - display: inline-block; -} -@-moz-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@-webkit-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@-o-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@-ms-keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} -@keyframes spin { - 0% { - -moz-transform: rotate(0deg); - -o-transform: rotate(0deg); - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - - 100% { - -moz-transform: rotate(359deg); - -o-transform: rotate(359deg); - -webkit-transform: rotate(359deg); - transform: rotate(359deg); - } -} diff --git a/css/contours.css b/css/contours.css deleted file mode 100644 index 0a64040..0000000 --- a/css/contours.css +++ /dev/null @@ -1,581 +0,0 @@ -/* Contours — application chrome. - The map is the hero; everything here is quiet field-notebook olive around it. */ - -:root { - --ink: #26261f; - --ink-soft: #55543f; - --muted: #7d7b68; - --chrome: #585748; /* the original header olive, kept for continuity */ - --chrome-deep: #3f3e33; - --paper: #f5f5f0; - --surface: #ffffff; - --line: #dcdbd0; - --line-strong: #c6c5b6; - --accent: #8c7556; /* the default contour color, so the UI matches the map */ - --accent-ink: #5e4c35; - --accent-wash: #efe8df; - --danger: #a35a4a; - --focus: #2f6f9f; - - --topbar-h: 48px; - --rail-w: 48px; - --panel-w: 320px; - --ease: cubic-bezier(.2, .7, .2, 1); - --dur: .32s; - - --font: 'IBM Plex Sans', 'Helvetica Neue', Arial, sans-serif; -} - -* { box-sizing: border-box; } - -html, body { - height: 100%; - margin: 0; -} - -body { - font-family: var(--font); - font-size: 14px; - line-height: 1.45; - color: var(--ink); - background: var(--surface); - -webkit-font-smoothing: antialiased; - overflow: hidden; -} - -#wrapper { - position: relative; - height: 100%; -} - -#map { - position: absolute; - inset: 0; - z-index: 0; -} - -.leaflet-container { background: #fff; font-family: var(--font); } -canvas#contours { position: absolute; top: 0; left: 0; } - -/* ---- Top bar ------------------------------------------------------------ */ - -.topbar { - position: absolute; - top: 0; left: 0; right: 0; - height: var(--topbar-h); - z-index: 1200; - display: flex; - align-items: center; - gap: 16px; - padding: 0 14px 0 0; - background: var(--chrome); - color: #fff; - box-shadow: 0 1px 0 rgba(0,0,0,.18); -} - -/* The one flourish: the wordmark is drawn as outlined letterforms, like contour lines. - On phones it lives at the foot of the rail, reading upward; on wider screens it sits in the top bar. */ -.wordmark { - margin: 0; - color: transparent; - -webkit-text-stroke: 1px #fff; - font-weight: 600; - text-transform: uppercase; - user-select: none; -} -@media (max-width: 719px) { - .wordmark { - position: fixed; - left: 0; - bottom: 14px; - z-index: 1150; - width: var(--rail-w); - display: flex; - justify-content: center; - font-size: 14px; - letter-spacing: .12em; - writing-mode: vertical-rl; - transform: rotate(180deg); - pointer-events: none; - } - .topbar { padding-left: 12px; } -} -@media (min-width: 720px) { - .wordmark { - flex: 0 0 auto; - height: var(--topbar-h); - display: flex; - align-items: center; - padding: 0 18px; - font-size: 20px; - letter-spacing: .14em; - background: var(--chrome-deep); - } -} - -.credit { - margin-left: auto; - display: inline-flex; - align-items: center; - gap: 8px; - color: rgba(255,255,255,.82); - text-decoration: none; - font-size: 13px; - white-space: nowrap; -} -.credit img { width: 20px; height: 20px; border-radius: 4px; display: block; } -.credit:hover { color: #fff; } -@media (max-width: 719px) { .credit span { display: none; } } - -/* ---- Search ------------------------------------------------------------- */ - -#search { - position: relative; - flex: 1 1 auto; - max-width: 420px; -} -.search-icon { - position: absolute; - left: 10px; top: 50%; - width: 16px; height: 16px; - transform: translateY(-50%); - color: var(--muted); - pointer-events: none; -} -#search input { - width: 100%; - height: 32px; - padding: 0 12px 0 32px; - border: 1px solid transparent; - border-radius: 6px; - background: rgba(255,255,255,.92); - color: var(--ink); - font: inherit; - font-size: 14px; - outline: none; - -webkit-appearance: none; - appearance: none; -} -#search input::-webkit-search-cancel-button { -webkit-appearance: none; } -#search input::placeholder { color: var(--muted); } -#search input:focus { background: #fff; border-color: #fff; box-shadow: 0 0 0 3px rgba(255,255,255,.35); } - -#search-results { - display: none; - position: absolute; - top: calc(100% + 6px); - left: 0; right: 0; - padding: 4px 0; - background: var(--surface); - color: var(--ink); - border-radius: 8px; - box-shadow: 0 8px 28px rgba(38,38,31,.22), 0 0 0 1px rgba(38,38,31,.08); - overflow: hidden; -} -.search-result { - padding: 8px 14px; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - cursor: pointer; -} -.search-result:hover, .search-result.highlight { background: var(--accent-wash); } - -/* ---- Left rail + panels ------------------------------------------------- */ - -.rail { - position: absolute; - top: var(--topbar-h); - left: 0; - bottom: 0; - width: var(--rail-w); - z-index: 1100; - display: flex; - flex-direction: column; - background: var(--chrome); - padding-top: 8px; -} - -.toggle { - position: relative; - width: 100%; - height: 56px; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - gap: 3px; - padding: 0; - border: 0; - background: transparent; - color: rgba(255,255,255,.78); - font: inherit; - font-size: 10px; - letter-spacing: .02em; - cursor: pointer; -} -.toggle svg { width: 22px; height: 22px; } -.toggle:hover { color: #fff; background: rgba(255,255,255,.06); } -.toggle.show { - color: var(--accent-ink); - background: var(--paper); -} -/* notch that joins the active toggle to the open panel */ -.toggle.show::after { - content: ''; - position: absolute; - right: -1px; top: 0; bottom: 0; - width: 2px; - background: var(--paper); -} - -#panels { - position: absolute; - top: var(--topbar-h); - bottom: 0; - left: var(--rail-w); - width: var(--panel-w); - max-width: calc(100vw - var(--rail-w)); - z-index: 1000; - background: var(--paper); - border-right: 1px solid var(--line-strong); - box-shadow: 0 0 24px rgba(38,38,31,.14); - transform: translateX(calc(-100% - 30px)); - transition: transform var(--dur) var(--ease), visibility 0s linear var(--dur); - visibility: hidden; - overflow: hidden; -} -.panel-open #panels { - transform: none; - visibility: visible; - transition: transform var(--dur) var(--ease); -} - -.panel { - position: absolute; - inset: 0; - overflow-y: auto; - overscroll-behavior: contain; - padding: 0 0 28px; - background: var(--paper); - display: none; -} -.panel.show { display: block; } - -.panel-head { - position: sticky; - top: 0; - z-index: 2; - display: flex; - align-items: center; - justify-content: space-between; - height: 48px; - padding: 0 8px 0 20px; - background: var(--paper); - border-bottom: 1px solid var(--line); -} -.panel-head h2 { - margin: 0; - font-size: 15px; - font-weight: 600; - letter-spacing: .01em; -} -.panel-close { - width: 32px; height: 32px; - display: inline-flex; align-items: center; justify-content: center; - border: 0; border-radius: 6px; - background: transparent; - color: var(--muted); - cursor: pointer; -} -.panel-close svg { width: 18px; height: 18px; } -.panel-close:hover { color: var(--ink); background: rgba(38,38,31,.06); } - -/* ---- Settings content --------------------------------------------------- */ - -.settings-row { - padding: 18px 20px 6px; - border-bottom: 1px solid var(--line); -} -.settings-row:last-of-type { border-bottom: 0; } - -.settings-title { - margin: 0 0 10px; - font-size: 12px; - font-weight: 600; - color: var(--ink-soft); -} - -.settings-content p { margin: 0 0 10px; color: var(--ink-soft); } -.settings-content code { - font-family: 'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 12px; - background: rgba(38,38,31,.06); - padding: 1px 4px; - border-radius: 3px; -} - -.settings-section { - display: flex; - align-items: center; - gap: 10px; - min-height: 36px; - margin-bottom: 8px; -} -.settings-section > span { flex: 1 1 auto; } - -.row-icon { - flex: 0 0 auto; - width: 36px; height: 20px; - display: flex; align-items: center; -} -.row-icon img { max-width: 100%; max-height: 100%; display: block; opacity: .85; } - -/* text inputs */ -.settings-section input[type="text"], -.settings-section select { - height: 30px; - padding: 0 8px; - border: 1px solid var(--line-strong); - border-radius: 6px; - background: var(--surface); - color: var(--ink); - font: inherit; - font-size: 13px; - font-variant-numeric: tabular-nums; -} -.settings-section input[type="text"] { width: 52px; text-align: center; } -.settings-section input#interval-input { width: 64px; } -.settings-section input.color-input { width: 78px; text-align: left; text-transform: lowercase; } -.settings-section input:focus-visible, .settings-section select:focus-visible, -.choice input:focus-visible + span, .segmented input:focus-visible + span, -.switch input:focus-visible + .switch-track { - outline: 2px solid var(--focus); - outline-offset: 1px; -} -.settings-section input:disabled, .settings-section select:disabled { opacity: .45; } - -.with-unit { display: inline-flex; align-items: center; gap: 6px; } -.with-unit em { font-style: normal; color: var(--muted); font-size: 12px; } - -.settings-section select { - padding-right: 26px; - -webkit-appearance: none; - appearance: none; - background-image: url("data:image/svg+xml;utf8,"); - background-repeat: no-repeat; - background-position: right 9px center; -} - -/* color swatch buttons (jscolor attaches to these) */ -button[type="color"] { - flex: 0 0 auto; - width: 36px; height: 20px; - margin: 0; - padding: 0; - border: 1px solid rgba(38,38,31,.25); - border-radius: 4px; - box-shadow: inset 0 0 0 1px rgba(255,255,255,.6); - cursor: pointer; -} -button[type="color"]:focus-visible { outline: 2px solid var(--focus); outline-offset: 1px; } - -/* radio choices as a list */ -.choice-list { display: flex; flex-direction: column; } -.choice { display: block; margin: 0 0 2px; } -.choice input[type="radio"] { - position: absolute; - opacity: 0; - width: 0; height: 0; - margin: 0; -} -.choice input[type="radio"] + span { - position: relative; - display: block; - padding: 7px 10px 7px 26px; - border-radius: 6px; - color: var(--ink-soft); - cursor: pointer; - width: auto; -} -.choice input[type="radio"] + span::before { - content: ''; - position: absolute; - left: 8px; top: 50%; - width: 10px; height: 10px; - transform: translateY(-50%); - border-radius: 50%; - border: 1.5px solid var(--line-strong); - background: var(--surface); - transition: border-color .15s, box-shadow .15s; -} -.choice input[type="radio"] + span:hover { background: rgba(38,38,31,.045); } -.choice input[type="radio"]:checked + span { - color: var(--ink); - font-weight: 500; - background: var(--accent-wash); -} -.choice input[type="radio"]:checked + span::before { - border-color: var(--accent-ink); - box-shadow: inset 0 0 0 2.5px var(--surface), inset 0 0 0 8px var(--accent-ink); -} - -/* indented color rows under a radio choice */ -.color-section { margin: 2px 0 10px 26px; } - -/* segmented unit control */ -.segmented { - display: inline-flex; - border: 1px solid var(--line-strong); - border-radius: 6px; - overflow: hidden; - background: var(--surface); -} -.segmented label { display: block; } -.segmented input[type="radio"] { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; } -.segmented input[type="radio"] + span { - display: block; - min-width: 34px; - height: 28px; - line-height: 28px; - padding: 0 8px; - text-align: center; - font-size: 13px; - color: var(--ink-soft); - cursor: pointer; -} -.segmented label + label span { border-left: 1px solid var(--line-strong); } -.segmented input[type="radio"]:checked + span { - background: var(--chrome); - color: #fff; - font-weight: 500; -} - -/* labels toggle switch */ -.switch { - display: flex; - align-items: center; - gap: 10px; - padding: 14px 20px 12px; - border-bottom: 1px solid var(--line); - cursor: pointer; -} -.switch input { position: absolute; opacity: 0; width: 0; height: 0; margin: 0; } -.switch-track { - position: relative; - flex: 0 0 auto; - width: 32px; height: 18px; - border-radius: 9px; - background: var(--line-strong); - transition: background .15s; -} -.switch-track::after { - content: ''; - position: absolute; - top: 2px; left: 2px; - width: 14px; height: 14px; - border-radius: 50%; - background: #fff; - box-shadow: 0 1px 2px rgba(0,0,0,.25); - transition: transform .15s var(--ease); -} -.switch input:checked + .switch-track { background: var(--chrome); } -.switch input:checked + .switch-track::after { transform: translateX(14px); } - -div.disabled { display: none; } - -.note { - margin: 0; - padding: 12px 20px; - font-size: 13px; - color: var(--ink-soft); - background: var(--accent-wash); - border-bottom: 1px solid var(--line); -} -.caveat { - font-size: 12px; - color: var(--danger) !important; -} - -.about { - margin: 0; - padding: 16px 20px 0; - font-size: 12px; - color: var(--muted); -} -.about a { color: var(--accent-ink); } - -/* download buttons */ -.download-button { - display: inline-flex; - align-items: center; - gap: 8px; - height: 36px; - margin: 2px 0 14px; - padding: 0 14px; - border: 0; - border-radius: 6px; - background: var(--chrome); - color: #fff; - font: inherit; - font-size: 13px; - font-weight: 500; - cursor: pointer; - -webkit-appearance: none; - appearance: none; -} -.download-button svg { width: 16px; height: 16px; } -.download-button:hover { background: var(--chrome-deep); } -.download-button:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; } - -/* ---- Leaflet controls --------------------------------------------------- */ - -.leaflet-top { top: calc(var(--topbar-h) + 10px); } -.leaflet-bar { border: 0 !important; box-shadow: 0 2px 10px rgba(38,38,31,.18), 0 0 0 1px rgba(38,38,31,.08) !important; border-radius: 6px !important; overflow: hidden; } -.leaflet-bar a { color: var(--ink-soft) !important; border-bottom-color: var(--line) !important; } -.leaflet-bar a:hover { color: var(--ink) !important; background: var(--paper) !important; } -.leaflet-control-attribution { - font-family: var(--font); - font-size: 11px; - color: var(--muted) !important; - background: rgba(255,255,255,.85) !important; - padding: 2px 8px !important; - border-top-left-radius: 6px; -} -.leaflet-control-attribution a { color: var(--ink-soft) !important; } - -/* ---- Loading ------------------------------------------------------------ */ - -#loading { - position: absolute; - inset: var(--topbar-h) 0 0 var(--rail-w); - z-index: 900; /* dims the map only; the panel and top bar stay usable-looking */ - display: flex; - align-items: center; - justify-content: center; - background: rgba(245,245,240,.55); - color: var(--ink); -} -.loading-card { - display: inline-flex; - align-items: center; - gap: 10px; - padding: 10px 16px; - border-radius: 999px; - background: var(--surface); - box-shadow: 0 6px 24px rgba(38,38,31,.18), 0 0 0 1px rgba(38,38,31,.08); - font-size: 14px; -} -.loading-ring { - width: 16px; height: 16px; - border-radius: 50%; - border: 2px solid var(--line-strong); - border-top-color: var(--accent-ink); - animation: spin .8s linear infinite; -} -@keyframes spin { to { transform: rotate(360deg); } } - -@media (prefers-reduced-motion: reduce) { - #panels, .toggle, .switch-track, .switch-track::after, .choice span::before { transition: none !important; } - .loading-ring { animation: none; border-top-color: var(--line-strong); } -} diff --git a/css/fontello-codes.css b/css/fontello-codes.css deleted file mode 100755 index aa1a997..0000000 --- a/css/fontello-codes.css +++ /dev/null @@ -1,5 +0,0 @@ - -.icon-palette:before { content: '\e806'; } /* '' */ -.icon-download:before { content: '\e80c'; } /* '' */ -.icon-cancel:before { content: '\e812'; } /* '' */ -.icon-spinner:before { content: '\f110'; } /* '' */ \ No newline at end of file diff --git a/css/fontello-embedded.css b/css/fontello-embedded.css deleted file mode 100755 index ceff0ba..0000000 --- a/css/fontello-embedded.css +++ /dev/null @@ -1,58 +0,0 @@ -@font-face { - font-family: 'fontello'; - src: url('../font/fontello.eot?76138226'); - src: url('../font/fontello.eot?76138226#iefix') format('embedded-opentype'), - url('../font/fontello.svg?76138226#fontello') format('svg'); - font-weight: normal; - font-style: normal; -} -@font-face { - font-family: 'fontello'; - src: url('data:application/octet-stream;base64,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') format('woff'), - url('data:application/octet-stream;base64,AAEAAAAPAIAAAwBwR1NVQiCLJXoAAAD8AAAAVE9TLzI+JlJRAAABUAAAAFZjbWFwBSGlZQAAAagAAAGyY3Z0IAbV/wQAAAzMAAAAIGZwZ22KkZBZAAAM7AAAC3BnYXNwAAAAEAAADMQAAAAIZ2x5ZoYoFl4AAANcAAAFqmhlYWQQ5QzRAAAJCAAAADZoaGVhB0MDaQAACUAAAAAkaG10eBJ7AAAAAAlkAAAAFGxvY2EDlgS9AAAJeAAAAAxtYXhwAQYMdAAACYQAAAAgbmFtZcydHiAAAAmkAAACzXBvc3RVRQRcAAAMdAAAAE9wcmVw5UErvAAAGFwAAACGAAEAAAAKADAAPgACREZMVAAObGF0bgAaAAQAAAAAAAAAAQAAAAQAAAAAAAAAAQAAAAFsaWdhAAgAAAABAAAAAQAEAAQAAAABAAgAAQAGAAAAAQAAAAEDsgGQAAUAAAJ6ArwAAACMAnoCvAAAAeAAMQECAAACAAUDAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFBmRWQAQOgG8RADUv9qAFoDUgCWAAAAAQAAAAAAAAAAAAUAAAADAAAALAAAAAQAAAFyAAEAAAAAAGwAAwABAAAALAADAAoAAAFyAAQAQAAAAAoACAACAALoBugM6BLxEP//AADoBugM6BLxEP//AAAAAAAAAAAAAQAKAAoACgAKAAAAAQACAAMABAAAAQYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADAAAAAAAQAAAAAAAAAAEAADoBgAA6AYAAAABAADoDAAA6AwAAAACAADoEgAA6BIAAAADAADxEAAA8RAAAAAEAAAACAAA/3QD7gM3AFoAYwBsAHUAfgCHALgA1QAsQCmfMAIBAsMBAwECRwACAQJvAAEDAW8AAwADbwAAAGbHxIqJSkkWEQQFFCsBBgcGDwIGBwYHFRYHBgcOAQ8BIicmJyYnJjc2Nz4BPwE2NzY3Nhc2NzY3Nj8BNjcuAQcGBw4BBwYeARcWNz4BPwE2NzY3NicmDwEGJyY3Njc2PwE2NzYnFiMlNh4BDgEuATYHNh4BDgEuATYDJj4BHgEOASYXBi4BPgEeAQYzBi4BPgEeAQYBJiIHBg8BBg8BBg8BBg8BBg8BBgcGDwEWHwEWFzI3Njc2PwE2PwE2NzY/ATY3MTYmASYHBgcGBwYHBgceATc+ATc2NzYvASYvASYnJiMDNA0aIj0EAxMcFBgKCgskIlYhIRIgJw4JAwQKHBkcFwwECwgmNA8MAgQLDA4abBMVQqVVWExTbRERNYNZW2FNkDkFGgsUBAUXFB4KKBEeBAIVDB0OPSYfLQEB/qYdMQshOTELIMUdMQshOjEKIJkGIToxCyE6Mb4dMQshOjEKIO0dMQogOjELIQG3DBkLNksDDQYOEwkDCwUBChJ6FRENCAYXGAQHAgIBGBEXFHQRCRIPFwUIB0AwCgL+BgoJKiAMBw4PEzwWQRYcTh8hCgYHBAIFBBQRBAQCGw8cJUAEBBYTDgkBGRshIh8tAgECAgQDBwkHERMVGx4JGgw6CAIDCQkbERcYZxMSJg8YGTc8qVpco28SEhgTWD0FGg4ZFBsNDAIBBAQGHhMXDRYMMzwvQQFkBiE6MQshOjFrBSE6MQohOTH+0h0xCyE6MQsh+wYhOjELIToxBSE6MQohOTEDXwoKLUQCDAYNEQkCCgUBCRF1EhoTFhAHFQQGBAEKDBAWexMJFBAbBQoISDoNGv5GAQEGMhIUJBEYKAIBAQEpHB8cFREHAwUEEAcCAAQAAP/5A6EDUgAIABEAJwA/AERAQTwBBwgJAAICAAJHCQEHCAMIBwNtAAYDBAMGBG0FAQMBAQACAwBgAAQAAgQCXAAICAwIST89JCUWIhIlORgSCgUdKyU0LgEOARY+ATc0LgEOARY+ATcVFAYHISImJzU0NjMhFxYyPwEhMhYDFg8BBiIvASY3NjsBNTQ2NzMyFgcVMzICyhQeFAIYGhiNFCASAhYcGEYgFvzLFx4BIBYBA0shViFMAQMWILYKEvoKHgr6EQkKF48WDo8OFgGPGGQPFAIYGhgCFA8PFAIYGhgCFIyzFh4BIBWzFiBMICBMIAEoFxD6Cwv6EBcV+g8UARYO+gAAAAEAAP/vAtQChgAkAB5AGyIZEAcEAAIBRwMBAgACbwEBAABmFBwUFAQFGCslFA8BBiIvAQcGIi8BJjQ/AScmND8BNjIfATc2Mh8BFhQPARcWAtQPTBAsEKSkECwQTBAQpKQQEEwQLBCkpBAsEEwPD6SkD3AWEEwPD6WlDw9MECwQpKQQLBBMEBCkpBAQTA8uD6SkDwAIAAD/jgPEA1IACAARABoAIwAsADUAPgBHAFhAVRsBAwEJAQIAAkcJAQQMAQwEAW0ACAAHDAgHYAANAAwEDQxgBgEBBQEAAgEAYAADAAIDAlwACgoLWAALCwwKSUZFQkE9PDk4MC8TFBMYFBMUExIOBR0rJRQGIiY0NjIWBRQGIi4BNh4BARQOAS4BNh4BARQGIiY+AR4BARQGIiY0NjIWARQOASY+AR4BARQGIiY0NjIWBRQOAS4BNjIWASYqOyoqOiwBFCg+JgQuNjD+dCo8KAIsOC4CnCo7KgImQCT96TRKNDRKNAKNKjosAig+Jv6dPlo+Plo+AShKZ0gBSmZKSB0qKjsqKpEdKio6LAIoAWoeKAIsOC4GIv7IHSoqOiwCKAINJTQ0SjQ0/sUeKAIsOC4GIgFnLT4+Wj4+oDRIAUpmSkoAAAAAAQAAAAEAAFj6TyZfDzz1AAsD6AAAAADW6+STAAAAANbr5JMAAP90A+4DUgAAAAgAAgAAAAAAAAABAAADUv9qAAAD+gAA//8D7gABAAAAAAAAAAAAAAAAAAAABQPoAAAD+gAAA6AAAAMRAAAD6AAAAAAAAAFkAegCMgLVAAEAAAAFANYACAAAAAAAAgAcACwAcwAAAGkLcAAAAAAAAAASAN4AAQAAAAAAAAA1AAAAAQAAAAAAAQAIADUAAQAAAAAAAgAHAD0AAQAAAAAAAwAIAEQAAQAAAAAABAAIAEwAAQAAAAAABQALAFQAAQAAAAAABgAIAF8AAQAAAAAACgArAGcAAQAAAAAACwATAJIAAwABBAkAAABqAKUAAwABBAkAAQAQAQ8AAwABBAkAAgAOAR8AAwABBAkAAwAQAS0AAwABBAkABAAQAT0AAwABBAkABQAWAU0AAwABBAkABgAQAWMAAwABBAkACgBWAXMAAwABBAkACwAmAclDb3B5cmlnaHQgKEMpIDIwMTggYnkgb3JpZ2luYWwgYXV0aG9ycyBAIGZvbnRlbGxvLmNvbWZvbnRlbGxvUmVndWxhcmZvbnRlbGxvZm9udGVsbG9WZXJzaW9uIDEuMGZvbnRlbGxvR2VuZXJhdGVkIGJ5IHN2ZzJ0dGYgZnJvbSBGb250ZWxsbyBwcm9qZWN0Lmh0dHA6Ly9mb250ZWxsby5jb20AQwBvAHAAeQByAGkAZwBoAHQAIAAoAEMAKQAgADIAMAAxADgAIABiAHkAIABvAHIAaQBnAGkAbgBhAGwAIABhAHUAdABoAG8AcgBzACAAQAAgAGYAbwBuAHQAZQBsAGwAbwAuAGMAbwBtAGYAbwBuAHQAZQBsAGwAbwBSAGUAZwB1AGwAYQByAGYAbwBuAHQAZQBsAGwAbwBmAG8AbgB0AGUAbABsAG8AVgBlAHIAcwBpAG8AbgAgADEALgAwAGYAbwBuAHQAZQBsAGwAbwBHAGUAbgBlAHIAYQB0AGUAZAAgAGIAeQAgAHMAdgBnADIAdAB0AGYAIABmAHIAbwBtACAARgBvAG4AdABlAGwAbABvACAAcAByAG8AagBlAGMAdAAuAGgAdAB0AHAAOgAvAC8AZgBvAG4AdABlAGwAbABvAC4AYwBvAG0AAAAAAgAAAAAAAAAKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFAQIBAwEEAQUBBgAHcGFsZXR0ZQhkb3dubG9hZAZjYW5jZWwHc3Bpbm5lcgAAAAABAAH//wAPAAAAAAAAAAAAAAAAAAAAAAAYABgAGAAYA1L/agNS/2qwACwgsABVWEVZICBLuAAOUUuwBlNaWLA0G7AoWWBmIIpVWLACJWG5CAAIAGNjI2IbISGwAFmwAEMjRLIAAQBDYEItsAEssCBgZi2wAiwgZCCwwFCwBCZasigBCkNFY0VSW1ghIyEbilggsFBQWCGwQFkbILA4UFghsDhZWSCxAQpDRWNFYWSwKFBYIbEBCkNFY0UgsDBQWCGwMFkbILDAUFggZiCKimEgsApQWGAbILAgUFghsApgGyCwNlBYIbA2YBtgWVlZG7ABK1lZI7AAUFhlWVktsAMsIEUgsAQlYWQgsAVDUFiwBSNCsAYjQhshIVmwAWAtsAQsIyEjISBksQViQiCwBiNCsQEKQ0VjsQEKQ7ABYEVjsAMqISCwBkMgiiCKsAErsTAFJbAEJlFYYFAbYVJZWCNZISCwQFNYsAErGyGwQFkjsABQWGVZLbAFLLAHQyuyAAIAQ2BCLbAGLLAHI0IjILAAI0JhsAJiZrABY7ABYLAFKi2wBywgIEUgsAtDY7gEAGIgsABQWLBAYFlmsAFjYESwAWAtsAgssgcLAENFQiohsgABAENgQi2wCSywAEMjRLIAAQBDYEItsAosICBFILABKyOwAEOwBCVgIEWKI2EgZCCwIFBYIbAAG7AwUFiwIBuwQFlZI7AAUFhlWbADJSNhRESwAWAtsAssICBFILABKyOwAEOwBCVgIEWKI2EgZLAkUFiwABuwQFkjsABQWGVZsAMlI2FERLABYC2wDCwgsAAjQrILCgNFWCEbIyFZKiEtsA0ssQICRbBkYUQtsA4ssAFgICCwDENKsABQWCCwDCNCWbANQ0qwAFJYILANI0JZLbAPLCCwEGJmsAFjILgEAGOKI2GwDkNgIIpgILAOI0IjLbAQLEtUWLEEZERZJLANZSN4LbARLEtRWEtTWLEEZERZGyFZJLATZSN4LbASLLEAD0NVWLEPD0OwAWFCsA8rWbAAQ7ACJUKxDAIlQrENAiVCsAEWIyCwAyVQWLEBAENgsAQlQoqKIIojYbAOKiEjsAFhIIojYbAOKiEbsQEAQ2CwAiVCsAIlYbAOKiFZsAxDR7ANQ0dgsAJiILAAUFiwQGBZZrABYyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsQAAEyNEsAFDsAA+sgEBAUNgQi2wEywAsQACRVRYsA8jQiBFsAsjQrAKI7ABYEIgYLABYbUQEAEADgBCQopgsRIGK7ByKxsiWS2wFCyxABMrLbAVLLEBEystsBYssQITKy2wFyyxAxMrLbAYLLEEEystsBkssQUTKy2wGiyxBhMrLbAbLLEHEystsBwssQgTKy2wHSyxCRMrLbAeLACwDSuxAAJFVFiwDyNCIEWwCyNCsAojsAFgQiBgsAFhtRAQAQAOAEJCimCxEgYrsHIrGyJZLbAfLLEAHistsCAssQEeKy2wISyxAh4rLbAiLLEDHistsCMssQQeKy2wJCyxBR4rLbAlLLEGHistsCYssQceKy2wJyyxCB4rLbAoLLEJHistsCksIDywAWAtsCosIGCwEGAgQyOwAWBDsAIlYbABYLApKiEtsCsssCorsCoqLbAsLCAgRyAgsAtDY7gEAGIgsABQWLBAYFlmsAFjYCNhOCMgilVYIEcgILALQ2O4BABiILAAUFiwQGBZZrABY2AjYTgbIVktsC0sALEAAkVUWLABFrAsKrABFTAbIlktsC4sALANK7EAAkVUWLABFrAsKrABFTAbIlktsC8sIDWwAWAtsDAsALABRWO4BABiILAAUFiwQGBZZrABY7ABK7ALQ2O4BABiILAAUFiwQGBZZrABY7ABK7AAFrQAAAAAAEQ+IzixLwEVKi2wMSwgPCBHILALQ2O4BABiILAAUFiwQGBZZrABY2CwAENhOC2wMiwuFzwtsDMsIDwgRyCwC0NjuAQAYiCwAFBYsEBgWWawAWNgsABDYbABQ2M4LbA0LLECABYlIC4gR7AAI0KwAiVJiopHI0cjYSBYYhshWbABI0KyMwEBFRQqLbA1LLAAFrAEJbAEJUcjRyNhsAlDK2WKLiMgIDyKOC2wNiywABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyCwCEMgiiNHI0cjYSNGYLAEQ7ACYiCwAFBYsEBgWWawAWNgILABKyCKimEgsAJDYGQjsANDYWRQWLACQ2EbsANDYFmwAyWwAmIgsABQWLBAYFlmsAFjYSMgILAEJiNGYTgbI7AIQ0awAiWwCENHI0cjYWAgsARDsAJiILAAUFiwQGBZZrABY2AjILABKyOwBENgsAErsAUlYbAFJbACYiCwAFBYsEBgWWawAWOwBCZhILAEJWBkI7ADJWBkUFghGyMhWSMgILAEJiNGYThZLbA3LLAAFiAgILAFJiAuRyNHI2EjPDgtsDgssAAWILAII0IgICBGI0ewASsjYTgtsDkssAAWsAMlsAIlRyNHI2GwAFRYLiA8IyEbsAIlsAIlRyNHI2EgsAUlsAQlRyNHI2GwBiWwBSVJsAIlYbkIAAgAY2MjIFhiGyFZY7gEAGIgsABQWLBAYFlmsAFjYCMuIyAgPIo4IyFZLbA6LLAAFiCwCEMgLkcjRyNhIGCwIGBmsAJiILAAUFiwQGBZZrABYyMgIDyKOC2wOywjIC5GsAIlRlJYIDxZLrErARQrLbA8LCMgLkawAiVGUFggPFkusSsBFCstsD0sIyAuRrACJUZSWCA8WSMgLkawAiVGUFggPFkusSsBFCstsD4ssDUrIyAuRrACJUZSWCA8WS6xKwEUKy2wPyywNiuKICA8sAQjQoo4IyAuRrACJUZSWCA8WS6xKwEUK7AEQy6wKystsEAssAAWsAQlsAQmIC5HI0cjYbAJQysjIDwgLiM4sSsBFCstsEEssQgEJUKwABawBCWwBCUgLkcjRyNhILAEI0KwCUMrILBgUFggsEBRWLMCIAMgG7MCJgMaWUJCIyBHsARDsAJiILAAUFiwQGBZZrABY2AgsAErIIqKYSCwAkNgZCOwA0NhZFBYsAJDYRuwA0NgWbADJbACYiCwAFBYsEBgWWawAWNhsAIlRmE4IyA8IzgbISAgRiNHsAErI2E4IVmxKwEUKy2wQiywNSsusSsBFCstsEMssDYrISMgIDywBCNCIzixKwEUK7AEQy6wKystsEQssAAVIEewACNCsgABARUUEy6wMSotsEUssAAVIEewACNCsgABARUUEy6wMSotsEYssQABFBOwMiotsEcssDQqLbBILLAAFkUjIC4gRoojYTixKwEUKy2wSSywCCNCsEgrLbBKLLIAAEErLbBLLLIAAUErLbBMLLIBAEErLbBNLLIBAUErLbBOLLIAAEIrLbBPLLIAAUIrLbBQLLIBAEIrLbBRLLIBAUIrLbBSLLIAAD4rLbBTLLIAAT4rLbBULLIBAD4rLbBVLLIBAT4rLbBWLLIAAEArLbBXLLIAAUArLbBYLLIBAEArLbBZLLIBAUArLbBaLLIAAEMrLbBbLLIAAUMrLbBcLLIBAEMrLbBdLLIBAUMrLbBeLLIAAD8rLbBfLLIAAT8rLbBgLLIBAD8rLbBhLLIBAT8rLbBiLLA3Ky6xKwEUKy2wYyywNyuwOystsGQssDcrsDwrLbBlLLAAFrA3K7A9Ky2wZiywOCsusSsBFCstsGcssDgrsDsrLbBoLLA4K7A8Ky2waSywOCuwPSstsGossDkrLrErARQrLbBrLLA5K7A7Ky2wbCywOSuwPCstsG0ssDkrsD0rLbBuLLA6Ky6xKwEUKy2wbyywOiuwOystsHAssDorsDwrLbBxLLA6K7A9Ky2wciyzCQQCA0VYIRsjIVlCK7AIZbADJFB4sAEVMC0AS7gAyFJYsQEBjlmwAbkIAAgAY3CxAAVCsgABACqxAAVCswoCAQgqsQAFQrMOAAEIKrEABkK6AsAAAQAJKrEAB0K6AEAAAQAJKrEDAESxJAGIUViwQIhYsQNkRLEmAYhRWLoIgAABBECIY1RYsQMARFlZWVmzDAIBDCq4Af+FsASNsQIARAAA') format('truetype'); -} -/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ -/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ -/* -@media screen and (-webkit-min-device-pixel-ratio:0) { - @font-face { - font-family: 'fontello'; - src: url('../font/fontello.svg?76138226#fontello') format('svg'); - } -} -*/ - - [class^="icon-"]:before, [class*=" icon-"]:before { - font-family: "fontello"; - font-style: normal; - font-weight: normal; - speak: none; - - display: inline-block; - text-decoration: inherit; - width: 1em; - margin-right: .2em; - text-align: center; - /* opacity: .8; */ - - /* For safety - reset parent styles, that can break glyph codes*/ - font-variant: normal; - text-transform: none; - - /* fix buttons height, for twitter bootstrap */ - line-height: 1em; - - /* Animation center compensation - margins should be symmetric */ - /* remove if not needed */ - margin-left: .2em; - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ -} -.icon-palette:before { content: '\e806'; } /* '' */ -.icon-download:before { content: '\e80c'; } /* '' */ -.icon-cancel:before { content: '\e812'; } /* '' */ -.icon-spinner:before { content: '\f110'; } /* '' */ \ No newline at end of file diff --git a/css/fontello-ie7-codes.css b/css/fontello-ie7-codes.css deleted file mode 100755 index 3e120e3..0000000 --- a/css/fontello-ie7-codes.css +++ /dev/null @@ -1,5 +0,0 @@ - -.icon-palette { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-download { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-spinner { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/css/fontello-ie7.css b/css/fontello-ie7.css deleted file mode 100755 index 3059035..0000000 --- a/css/fontello-ie7.css +++ /dev/null @@ -1,16 +0,0 @@ -[class^="icon-"], [class*=" icon-"] { - font-family: 'fontello'; - font-style: normal; - font-weight: normal; - - /* fix buttons height */ - line-height: 1em; - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ -} - -.icon-palette { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-download { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-cancel { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } -.icon-spinner { *zoom: expression( this.runtimeStyle['zoom'] = '1', this.innerHTML = ' '); } \ No newline at end of file diff --git a/css/fontello.css b/css/fontello.css deleted file mode 100755 index b3c42b6..0000000 --- a/css/fontello.css +++ /dev/null @@ -1,58 +0,0 @@ -@font-face { - font-family: 'fontello'; - src: url('../font/fontello.eot?91040647'); - src: url('../font/fontello.eot?91040647#iefix') format('embedded-opentype'), - url('../font/fontello.woff2?91040647') format('woff2'), - url('../font/fontello.woff?91040647') format('woff'), - url('../font/fontello.ttf?91040647') format('truetype'), - url('../font/fontello.svg?91040647#fontello') format('svg'); - font-weight: normal; - font-style: normal; -} -/* Chrome hack: SVG is rendered more smooth in Windozze. 100% magic, uncomment if you need it. */ -/* Note, that will break hinting! In other OS-es font will be not as sharp as it could be */ -/* -@media screen and (-webkit-min-device-pixel-ratio:0) { - @font-face { - font-family: 'fontello'; - src: url('../font/fontello.svg?91040647#fontello') format('svg'); - } -} -*/ - - [class^="icon-"]:before, [class*=" icon-"]:before { - font-family: "fontello"; - font-style: normal; - font-weight: normal; - speak: none; - - display: inline-block; - text-decoration: inherit; - text-align: center; - /* opacity: .8; */ - - /* For safety - reset parent styles, that can break glyph codes*/ - font-variant: normal; - text-transform: none; - - /* fix buttons height, for twitter bootstrap */ - line-height: 1em; - - - /* you can be more comfortable with increased icons size */ - /* font-size: 120%; */ - - /* Font smoothing. That was taken from TWBS */ - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - - /* Uncomment for 3D effect */ - /* text-shadow: 1px 1px 1px rgba(127, 127, 127, 0.3); */ -} - -.icon-palette:before { content: '\e806'; } /* '' */ -.icon-left-open:before { content: '\e80a'; } /* '' */ -.icon-download:before { content: '\e80c'; } /* '' */ -.icon-cancel:before { content: '\e812'; } /* '' */ -.icon-spinner:before { content: '\f110'; } /* '' */ -.icon-brush:before { content: '\f1fc'; } /* '' */ \ No newline at end of file diff --git a/download.png b/download.png deleted file mode 100644 index 426f371455157a12c883e4d6469d2dc588fb4713..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3488 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 diff --git a/font/fontello.svg b/font/fontello.svg deleted file mode 100755 index 6a902f4..0000000 --- a/font/fontello.svg +++ /dev/null @@ -1,22 +0,0 @@ - - - -Copyright (C) 2018 by original authors @ fontello.com - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/font/fontello.ttf b/font/fontello.ttf deleted file mode 100755 index 7f621b6297e17b4103a0e0449f1ab0c5d3e9aedb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6764 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 diff --git a/font/fontello.woff b/font/fontello.woff deleted file mode 100755 index 088c430dac9ddfa63e94fdfae9c6c517e0c40bb5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4112 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 diff --git a/font/fontello.woff2 b/font/fontello.woff2 deleted file mode 100755 index 08e44963e8a28d69b8f9dcdc147678fb673ffbb3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3444 zcmV-)4U6)3Pew8T0RR9101b2i4*&oF02*un01X`g0RR9100000000000000000000 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 diff --git a/index.html b/index.html deleted file mode 100644 index 627d616..0000000 --- a/index.html +++ /dev/null @@ -1,221 +0,0 @@ - - - - - - Contours - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-
Drawing contours…
-
- - - - diff --git a/jscolor.js b/jscolor.js deleted file mode 100644 index ebbd047..0000000 --- a/jscolor.js +++ /dev/null @@ -1,1844 +0,0 @@ -/** - * jscolor - JavaScript Color Picker - * - * @link http://jscolor.com - * @license For open source use: GPLv3 - * For commercial use: JSColor Commercial License - * @author Jan Odvarko - * @version 2.0.4 - * - * See usage examples at http://jscolor.com/examples/ - */ - - -"use strict"; - - -if (!window.jscolor) { window.jscolor = (function () { - - -var jsc = { - - - register : function () { - jsc.attachDOMReadyEvent(jsc.init); - jsc.attachEvent(document, 'mousedown', jsc.onDocumentMouseDown); - jsc.attachEvent(document, 'touchstart', jsc.onDocumentTouchStart); - jsc.attachEvent(window, 'resize', jsc.onWindowResize); - }, - - - init : function () { - if (jsc.jscolor.lookupClass) { - jsc.jscolor.installByClassName(jsc.jscolor.lookupClass); - } - }, - - - tryInstallOnElements : function (elms, className) { - var matchClass = new RegExp('(^|\\s)(' + className + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i'); - - for (var i = 0; i < elms.length; i += 1) { - if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color') { - if (jsc.isColorAttrSupported) { - // skip inputs of type 'color' if supported by the browser - continue; - } - } - var m; - if (!elms[i].jscolor && elms[i].className && (m = elms[i].className.match(matchClass))) { - var targetElm = elms[i]; - var optsStr = null; - - var dataOptions = jsc.getDataAttr(targetElm, 'jscolor'); - if (dataOptions !== null) { - optsStr = dataOptions; - } else if (m[4]) { - optsStr = m[4]; - } - - var opts = {}; - if (optsStr) { - try { - opts = (new Function ('return (' + optsStr + ')'))(); - } catch(eParseError) { - jsc.warn('Error parsing jscolor options: ' + eParseError + ':\n' + optsStr); - } - } - targetElm.jscolor = new jsc.jscolor(targetElm, opts); - } - } - }, - - - isColorAttrSupported : (function () { - var elm = document.createElement('input'); - if (elm.setAttribute) { - elm.setAttribute('type', 'color'); - if (elm.type.toLowerCase() == 'color') { - return true; - } - } - return false; - })(), - - - isCanvasSupported : (function () { - var elm = document.createElement('canvas'); - return !!(elm.getContext && elm.getContext('2d')); - })(), - - - fetchElement : function (mixed) { - return typeof mixed === 'string' ? document.getElementById(mixed) : mixed; - }, - - - isElementType : function (elm, type) { - return elm.nodeName.toLowerCase() === type.toLowerCase(); - }, - - - getDataAttr : function (el, name) { - var attrName = 'data-' + name; - var attrValue = el.getAttribute(attrName); - if (attrValue !== null) { - return attrValue; - } - return null; - }, - - - attachEvent : function (el, evnt, func) { - if (el.addEventListener) { - el.addEventListener(evnt, func, false); - } else if (el.attachEvent) { - el.attachEvent('on' + evnt, func); - } - }, - - - detachEvent : function (el, evnt, func) { - if (el.removeEventListener) { - el.removeEventListener(evnt, func, false); - } else if (el.detachEvent) { - el.detachEvent('on' + evnt, func); - } - }, - - - _attachedGroupEvents : {}, - - - attachGroupEvent : function (groupName, el, evnt, func) { - if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) { - jsc._attachedGroupEvents[groupName] = []; - } - jsc._attachedGroupEvents[groupName].push([el, evnt, func]); - jsc.attachEvent(el, evnt, func); - }, - - - detachGroupEvents : function (groupName) { - if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) { - for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) { - var evt = jsc._attachedGroupEvents[groupName][i]; - jsc.detachEvent(evt[0], evt[1], evt[2]); - } - delete jsc._attachedGroupEvents[groupName]; - } - }, - - - attachDOMReadyEvent : function (func) { - var fired = false; - var fireOnce = function () { - if (!fired) { - fired = true; - func(); - } - }; - - if (document.readyState === 'complete') { - setTimeout(fireOnce, 1); // async - return; - } - - if (document.addEventListener) { - document.addEventListener('DOMContentLoaded', fireOnce, false); - - // Fallback - window.addEventListener('load', fireOnce, false); - - } else if (document.attachEvent) { - // IE - document.attachEvent('onreadystatechange', function () { - if (document.readyState === 'complete') { - document.detachEvent('onreadystatechange', arguments.callee); - fireOnce(); - } - }) - - // Fallback - window.attachEvent('onload', fireOnce); - - // IE7/8 - if (document.documentElement.doScroll && window == window.top) { - var tryScroll = function () { - if (!document.body) { return; } - try { - document.documentElement.doScroll('left'); - fireOnce(); - } catch (e) { - setTimeout(tryScroll, 1); - } - }; - tryScroll(); - } - } - }, - - - warn : function (msg) { - if (window.console && window.console.warn) { - window.console.warn(msg); - } - }, - - - preventDefault : function (e) { - if (e.preventDefault) { e.preventDefault(); } - e.returnValue = false; - }, - - - captureTarget : function (target) { - // IE - if (target.setCapture) { - jsc._capturedTarget = target; - jsc._capturedTarget.setCapture(); - } - }, - - - releaseTarget : function () { - // IE - if (jsc._capturedTarget) { - jsc._capturedTarget.releaseCapture(); - jsc._capturedTarget = null; - } - }, - - - fireEvent : function (el, evnt) { - if (!el) { - return; - } - if (document.createEvent) { - var ev = document.createEvent('HTMLEvents'); - ev.initEvent(evnt, true, true); - el.dispatchEvent(ev); - } else if (document.createEventObject) { - var ev = document.createEventObject(); - el.fireEvent('on' + evnt, ev); - } else if (el['on' + evnt]) { // alternatively use the traditional event model - el['on' + evnt](); - } - }, - - - classNameToList : function (className) { - return className.replace(/^\s+|\s+$/g, '').split(/\s+/); - }, - - - // The className parameter (str) can only contain a single class name - hasClass : function (elm, className) { - if (!className) { - return false; - } - return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' '); - }, - - - // The className parameter (str) can contain multiple class names separated by whitespace - setClass : function (elm, className) { - var classList = jsc.classNameToList(className); - for (var i = 0; i < classList.length; i += 1) { - if (!jsc.hasClass(elm, classList[i])) { - elm.className += (elm.className ? ' ' : '') + classList[i]; - } - } - }, - - - // The className parameter (str) can contain multiple class names separated by whitespace - unsetClass : function (elm, className) { - var classList = jsc.classNameToList(className); - for (var i = 0; i < classList.length; i += 1) { - var repl = new RegExp( - '^\\s*' + classList[i] + '\\s*|' + - '\\s*' + classList[i] + '\\s*$|' + - '\\s+' + classList[i] + '(\\s+)', - 'g' - ); - elm.className = elm.className.replace(repl, '$1'); - } - }, - - - getStyle : function (elm) { - return window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle; - }, - - - setStyle : (function () { - var helper = document.createElement('div'); - var getSupportedProp = function (names) { - for (var i = 0; i < names.length; i += 1) { - if (names[i] in helper.style) { - return names[i]; - } - } - }; - var props = { - borderRadius: getSupportedProp(['borderRadius', 'MozBorderRadius', 'webkitBorderRadius']), - boxShadow: getSupportedProp(['boxShadow', 'MozBoxShadow', 'webkitBoxShadow']) - }; - return function (elm, prop, value) { - switch (prop.toLowerCase()) { - case 'opacity': - var alphaOpacity = Math.round(parseFloat(value) * 100); - elm.style.opacity = value; - elm.style.filter = 'alpha(opacity=' + alphaOpacity + ')'; - break; - default: - elm.style[props[prop]] = value; - break; - } - }; - })(), - - - setBorderRadius : function (elm, value) { - jsc.setStyle(elm, 'borderRadius', value || '0'); - }, - - - setBoxShadow : function (elm, value) { - jsc.setStyle(elm, 'boxShadow', value || 'none'); - }, - - - getElementPos : function (e, relativeToViewport) { - var x=0, y=0; - var rect = e.getBoundingClientRect(); - x = rect.left; - y = rect.top; - if (!relativeToViewport) { - var viewPos = jsc.getViewPos(); - x += viewPos[0]; - y += viewPos[1]; - } - return [x, y]; - }, - - - getElementSize : function (e) { - return [e.offsetWidth, e.offsetHeight]; - }, - - - // get pointer's X/Y coordinates relative to viewport - getAbsPointerPos : function (e) { - if (!e) { e = window.event; } - var x = 0, y = 0; - if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { - // touch devices - x = e.changedTouches[0].clientX; - y = e.changedTouches[0].clientY; - } else if (typeof e.clientX === 'number') { - x = e.clientX; - y = e.clientY; - } - return { x: x, y: y }; - }, - - - // get pointer's X/Y coordinates relative to target element - getRelPointerPos : function (e) { - if (!e) { e = window.event; } - var target = e.target || e.srcElement; - var targetRect = target.getBoundingClientRect(); - - var x = 0, y = 0; - - var clientX = 0, clientY = 0; - if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) { - // touch devices - clientX = e.changedTouches[0].clientX; - clientY = e.changedTouches[0].clientY; - } else if (typeof e.clientX === 'number') { - clientX = e.clientX; - clientY = e.clientY; - } - - x = clientX - targetRect.left; - y = clientY - targetRect.top; - return { x: x, y: y }; - }, - - - getViewPos : function () { - var doc = document.documentElement; - return [ - (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0), - (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0) - ]; - }, - - - getViewSize : function () { - var doc = document.documentElement; - return [ - (window.innerWidth || doc.clientWidth), - (window.innerHeight || doc.clientHeight), - ]; - }, - - - redrawPosition : function () { - - if (jsc.picker && jsc.picker.owner) { - var thisObj = jsc.picker.owner; - - var tp, vp; - - if (thisObj.fixed) { - // Fixed elements are positioned relative to viewport, - // therefore we can ignore the scroll offset - tp = jsc.getElementPos(thisObj.targetElement, true); // target pos - vp = [0, 0]; // view pos - } else { - tp = jsc.getElementPos(thisObj.targetElement); // target pos - vp = jsc.getViewPos(); // view pos - } - - var ts = jsc.getElementSize(thisObj.targetElement); // target size - var vs = jsc.getViewSize(); // view size - var ps = jsc.getPickerOuterDims(thisObj); // picker size - var a, b, c; - switch (thisObj.position.toLowerCase()) { - case 'left': a=1; b=0; c=-1; break; - case 'right':a=1; b=0; c=1; break; - case 'top': a=0; b=1; c=-1; break; - default: a=0; b=1; c=1; break; - } - var l = (ts[b]+ps[b])/2; - - // compute picker position - if (!thisObj.smartPosition) { - var pp = [ - tp[a], - tp[b]+ts[b]-l+l*c - ]; - } else { - var pp = [ - -vp[a]+tp[a]+ps[a] > vs[a] ? - (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) : - tp[a], - -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ? - (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) : - (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c) - ]; - } - - var x = pp[a]; - var y = pp[b]; - var positionValue = thisObj.fixed ? 'fixed' : 'absolute'; - var contractShadow = - (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) && - (pp[1] + ps[1] < tp[1] + ts[1]); - - jsc._drawPosition(thisObj, x, y, positionValue, contractShadow); - } - }, - - - _drawPosition : function (thisObj, x, y, positionValue, contractShadow) { - var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px - - jsc.picker.wrap.style.position = positionValue; - jsc.picker.wrap.style.left = x + 'px'; - jsc.picker.wrap.style.top = y + 'px'; - - jsc.setBoxShadow( - jsc.picker.boxS, - thisObj.shadow ? - new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) : - null); - }, - - - getPickerDims : function (thisObj) { - var displaySlider = !!jsc.getSliderComponent(thisObj); - var dims = [ - 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.width + - (displaySlider ? 2 * thisObj.insetWidth + jsc.getPadToSliderPadding(thisObj) + thisObj.sliderSize : 0), - 2 * thisObj.insetWidth + 2 * thisObj.padding + thisObj.height + - (thisObj.closable ? 2 * thisObj.insetWidth + thisObj.padding + thisObj.buttonHeight : 0) - ]; - return dims; - }, - - - getPickerOuterDims : function (thisObj) { - var dims = jsc.getPickerDims(thisObj); - return [ - dims[0] + 2 * thisObj.borderWidth, - dims[1] + 2 * thisObj.borderWidth - ]; - }, - - - getPadToSliderPadding : function (thisObj) { - return Math.max(thisObj.padding, 1.5 * (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness)); - }, - - - getPadYComponent : function (thisObj) { - switch (thisObj.mode.charAt(1).toLowerCase()) { - case 'v': return 'v'; break; - } - return 's'; - }, - - - getSliderComponent : function (thisObj) { - if (thisObj.mode.length > 2) { - switch (thisObj.mode.charAt(2).toLowerCase()) { - case 's': return 's'; break; - case 'v': return 'v'; break; - } - } - return null; - }, - - - onDocumentMouseDown : function (e) { - if (!e) { e = window.event; } - var target = e.target || e.srcElement; - - if (target._jscLinkedInstance) { - if (target._jscLinkedInstance.showOnClick) { - target._jscLinkedInstance.show(); - } - } else if (target._jscControlName) { - jsc.onControlPointerStart(e, target, target._jscControlName, 'mouse'); - } else { - // Mouse is outside the picker controls -> hide the color picker! - if (jsc.picker && jsc.picker.owner) { - jsc.picker.owner.hide(); - } - } - }, - - - onDocumentTouchStart : function (e) { - if (!e) { e = window.event; } - var target = e.target || e.srcElement; - - if (target._jscLinkedInstance) { - if (target._jscLinkedInstance.showOnClick) { - target._jscLinkedInstance.show(); - } - } else if (target._jscControlName) { - jsc.onControlPointerStart(e, target, target._jscControlName, 'touch'); - } else { - if (jsc.picker && jsc.picker.owner) { - jsc.picker.owner.hide(); - } - } - }, - - - onWindowResize : function (e) { - jsc.redrawPosition(); - }, - - - onParentScroll : function (e) { - // hide the picker when one of the parent elements is scrolled - if (jsc.picker && jsc.picker.owner) { - jsc.picker.owner.hide(); - } - }, - - - _pointerMoveEvent : { - mouse: 'mousemove', - touch: 'touchmove' - }, - _pointerEndEvent : { - mouse: 'mouseup', - touch: 'touchend' - }, - - - _pointerOrigin : null, - _capturedTarget : null, - - - onControlPointerStart : function (e, target, controlName, pointerType) { - var thisObj = target._jscInstance; - - jsc.preventDefault(e); - jsc.captureTarget(target); - - var registerDragEvents = function (doc, offset) { - jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType], - jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset)); - jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType], - jsc.onDocumentPointerEnd(e, target, controlName, pointerType)); - }; - - registerDragEvents(document, [0, 0]); - - if (window.parent && window.frameElement) { - var rect = window.frameElement.getBoundingClientRect(); - var ofs = [-rect.left, -rect.top]; - registerDragEvents(window.parent.window.document, ofs); - } - - var abs = jsc.getAbsPointerPos(e); - var rel = jsc.getRelPointerPos(e); - jsc._pointerOrigin = { - x: abs.x - rel.x, - y: abs.y - rel.y - }; - - switch (controlName) { - case 'pad': - // if the slider is at the bottom, move it up - switch (jsc.getSliderComponent(thisObj)) { - case 's': if (thisObj.hsv[1] === 0) { thisObj.fromHSV(null, 100, null); }; break; - case 'v': if (thisObj.hsv[2] === 0) { thisObj.fromHSV(null, null, 100); }; break; - } - jsc.setPad(thisObj, e, 0, 0); - break; - - case 'sld': - jsc.setSld(thisObj, e, 0); - break; - } - - jsc.dispatchFineChange(thisObj); - }, - - - onDocumentPointerMove : function (e, target, controlName, pointerType, offset) { - return function (e) { - var thisObj = target._jscInstance; - switch (controlName) { - case 'pad': - if (!e) { e = window.event; } - jsc.setPad(thisObj, e, offset[0], offset[1]); - jsc.dispatchFineChange(thisObj); - break; - - case 'sld': - if (!e) { e = window.event; } - jsc.setSld(thisObj, e, offset[1]); - jsc.dispatchFineChange(thisObj); - break; - } - } - }, - - - onDocumentPointerEnd : function (e, target, controlName, pointerType) { - return function (e) { - var thisObj = target._jscInstance; - jsc.detachGroupEvents('drag'); - jsc.releaseTarget(); - // Always dispatch changes after detaching outstanding mouse handlers, - // in case some user interaction will occur in user's onchange callback - // that would intrude with current mouse events - jsc.dispatchChange(thisObj); - }; - }, - - - dispatchChange : function (thisObj) { - if (thisObj.valueElement) { - if (jsc.isElementType(thisObj.valueElement, 'input')) { - jsc.fireEvent(thisObj.valueElement, 'change'); - } - } - }, - - - dispatchFineChange : function (thisObj) { - if (thisObj.onFineChange) { - var callback; - if (typeof thisObj.onFineChange === 'string') { - callback = new Function (thisObj.onFineChange); - } else { - callback = thisObj.onFineChange; - } - callback.call(thisObj); - } - }, - - - setPad : function (thisObj, e, ofsX, ofsY) { - var pointerAbs = jsc.getAbsPointerPos(e); - var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.insetWidth; - var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; - - var xVal = x * (360 / (thisObj.width - 1)); - var yVal = 100 - (y * (100 / (thisObj.height - 1))); - - switch (jsc.getPadYComponent(thisObj)) { - case 's': thisObj.fromHSV(xVal, yVal, null, jsc.leaveSld); break; - case 'v': thisObj.fromHSV(xVal, null, yVal, jsc.leaveSld); break; - } - }, - - - setSld : function (thisObj, e, ofsY) { - var pointerAbs = jsc.getAbsPointerPos(e); - var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.insetWidth; - - var yVal = 100 - (y * (100 / (thisObj.height - 1))); - - switch (jsc.getSliderComponent(thisObj)) { - case 's': thisObj.fromHSV(null, yVal, null, jsc.leavePad); break; - case 'v': thisObj.fromHSV(null, null, yVal, jsc.leavePad); break; - } - }, - - - _vmlNS : 'jsc_vml_', - _vmlCSS : 'jsc_vml_css_', - _vmlReady : false, - - - initVML : function () { - if (!jsc._vmlReady) { - // init VML namespace - var doc = document; - if (!doc.namespaces[jsc._vmlNS]) { - doc.namespaces.add(jsc._vmlNS, 'urn:schemas-microsoft-com:vml'); - } - if (!doc.styleSheets[jsc._vmlCSS]) { - var tags = ['shape', 'shapetype', 'group', 'background', 'path', 'formulas', 'handles', 'fill', 'stroke', 'shadow', 'textbox', 'textpath', 'imagedata', 'line', 'polyline', 'curve', 'rect', 'roundrect', 'oval', 'arc', 'image']; - var ss = doc.createStyleSheet(); - ss.owningElement.id = jsc._vmlCSS; - for (var i = 0; i < tags.length; i += 1) { - ss.addRule(jsc._vmlNS + '\\:' + tags[i], 'behavior:url(#default#VML);'); - } - } - jsc._vmlReady = true; - } - }, - - - createPalette : function () { - - var paletteObj = { - elm: null, - draw: null - }; - - if (jsc.isCanvasSupported) { - // Canvas implementation for modern browsers - - var canvas = document.createElement('canvas'); - var ctx = canvas.getContext('2d'); - - var drawFunc = function (width, height, type) { - canvas.width = width; - canvas.height = height; - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0); - hGrad.addColorStop(0 / 6, '#F00'); - hGrad.addColorStop(1 / 6, '#FF0'); - hGrad.addColorStop(2 / 6, '#0F0'); - hGrad.addColorStop(3 / 6, '#0FF'); - hGrad.addColorStop(4 / 6, '#00F'); - hGrad.addColorStop(5 / 6, '#F0F'); - hGrad.addColorStop(6 / 6, '#F00'); - - ctx.fillStyle = hGrad; - ctx.fillRect(0, 0, canvas.width, canvas.height); - - var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height); - switch (type.toLowerCase()) { - case 's': - vGrad.addColorStop(0, 'rgba(255,255,255,0)'); - vGrad.addColorStop(1, 'rgba(255,255,255,1)'); - break; - case 'v': - vGrad.addColorStop(0, 'rgba(0,0,0,0)'); - vGrad.addColorStop(1, 'rgba(0,0,0,1)'); - break; - } - ctx.fillStyle = vGrad; - ctx.fillRect(0, 0, canvas.width, canvas.height); - }; - - paletteObj.elm = canvas; - paletteObj.draw = drawFunc; - - } else { - // VML fallback for IE 7 and 8 - - jsc.initVML(); - - var vmlContainer = document.createElement('div'); - vmlContainer.style.position = 'relative'; - vmlContainer.style.overflow = 'hidden'; - - var hGrad = document.createElement(jsc._vmlNS + ':fill'); - hGrad.type = 'gradient'; - hGrad.method = 'linear'; - hGrad.angle = '90'; - hGrad.colors = '16.67% #F0F, 33.33% #00F, 50% #0FF, 66.67% #0F0, 83.33% #FF0' - - var hRect = document.createElement(jsc._vmlNS + ':rect'); - hRect.style.position = 'absolute'; - hRect.style.left = -1 + 'px'; - hRect.style.top = -1 + 'px'; - hRect.stroked = false; - hRect.appendChild(hGrad); - vmlContainer.appendChild(hRect); - - var vGrad = document.createElement(jsc._vmlNS + ':fill'); - vGrad.type = 'gradient'; - vGrad.method = 'linear'; - vGrad.angle = '180'; - vGrad.opacity = '0'; - - var vRect = document.createElement(jsc._vmlNS + ':rect'); - vRect.style.position = 'absolute'; - vRect.style.left = -1 + 'px'; - vRect.style.top = -1 + 'px'; - vRect.stroked = false; - vRect.appendChild(vGrad); - vmlContainer.appendChild(vRect); - - var drawFunc = function (width, height, type) { - vmlContainer.style.width = width + 'px'; - vmlContainer.style.height = height + 'px'; - - hRect.style.width = - vRect.style.width = - (width + 1) + 'px'; - hRect.style.height = - vRect.style.height = - (height + 1) + 'px'; - - // Colors must be specified during every redraw, otherwise IE won't display - // a full gradient during a subsequential redraw - hGrad.color = '#F00'; - hGrad.color2 = '#F00'; - - switch (type.toLowerCase()) { - case 's': - vGrad.color = vGrad.color2 = '#FFF'; - break; - case 'v': - vGrad.color = vGrad.color2 = '#000'; - break; - } - }; - - paletteObj.elm = vmlContainer; - paletteObj.draw = drawFunc; - } - - return paletteObj; - }, - - - createSliderGradient : function () { - - var sliderObj = { - elm: null, - draw: null - }; - - if (jsc.isCanvasSupported) { - // Canvas implementation for modern browsers - - var canvas = document.createElement('canvas'); - var ctx = canvas.getContext('2d'); - - var drawFunc = function (width, height, color1, color2) { - canvas.width = width; - canvas.height = height; - - ctx.clearRect(0, 0, canvas.width, canvas.height); - - var grad = ctx.createLinearGradient(0, 0, 0, canvas.height); - grad.addColorStop(0, color1); - grad.addColorStop(1, color2); - - ctx.fillStyle = grad; - ctx.fillRect(0, 0, canvas.width, canvas.height); - }; - - sliderObj.elm = canvas; - sliderObj.draw = drawFunc; - - } else { - // VML fallback for IE 7 and 8 - - jsc.initVML(); - - var vmlContainer = document.createElement('div'); - vmlContainer.style.position = 'relative'; - vmlContainer.style.overflow = 'hidden'; - - var grad = document.createElement(jsc._vmlNS + ':fill'); - grad.type = 'gradient'; - grad.method = 'linear'; - grad.angle = '180'; - - var rect = document.createElement(jsc._vmlNS + ':rect'); - rect.style.position = 'absolute'; - rect.style.left = -1 + 'px'; - rect.style.top = -1 + 'px'; - rect.stroked = false; - rect.appendChild(grad); - vmlContainer.appendChild(rect); - - var drawFunc = function (width, height, color1, color2) { - vmlContainer.style.width = width + 'px'; - vmlContainer.style.height = height + 'px'; - - rect.style.width = (width + 1) + 'px'; - rect.style.height = (height + 1) + 'px'; - - grad.color = color1; - grad.color2 = color2; - }; - - sliderObj.elm = vmlContainer; - sliderObj.draw = drawFunc; - } - - return sliderObj; - }, - - - leaveValue : 1<<0, - leaveStyle : 1<<1, - leavePad : 1<<2, - leaveSld : 1<<3, - - - BoxShadow : (function () { - var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) { - this.hShadow = hShadow; - this.vShadow = vShadow; - this.blur = blur; - this.spread = spread; - this.color = color; - this.inset = !!inset; - }; - - BoxShadow.prototype.toString = function () { - var vals = [ - Math.round(this.hShadow) + 'px', - Math.round(this.vShadow) + 'px', - Math.round(this.blur) + 'px', - Math.round(this.spread) + 'px', - this.color - ]; - if (this.inset) { - vals.push('inset'); - } - return vals.join(' '); - }; - - return BoxShadow; - })(), - - - // - // Usage: - // var myColor = new jscolor( [, ]) - // - - jscolor : function (targetElement, options) { - - // General options - // - this.value = null; // initial HEX color. To change it later, use methods fromString(), fromHSV() and fromRGB() - this.valueElement = targetElement; // element that will be used to display and input the color code - this.styleElement = targetElement; // element that will preview the picked color using CSS backgroundColor - this.required = true; // whether the associated text can be left empty - this.refine = true; // whether to refine the entered color code (e.g. uppercase it and remove whitespace) - this.hash = false; // whether to prefix the HEX color code with # symbol - this.uppercase = true; // whether to uppercase the color code - this.onFineChange = null; // called instantly every time the color changes (value can be either a function or a string with javascript code) - this.activeClass = 'jscolor-active'; // class to be set to the target element when a picker window is open on it - this.minS = 0; // min allowed saturation (0 - 100) - this.maxS = 100; // max allowed saturation (0 - 100) - this.minV = 0; // min allowed value (brightness) (0 - 100) - this.maxV = 100; // max allowed value (brightness) (0 - 100) - - // Accessing the picked color - // - this.hsv = [0, 0, 100]; // read-only [0-360, 0-100, 0-100] - this.rgb = [255, 255, 255]; // read-only [0-255, 0-255, 0-255] - - // Color Picker options - // - this.width = 181; // width of color palette (in px) - this.height = 101; // height of color palette (in px) - this.showOnClick = true; // whether to display the color picker when user clicks on its target element - this.mode = 'HSV'; // HSV | HVS | HS | HV - layout of the color picker controls - this.position = 'bottom'; // left | right | top | bottom - position relative to the target element - this.smartPosition = true; // automatically change picker position when there is not enough space for it - this.sliderSize = 16; // px - this.crossSize = 8; // px - this.closable = false; // whether to display the Close button - this.closeText = 'Close'; - this.buttonColor = '#000000'; // CSS color - this.buttonHeight = 18; // px - this.padding = 12; // px - this.backgroundColor = '#FFFFFF'; // CSS color - this.borderWidth = 1; // px - this.borderColor = '#BBBBBB'; // CSS color - this.borderRadius = 8; // px - this.insetWidth = 1; // px - this.insetColor = '#BBBBBB'; // CSS color - this.shadow = true; // whether to display shadow - this.shadowBlur = 15; // px - this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color - this.pointerColor = '#4C4C4C'; // px - this.pointerBorderColor = '#FFFFFF'; // px - this.pointerBorderWidth = 1; // px - this.pointerThickness = 2; // px - this.zIndex = 1000; - this.container = null; // where to append the color picker (BODY element by default) - - - for (var opt in options) { - if (options.hasOwnProperty(opt)) { - this[opt] = options[opt]; - } - } - - - this.hide = function () { - if (isPickerOwner()) { - detachPicker(); - } - }; - - - this.show = function () { - drawPicker(); - }; - - - this.redraw = function () { - if (isPickerOwner()) { - drawPicker(); - } - }; - - - this.importColor = function () { - if (!this.valueElement) { - this.exportColor(); - } else { - if (jsc.isElementType(this.valueElement, 'input')) { - if (!this.refine) { - if (!this.fromString(this.valueElement.value, jsc.leaveValue)) { - if (this.styleElement) { - this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; - this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; - this.styleElement.style.color = this.styleElement._jscOrigStyle.color; - } - this.exportColor(jsc.leaveValue | jsc.leaveStyle); - } - } else if (!this.required && /^\s*$/.test(this.valueElement.value)) { - this.valueElement.value = ''; - if (this.styleElement) { - this.styleElement.style.backgroundImage = this.styleElement._jscOrigStyle.backgroundImage; - this.styleElement.style.backgroundColor = this.styleElement._jscOrigStyle.backgroundColor; - this.styleElement.style.color = this.styleElement._jscOrigStyle.color; - } - this.exportColor(jsc.leaveValue | jsc.leaveStyle); - - } else if (this.fromString(this.valueElement.value)) { - // managed to import color successfully from the value -> OK, don't do anything - } else { - this.exportColor(); - } - } else { - // not an input element -> doesn't have any value - this.exportColor(); - } - } - }; - - - this.exportColor = function (flags) { - if (!(flags & jsc.leaveValue) && this.valueElement) { - var value = this.toString(); - if (this.uppercase) { value = value.toUpperCase(); } - if (this.hash) { value = '#' + value; } - - if (jsc.isElementType(this.valueElement, 'input')) { - this.valueElement.value = value; - } else { - this.valueElement.innerHTML = value; - } - } - if (!(flags & jsc.leaveStyle)) { - if (this.styleElement) { - this.styleElement.style.backgroundImage = 'none'; - this.styleElement.style.backgroundColor = '#' + this.toString(); - this.styleElement.style.color = this.isLight() ? '#000' : '#FFF'; - } - } - if (!(flags & jsc.leavePad) && isPickerOwner()) { - redrawPad(); - } - if (!(flags & jsc.leaveSld) && isPickerOwner()) { - redrawSld(); - } - }; - - - // h: 0-360 - // s: 0-100 - // v: 0-100 - // - this.fromHSV = function (h, s, v, flags) { // null = don't change - if (h !== null) { - if (isNaN(h)) { return false; } - h = Math.max(0, Math.min(360, h)); - } - if (s !== null) { - if (isNaN(s)) { return false; } - s = Math.max(0, Math.min(100, this.maxS, s), this.minS); - } - if (v !== null) { - if (isNaN(v)) { return false; } - v = Math.max(0, Math.min(100, this.maxV, v), this.minV); - } - - this.rgb = HSV_RGB( - h===null ? this.hsv[0] : (this.hsv[0]=h), - s===null ? this.hsv[1] : (this.hsv[1]=s), - v===null ? this.hsv[2] : (this.hsv[2]=v) - ); - - this.exportColor(flags); - }; - - - // r: 0-255 - // g: 0-255 - // b: 0-255 - // - this.fromRGB = function (r, g, b, flags) { // null = don't change - if (r !== null) { - if (isNaN(r)) { return false; } - r = Math.max(0, Math.min(255, r)); - } - if (g !== null) { - if (isNaN(g)) { return false; } - g = Math.max(0, Math.min(255, g)); - } - if (b !== null) { - if (isNaN(b)) { return false; } - b = Math.max(0, Math.min(255, b)); - } - - var hsv = RGB_HSV( - r===null ? this.rgb[0] : r, - g===null ? this.rgb[1] : g, - b===null ? this.rgb[2] : b - ); - if (hsv[0] !== null) { - this.hsv[0] = Math.max(0, Math.min(360, hsv[0])); - } - if (hsv[2] !== 0) { - this.hsv[1] = hsv[1]===null ? null : Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1])); - } - this.hsv[2] = hsv[2]===null ? null : Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2])); - - // update RGB according to final HSV, as some values might be trimmed - var rgb = HSV_RGB(this.hsv[0], this.hsv[1], this.hsv[2]); - this.rgb[0] = rgb[0]; - this.rgb[1] = rgb[1]; - this.rgb[2] = rgb[2]; - - this.exportColor(flags); - }; - - - this.fromString = function (str, flags) { - var m; - if (m = str.match(/^\W*([0-9A-F]{3}([0-9A-F]{3})?)\W*$/i)) { - // HEX notation - // - - if (m[1].length === 6) { - // 6-char notation - this.fromRGB( - parseInt(m[1].substr(0,2),16), - parseInt(m[1].substr(2,2),16), - parseInt(m[1].substr(4,2),16), - flags - ); - } else { - // 3-char notation - this.fromRGB( - parseInt(m[1].charAt(0) + m[1].charAt(0),16), - parseInt(m[1].charAt(1) + m[1].charAt(1),16), - parseInt(m[1].charAt(2) + m[1].charAt(2),16), - flags - ); - } - return true; - - } else if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) { - var params = m[1].split(','); - var re = /^\s*(\d*)(\.\d+)?\s*$/; - var mR, mG, mB; - if ( - params.length >= 3 && - (mR = params[0].match(re)) && - (mG = params[1].match(re)) && - (mB = params[2].match(re)) - ) { - var r = parseFloat((mR[1] || '0') + (mR[2] || '')); - var g = parseFloat((mG[1] || '0') + (mG[2] || '')); - var b = parseFloat((mB[1] || '0') + (mB[2] || '')); - this.fromRGB(r, g, b, flags); - return true; - } - } - return false; - }; - - - this.toString = function () { - return ( - (0x100 | Math.round(this.rgb[0])).toString(16).substr(1) + - (0x100 | Math.round(this.rgb[1])).toString(16).substr(1) + - (0x100 | Math.round(this.rgb[2])).toString(16).substr(1) - ); - }; - - - this.toHEXString = function () { - return '#' + this.toString().toUpperCase(); - }; - - - this.toRGBString = function () { - return ('rgb(' + - Math.round(this.rgb[0]) + ',' + - Math.round(this.rgb[1]) + ',' + - Math.round(this.rgb[2]) + ')' - ); - }; - - - this.isLight = function () { - return ( - 0.213 * this.rgb[0] + - 0.715 * this.rgb[1] + - 0.072 * this.rgb[2] > - 255 / 2 - ); - }; - - - this._processParentElementsInDOM = function () { - if (this._linkedElementsProcessed) { return; } - this._linkedElementsProcessed = true; - - var elm = this.targetElement; - do { - // If the target element or one of its parent nodes has fixed position, - // then use fixed positioning instead - // - // Note: In Firefox, getComputedStyle returns null in a hidden iframe, - // that's why we need to check if the returned style object is non-empty - var currStyle = jsc.getStyle(elm); - if (currStyle && currStyle.position.toLowerCase() === 'fixed') { - this.fixed = true; - } - - if (elm !== this.targetElement) { - // Ensure to attach onParentScroll only once to each parent element - // (multiple targetElements can share the same parent nodes) - // - // Note: It's not just offsetParents that can be scrollable, - // that's why we loop through all parent nodes - if (!elm._jscEventsAttached) { - jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); - elm._jscEventsAttached = true; - } - } - } while ((elm = elm.parentNode) && !jsc.isElementType(elm, 'body')); - }; - - - // r: 0-255 - // g: 0-255 - // b: 0-255 - // - // returns: [ 0-360, 0-100, 0-100 ] - // - function RGB_HSV (r, g, b) { - r /= 255; - g /= 255; - b /= 255; - var n = Math.min(Math.min(r,g),b); - var v = Math.max(Math.max(r,g),b); - var m = v - n; - if (m === 0) { return [ null, 0, 100 * v ]; } - var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m); - return [ - 60 * (h===6?0:h), - 100 * (m/v), - 100 * v - ]; - } - - - // h: 0-360 - // s: 0-100 - // v: 0-100 - // - // returns: [ 0-255, 0-255, 0-255 ] - // - function HSV_RGB (h, s, v) { - var u = 255 * (v / 100); - - if (h === null) { - return [ u, u, u ]; - } - - h /= 60; - s /= 100; - - var i = Math.floor(h); - var f = i%2 ? h-i : 1-(h-i); - var m = u * (1 - s); - var n = u * (1 - s * f); - switch (i) { - case 6: - case 0: return [u,n,m]; - case 1: return [n,u,m]; - case 2: return [m,u,n]; - case 3: return [m,n,u]; - case 4: return [n,m,u]; - case 5: return [u,m,n]; - } - } - - - function detachPicker () { - jsc.unsetClass(THIS.targetElement, THIS.activeClass); - jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap); - delete jsc.picker.owner; - } - - - function drawPicker () { - - // At this point, when drawing the picker, we know what the parent elements are - // and we can do all related DOM operations, such as registering events on them - // or checking their positioning - THIS._processParentElementsInDOM(); - - if (!jsc.picker) { - jsc.picker = { - owner: null, - wrap : document.createElement('div'), - box : document.createElement('div'), - boxS : document.createElement('div'), // shadow area - boxB : document.createElement('div'), // border - pad : document.createElement('div'), - padB : document.createElement('div'), // border - padM : document.createElement('div'), // mouse/touch area - padPal : jsc.createPalette(), - cross : document.createElement('div'), - crossBY : document.createElement('div'), // border Y - crossBX : document.createElement('div'), // border X - crossLY : document.createElement('div'), // line Y - crossLX : document.createElement('div'), // line X - sld : document.createElement('div'), - sldB : document.createElement('div'), // border - sldM : document.createElement('div'), // mouse/touch area - sldGrad : jsc.createSliderGradient(), - sldPtrS : document.createElement('div'), // slider pointer spacer - sldPtrIB : document.createElement('div'), // slider pointer inner border - sldPtrMB : document.createElement('div'), // slider pointer middle border - sldPtrOB : document.createElement('div'), // slider pointer outer border - btn : document.createElement('div'), - btnT : document.createElement('span') // text - }; - - jsc.picker.pad.appendChild(jsc.picker.padPal.elm); - jsc.picker.padB.appendChild(jsc.picker.pad); - jsc.picker.cross.appendChild(jsc.picker.crossBY); - jsc.picker.cross.appendChild(jsc.picker.crossBX); - jsc.picker.cross.appendChild(jsc.picker.crossLY); - jsc.picker.cross.appendChild(jsc.picker.crossLX); - jsc.picker.padB.appendChild(jsc.picker.cross); - jsc.picker.box.appendChild(jsc.picker.padB); - jsc.picker.box.appendChild(jsc.picker.padM); - - jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm); - jsc.picker.sldB.appendChild(jsc.picker.sld); - jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB); - jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB); - jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB); - jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS); - jsc.picker.box.appendChild(jsc.picker.sldB); - jsc.picker.box.appendChild(jsc.picker.sldM); - - jsc.picker.btn.appendChild(jsc.picker.btnT); - jsc.picker.box.appendChild(jsc.picker.btn); - - jsc.picker.boxB.appendChild(jsc.picker.box); - jsc.picker.wrap.appendChild(jsc.picker.boxS); - jsc.picker.wrap.appendChild(jsc.picker.boxB); - } - - var p = jsc.picker; - - var displaySlider = !!jsc.getSliderComponent(THIS); - var dims = jsc.getPickerDims(THIS); - var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); - var padToSliderPadding = jsc.getPadToSliderPadding(THIS); - var borderRadius = Math.min( - THIS.borderRadius, - Math.round(THIS.padding * Math.PI)); // px - var padCursor = 'crosshair'; - - // wrap - p.wrap.style.clear = 'both'; - p.wrap.style.width = (dims[0] + 2 * THIS.borderWidth) + 'px'; - p.wrap.style.height = (dims[1] + 2 * THIS.borderWidth) + 'px'; - p.wrap.style.zIndex = THIS.zIndex; - - // picker - p.box.style.width = dims[0] + 'px'; - p.box.style.height = dims[1] + 'px'; - - p.boxS.style.position = 'absolute'; - p.boxS.style.left = '0'; - p.boxS.style.top = '0'; - p.boxS.style.width = '100%'; - p.boxS.style.height = '100%'; - jsc.setBorderRadius(p.boxS, borderRadius + 'px'); - - // picker border - p.boxB.style.position = 'relative'; - p.boxB.style.border = THIS.borderWidth + 'px solid'; - p.boxB.style.borderColor = THIS.borderColor; - p.boxB.style.background = THIS.backgroundColor; - jsc.setBorderRadius(p.boxB, borderRadius + 'px'); - - // IE hack: - // If the element is transparent, IE will trigger the event on the elements under it, - // e.g. on Canvas or on elements with border - p.padM.style.background = - p.sldM.style.background = - '#FFF'; - jsc.setStyle(p.padM, 'opacity', '0'); - jsc.setStyle(p.sldM, 'opacity', '0'); - - // pad - p.pad.style.position = 'relative'; - p.pad.style.width = THIS.width + 'px'; - p.pad.style.height = THIS.height + 'px'; - - // pad palettes (HSV and HVS) - p.padPal.draw(THIS.width, THIS.height, jsc.getPadYComponent(THIS)); - - // pad border - p.padB.style.position = 'absolute'; - p.padB.style.left = THIS.padding + 'px'; - p.padB.style.top = THIS.padding + 'px'; - p.padB.style.border = THIS.insetWidth + 'px solid'; - p.padB.style.borderColor = THIS.insetColor; - - // pad mouse area - p.padM._jscInstance = THIS; - p.padM._jscControlName = 'pad'; - p.padM.style.position = 'absolute'; - p.padM.style.left = '0'; - p.padM.style.top = '0'; - p.padM.style.width = (THIS.padding + 2 * THIS.insetWidth + THIS.width + padToSliderPadding / 2) + 'px'; - p.padM.style.height = dims[1] + 'px'; - p.padM.style.cursor = padCursor; - - // pad cross - p.cross.style.position = 'absolute'; - p.cross.style.left = - p.cross.style.top = - '0'; - p.cross.style.width = - p.cross.style.height = - crossOuterSize + 'px'; - - // pad cross border Y and X - p.crossBY.style.position = - p.crossBX.style.position = - 'absolute'; - p.crossBY.style.background = - p.crossBX.style.background = - THIS.pointerBorderColor; - p.crossBY.style.width = - p.crossBX.style.height = - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; - p.crossBY.style.height = - p.crossBX.style.width = - crossOuterSize + 'px'; - p.crossBY.style.left = - p.crossBX.style.top = - (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px'; - p.crossBY.style.top = - p.crossBX.style.left = - '0'; - - // pad cross line Y and X - p.crossLY.style.position = - p.crossLX.style.position = - 'absolute'; - p.crossLY.style.background = - p.crossLX.style.background = - THIS.pointerColor; - p.crossLY.style.height = - p.crossLX.style.width = - (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px'; - p.crossLY.style.width = - p.crossLX.style.height = - THIS.pointerThickness + 'px'; - p.crossLY.style.left = - p.crossLX.style.top = - (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px'; - p.crossLY.style.top = - p.crossLX.style.left = - THIS.pointerBorderWidth + 'px'; - - // slider - p.sld.style.overflow = 'hidden'; - p.sld.style.width = THIS.sliderSize + 'px'; - p.sld.style.height = THIS.height + 'px'; - - // slider gradient - p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000'); - - // slider border - p.sldB.style.display = displaySlider ? 'block' : 'none'; - p.sldB.style.position = 'absolute'; - p.sldB.style.right = THIS.padding + 'px'; - p.sldB.style.top = THIS.padding + 'px'; - p.sldB.style.border = THIS.insetWidth + 'px solid'; - p.sldB.style.borderColor = THIS.insetColor; - - // slider mouse area - p.sldM._jscInstance = THIS; - p.sldM._jscControlName = 'sld'; - p.sldM.style.display = displaySlider ? 'block' : 'none'; - p.sldM.style.position = 'absolute'; - p.sldM.style.right = '0'; - p.sldM.style.top = '0'; - p.sldM.style.width = (THIS.sliderSize + padToSliderPadding / 2 + THIS.padding + 2 * THIS.insetWidth) + 'px'; - p.sldM.style.height = dims[1] + 'px'; - p.sldM.style.cursor = 'default'; - - // slider pointer inner and outer border - p.sldPtrIB.style.border = - p.sldPtrOB.style.border = - THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor; - - // slider pointer outer border - p.sldPtrOB.style.position = 'absolute'; - p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px'; - p.sldPtrOB.style.top = '0'; - - // slider pointer middle border - p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor; - - // slider pointer spacer - p.sldPtrS.style.width = THIS.sliderSize + 'px'; - p.sldPtrS.style.height = sliderPtrSpace + 'px'; - - // the Close button - function setBtnBorder () { - var insetColors = THIS.insetColor.split(/\s+/); - var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1]; - p.btn.style.borderColor = outsetColor; - } - p.btn.style.display = THIS.closable ? 'block' : 'none'; - p.btn.style.position = 'absolute'; - p.btn.style.left = THIS.padding + 'px'; - p.btn.style.bottom = THIS.padding + 'px'; - p.btn.style.padding = '0 15px'; - p.btn.style.height = THIS.buttonHeight + 'px'; - p.btn.style.border = THIS.insetWidth + 'px solid'; - setBtnBorder(); - p.btn.style.color = THIS.buttonColor; - p.btn.style.font = '12px sans-serif'; - p.btn.style.textAlign = 'center'; - try { - p.btn.style.cursor = 'pointer'; - } catch(eOldIE) { - p.btn.style.cursor = 'hand'; - } - p.btn.onmousedown = function () { - THIS.hide(); - }; - p.btnT.style.lineHeight = THIS.buttonHeight + 'px'; - p.btnT.innerHTML = ''; - p.btnT.appendChild(document.createTextNode(THIS.closeText)); - - // place pointers - redrawPad(); - redrawSld(); - - // If we are changing the owner without first closing the picker, - // make sure to first deal with the old owner - if (jsc.picker.owner && jsc.picker.owner !== THIS) { - jsc.unsetClass(jsc.picker.owner.targetElement, THIS.activeClass); - } - - // Set the new picker owner - jsc.picker.owner = THIS; - - // The redrawPosition() method needs picker.owner to be set, that's why we call it here, - // after setting the owner - if (jsc.isElementType(container, 'body')) { - jsc.redrawPosition(); - } else { - jsc._drawPosition(THIS, 0, 0, 'relative', false); - } - - if (p.wrap.parentNode != container) { - container.appendChild(p.wrap); - } - - jsc.setClass(THIS.targetElement, THIS.activeClass); - } - - - function redrawPad () { - // redraw the pad pointer - switch (jsc.getPadYComponent(THIS)) { - case 's': var yComponent = 1; break; - case 'v': var yComponent = 2; break; - } - var x = Math.round((THIS.hsv[0] / 360) * (THIS.width - 1)); - var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); - var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize); - var ofs = -Math.floor(crossOuterSize / 2); - jsc.picker.cross.style.left = (x + ofs) + 'px'; - jsc.picker.cross.style.top = (y + ofs) + 'px'; - - // redraw the slider - switch (jsc.getSliderComponent(THIS)) { - case 's': - var rgb1 = HSV_RGB(THIS.hsv[0], 100, THIS.hsv[2]); - var rgb2 = HSV_RGB(THIS.hsv[0], 0, THIS.hsv[2]); - var color1 = 'rgb(' + - Math.round(rgb1[0]) + ',' + - Math.round(rgb1[1]) + ',' + - Math.round(rgb1[2]) + ')'; - var color2 = 'rgb(' + - Math.round(rgb2[0]) + ',' + - Math.round(rgb2[1]) + ',' + - Math.round(rgb2[2]) + ')'; - jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); - break; - case 'v': - var rgb = HSV_RGB(THIS.hsv[0], THIS.hsv[1], 100); - var color1 = 'rgb(' + - Math.round(rgb[0]) + ',' + - Math.round(rgb[1]) + ',' + - Math.round(rgb[2]) + ')'; - var color2 = '#000'; - jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2); - break; - } - } - - - function redrawSld () { - var sldComponent = jsc.getSliderComponent(THIS); - if (sldComponent) { - // redraw the slider pointer - switch (sldComponent) { - case 's': var yComponent = 1; break; - case 'v': var yComponent = 2; break; - } - var y = Math.round((1 - THIS.hsv[yComponent] / 100) * (THIS.height - 1)); - jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(sliderPtrSpace / 2)) + 'px'; - } - } - - - function isPickerOwner () { - return jsc.picker && jsc.picker.owner === THIS; - } - - - function blurValue () { - THIS.importColor(); - } - - - // Find the target element - if (typeof targetElement === 'string') { - var id = targetElement; - var elm = document.getElementById(id); - if (elm) { - this.targetElement = elm; - } else { - jsc.warn('Could not find target element with ID \'' + id + '\''); - } - } else if (targetElement) { - this.targetElement = targetElement; - } else { - jsc.warn('Invalid target element: \'' + targetElement + '\''); - } - - if (this.targetElement._jscLinkedInstance) { - jsc.warn('Cannot link jscolor twice to the same element. Skipping.'); - return; - } - this.targetElement._jscLinkedInstance = this; - - // Find the value element - this.valueElement = jsc.fetchElement(this.valueElement); - // Find the style element - this.styleElement = jsc.fetchElement(this.styleElement); - - var THIS = this; - var container = - this.container ? - jsc.fetchElement(this.container) : - document.getElementsByTagName('body')[0]; - var sliderPtrSpace = 3; // px - - // For BUTTON elements it's important to stop them from sending the form when clicked - // (e.g. in Safari) - if (jsc.isElementType(this.targetElement, 'button')) { - if (this.targetElement.onclick) { - var origCallback = this.targetElement.onclick; - this.targetElement.onclick = function (evt) { - origCallback.call(this, evt); - return false; - }; - } else { - this.targetElement.onclick = function () { return false; }; - } - } - - /* - var elm = this.targetElement; - do { - // If the target element or one of its offsetParents has fixed position, - // then use fixed positioning instead - // - // Note: In Firefox, getComputedStyle returns null in a hidden iframe, - // that's why we need to check if the returned style object is non-empty - var currStyle = jsc.getStyle(elm); - if (currStyle && currStyle.position.toLowerCase() === 'fixed') { - this.fixed = true; - } - - if (elm !== this.targetElement) { - // attach onParentScroll so that we can recompute the picker position - // when one of the offsetParents is scrolled - if (!elm._jscEventsAttached) { - jsc.attachEvent(elm, 'scroll', jsc.onParentScroll); - elm._jscEventsAttached = true; - } - } - } while ((elm = elm.offsetParent) && !jsc.isElementType(elm, 'body')); - */ - - // valueElement - if (this.valueElement) { - if (jsc.isElementType(this.valueElement, 'input')) { - var updateField = function () { - THIS.fromString(THIS.valueElement.value, jsc.leaveValue); - jsc.dispatchFineChange(THIS); - }; - jsc.attachEvent(this.valueElement, 'keyup', updateField); - jsc.attachEvent(this.valueElement, 'input', updateField); - jsc.attachEvent(this.valueElement, 'blur', blurValue); - this.valueElement.setAttribute('autocomplete', 'off'); - } - } - - // styleElement - if (this.styleElement) { - this.styleElement._jscOrigStyle = { - backgroundImage : this.styleElement.style.backgroundImage, - backgroundColor : this.styleElement.style.backgroundColor, - color : this.styleElement.style.color - }; - } - - if (this.value) { - // Try to set the color from the .value option and if unsuccessful, - // export the current color - this.fromString(this.value) || this.exportColor(); - } else { - this.importColor(); - } - } - -}; - - -//================================ -// Public properties and methods -//================================ - - -// By default, search for all elements with class="jscolor" and install a color picker on them. -// -// You can change what class name will be looked for by setting the property jscolor.lookupClass -// anywhere in your HTML document. To completely disable the automatic lookup, set it to null. -// -jsc.jscolor.lookupClass = 'jscolor'; - - -jsc.jscolor.installByClassName = function (className) { - var inputElms = document.getElementsByTagName('input'); - var buttonElms = document.getElementsByTagName('button'); - - jsc.tryInstallOnElements(inputElms, className); - jsc.tryInstallOnElements(buttonElms, className); -}; - - -jsc.register(); - - -return jsc.jscolor; - - -})(); } diff --git a/jscolor.min.js b/jscolor.min.js deleted file mode 100644 index 2a7a788..0000000 --- a/jscolor.min.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * jscolor - JavaScript Color Picker - * - * @link http://jscolor.com - * @license For open source use: GPLv3 - * For commercial use: JSColor Commercial License - * @author Jan Odvarko - * - * See usage examples at http://jscolor.com/examples/ - */"use strict";window.jscolor||(window.jscolor=function(){var e={register:function(){e.attachDOMReadyEvent(e.init),e.attachEvent(document,"mousedown",e.onDocumentMouseDown),e.attachEvent(document,"touchstart",e.onDocumentTouchStart),e.attachEvent(window,"resize",e.onWindowResize)},init:function(){e.jscolor.lookupClass&&e.jscolor.installByClassName(e.jscolor.lookupClass)},tryInstallOnElements:function(t,n){var r=new RegExp("(^|\\s)("+n+")(\\s*(\\{[^}]*\\})|\\s|$)","i");for(var i=0;is[u]?-r[u]+n[u]+i[u]/2>s[u]/2&&n[u]+i[u]-o[u]>=0?n[u]+i[u]-o[u]:n[u]:n[u],-r[a]+n[a]+i[a]+o[a]-l+l*f>s[a]?-r[a]+n[a]+i[a]/2>s[a]/2&&n[a]+i[a]-l-l*f>=0?n[a]+i[a]-l-l*f:n[a]+i[a]-l+l*f:n[a]+i[a]-l+l*f>=0?n[a]+i[a]-l+l*f:n[a]+i[a]-l-l*f];var h=c[u],p=c[a],d=t.fixed?"fixed":"absolute",v=(c[0]+o[0]>n[0]||c[0]2)switch(e.mode.charAt(2).toLowerCase()){case"s":return"s";case"v":return"v"}return null},onDocumentMouseDown:function(t){t||(t=window.event);var n=t.target||t.srcElement;n._jscLinkedInstance?n._jscLinkedInstance.showOnClick&&n._jscLinkedInstance.show():n._jscControlName?e.onControlPointerStart(t,n,n._jscControlName,"mouse"):e.picker&&e.picker.owner&&e.picker.owner.hide()},onDocumentTouchStart:function(t){t||(t=window.event);var n=t.target||t.srcElement;n._jscLinkedInstance?n._jscLinkedInstance.showOnClick&&n._jscLinkedInstance.show():n._jscControlName?e.onControlPointerStart(t,n,n._jscControlName,"touch"):e.picker&&e.picker.owner&&e.picker.owner.hide()},onWindowResize:function(t){e.redrawPosition()},onParentScroll:function(t){e.picker&&e.picker.owner&&e.picker.owner.hide()},_pointerMoveEvent:{mouse:"mousemove",touch:"touchmove"},_pointerEndEvent:{mouse:"mouseup",touch:"touchend"},_pointerOrigin:null,_capturedTarget:null,onControlPointerStart:function(t,n,r,i){var s=n._jscInstance;e.preventDefault(t),e.captureTarget(n);var o=function(s,o){e.attachGroupEvent("drag",s,e._pointerMoveEvent[i],e.onDocumentPointerMove(t,n,r,i,o)),e.attachGroupEvent("drag",s,e._pointerEndEvent[i],e.onDocumentPointerEnd(t,n,r,i))};o(document,[0,0]);if(window.parent&&window.frameElement){var u=window.frameElement.getBoundingClientRect(),a=[-u.left,-u.top];o(window.parent.window.document,a)}var f=e.getAbsPointerPos(t),l=e.getRelPointerPos(t);e._pointerOrigin={x:f.x-l.x,y:f.y-l.y};switch(r){case"pad":switch(e.getSliderComponent(s)){case"s":s.hsv[1]===0&&s.fromHSV(null,100,null);break;case"v":s.hsv[2]===0&&s.fromHSV(null,null,100)}e.setPad(s,t,0,0);break;case"sld":e.setSld(s,t,0)}e.dispatchFineChange(s)},onDocumentPointerMove:function(t,n,r,i,s){return function(t){var i=n._jscInstance;switch(r){case"pad":t||(t=window.event),e.setPad(i,t,s[0],s[1]),e.dispatchFineChange(i);break;case"sld":t||(t=window.event),e.setSld(i,t,s[1]),e.dispatchFineChange(i)}}},onDocumentPointerEnd:function(t,n,r,i){return function(t){var r=n._jscInstance;e.detachGroupEvents("drag"),e.releaseTarget(),e.dispatchChange(r)}},dispatchChange:function(t){t.valueElement&&e.isElementType(t.valueElement,"input")&&e.fireEvent(t.valueElement,"change")},dispatchFineChange:function(e){if(e.onFineChange){var t;typeof e.onFineChange=="string"?t=new Function(e.onFineChange):t=e.onFineChange,t.call(e)}},setPad:function(t,n,r,i){var s=e.getAbsPointerPos(n),o=r+s.x-e._pointerOrigin.x-t.padding-t.insetWidth,u=i+s.y-e._pointerOrigin.y-t.padding-t.insetWidth,a=o*(360/(t.width-1)),f=100-u*(100/(t.height-1));switch(e.getPadYComponent(t)){case"s":t.fromHSV(a,f,null,e.leaveSld);break;case"v":t.fromHSV(a,null,f,e.leaveSld)}},setSld:function(t,n,r){var i=e.getAbsPointerPos(n),s=r+i.y-e._pointerOrigin.y-t.padding-t.insetWidth,o=100-s*(100/(t.height-1));switch(e.getSliderComponent(t)){case"s":t.fromHSV(null,o,null,e.leavePad);break;case"v":t.fromHSV(null,null,o,e.leavePad)}},_vmlNS:"jsc_vml_",_vmlCSS:"jsc_vml_css_",_vmlReady:!1,initVML:function(){if(!e._vmlReady){var t=document;t.namespaces[e._vmlNS]||t.namespaces.add(e._vmlNS,"urn:schemas-microsoft-com:vml");if(!t.styleSheets[e._vmlCSS]){var n=["shape","shapetype","group","background","path","formulas","handles","fill","stroke","shadow","textbox","textpath","imagedata","line","polyline","curve","rect","roundrect","oval","arc","image"],r=t.createStyleSheet();r.owningElement.id=e._vmlCSS;for(var i=0;i=3&&(s=r[0].match(i))&&(o=r[1].match(i))&&(u=r[2].match(i))){var a=parseFloat((s[1]||"0")+(s[2]||"")),f=parseFloat((o[1]||"0")+(o[2]||"")),l=parseFloat((u[1]||"0")+(u[2]||""));return this.fromRGB(a,f,l,t),!0}}return!1},this.toString=function(){return(256|Math.round(this.rgb[0])).toString(16).substr(1)+(256|Math.round(this.rgb[1])).toString(16).substr(1)+(256|Math.round(this.rgb[2])).toString(16).substr(1)},this.toHEXString=function(){return"#"+this.toString().toUpperCase()},this.toRGBString=function(){return"rgb("+Math.round(this.rgb[0])+","+Math.round(this.rgb[1])+","+Math.round(this.rgb[2])+")"},this.isLight=function(){return.213*this.rgb[0]+.715*this.rgb[1]+.072*this.rgb[2]>127.5},this._processParentElementsInDOM=function(){if(this._linkedElementsProcessed)return;this._linkedElementsProcessed=!0;var t=this.targetElement;do{var n=e.getStyle(t);n&&n.position.toLowerCase()==="fixed"&&(this.fixed=!0),t!==this.targetElement&&(t._jscEventsAttached||(e.attachEvent(t,"scroll",e.onParentScroll),t._jscEventsAttached=!0))}while((t=t.parentNode)&&!e.isElementType(t,"body"))};if(typeof t=="string"){var h=t,p=document.getElementById(h);p?this.targetElement=p:e.warn("Could not find target element with ID '"+h+"'")}else t?this.targetElement=t:e.warn("Invalid target element: '"+t+"'");if(this.targetElement._jscLinkedInstance){e.warn("Cannot link jscolor twice to the same element. Skipping.");return}this.targetElement._jscLinkedInstance=this,this.valueElement=e.fetchElement(this.valueElement),this.styleElement=e.fetchElement(this.styleElement);var d=this,v=this.container?e.fetchElement(this.container):document.getElementsByTagName("body")[0],m=3;if(e.isElementType(this.targetElement,"button"))if(this.targetElement.onclick){var g=this.targetElement.onclick;this.targetElement.onclick=function(e){return g.call(this,e),!1}}else this.targetElement.onclick=function(){return!1};if(this.valueElement&&e.isElementType(this.valueElement,"input")){var y=function(){d.fromString(d.valueElement.value,e.leaveValue),e.dispatchFineChange(d)};e.attachEvent(this.valueElement,"keyup",y),e.attachEvent(this.valueElement,"input",y),e.attachEvent(this.valueElement,"blur",c),this.valueElement.setAttribute("autocomplete","off")}this.styleElement&&(this.styleElement._jscOrigStyle={backgroundImage:this.styleElement.style.backgroundImage,backgroundColor:this.styleElement.style.backgroundColor,color:this.styleElement.style.color}),this.value?this.fromString(this.value)||this.exportColor():this.importColor()}};return e.jscolor.lookupClass="jscolor",e.jscolor.installByClassName=function(t){var n=document.getElementsByTagName("input"),r=document.getElementsByTagName("button");e.tryInstallOnElements(n,t),e.tryInstallOnElements(r,t)},e.register(),e.jscolor}()); \ No newline at end of file diff --git a/lib/leaflet-hash.js b/lib/leaflet-hash.js deleted file mode 100644 index 29ba051..0000000 --- a/lib/leaflet-hash.js +++ /dev/null @@ -1,124 +0,0 @@ -(function(window) { - - L.Hash = function(map) { - - if (map) { - this.init(map); - } - }; - - L.Hash.parseHash = function(hash) { - if(hash.indexOf('#') === 0) { - hash = hash.substr(1); - } - var args = hash.split("/"); - if (args.length == 3) { - var zoom = parseInt(args[0], 10), - lat = parseFloat(args[1]), - lon = parseFloat(args[2]); - if (isNaN(zoom) || isNaN(lat) || isNaN(lon)) { - return false; - } else { - return { - center: new L.LatLng(lat, lon), - zoom: zoom - }; - } - } else { - return false; - } - }; - - L.Hash.formatHash = function(map) { - var center = map.getCenter(), - zoom = map.getZoom(), - precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); - - // cap zoom at 5 digits for beauty - return "#" + [parseFloat(zoom.toFixed(5)), - center.lat.toFixed(precision), - center.lng.toFixed(precision) - ].join("/"); - }, - - L.Hash.prototype = { - map: null, - lastHash: null, - - parseHash: L.Hash.parseHash, - formatHash: L.Hash.formatHash, - - init: function(map) { - this.map = map; - - // reset the hash - this.lastHash = null; - - if (!this.isListening) { - this.startListening(); - } - }, - - removeFrom: function(map) { - if (this.changeTimeout) { - clearTimeout(this.changeTimeout); - } - - if (this.isListening) { - this.stopListening(); - } - - this.map = null; - }, - - // defer hash change updates every changeDefer milliseconds - changeDefer: 250, - changeTimeout: null, - onMapMove: function() { - // bail if we're moving the map (updating from a hash), - // or if the map is not yet loaded - if (this.movingMap || !this.map._loaded) { - return false; - } - - var hash = this.formatHash(this.map); - if (this.lastHash != hash) { - this.lastHash = hash; - } - if (!this.changeTimeout) { - var that = this; - this.changeTimeout = setTimeout(function() { - - // update the hash - window.location.replace(hash); - - that.changeTimeout = null; - }, this.changeDefer); - } - }, - - movingMap: false, - - isListening: false, - startListening: function() { - this.map.on("moveend", this.onMapMove, this); - - this.isListening = true; - }, - - stopListening: function() { - this.map.off("moveend", this.onMapMove, this); - - this.isListening = false; - } - }; - L.hash = function(map) { - return new L.Hash(map); - }; - L.Map.prototype.addHash = function() { - this._hash = L.hash(this); - }; - L.Map.prototype.removeHash = function() { - this._hash.removeFrom(); - }; -})(window); diff --git a/next.config.mjs b/next.config.mjs new file mode 100644 index 0000000..7bcbd41 --- /dev/null +++ b/next.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + optimizePackageImports: ["@untitledui/icons"], + }, +}; + +export default nextConfig; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5364aa0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2683 @@ +{ + "name": "untitledui-nextjs-starter-kit", + "version": "0.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "untitledui-nextjs-starter-kit", + "version": "0.0.0", + "dependencies": { + "@tailwindcss/postcss": "^4.3.3", + "@untitledui/icons": "^0.0.23", + "d3-array": "^3.2.5", + "d3-contour": "^4.0.3", + "d3-geo": "^3.1.2", + "d3-interpolate": "^3.0.2", + "d3-scale": "^4.0.3", + "leaflet": "^1.9.4", + "next": "16.4.3", + "react": "^19.3.0", + "react-aria-components": "^1.22.1", + "react-dom": "^19.3.0", + "tailwind-merge": "^3.7.0", + "tailwindcss": "^4.3.3", + "tailwindcss-animate": "^1.0.7", + "tailwindcss-react-aria-components": "^2.2.0" + }, + "devDependencies": { + "@trivago/prettier-plugin-sort-imports": "^6.0.2", + "@types/d3-array": "^3.2.2", + "@types/d3-contour": "^3.0.6", + "@types/d3-geo": "^3.1.1", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-scale": "^4.0.9", + "@types/geojson": "^7946.0.16", + "@types/leaflet": "^1.9.22", + "@types/node": "^26.6.3", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "postcss": "^8.5.28", + "postcss-loader": "^8.2.1", + "prettier": "^3.9.9", + "prettier-plugin-tailwindcss": "^0.8.1", + "typescript": "^6.0.3" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz", + "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.13.tgz", + "integrity": "sha512-T5FRo+RzVHvGjWOuF1Ljc44b+NXAOazodHkGUHp4I3zCkQ4/sNVJm56h1S278LMR0bY5xBhztGziSbli8zObIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.13", + "@babel/types": "^7.29.11", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz", + "integrity": "sha512-fWegr+WsEsiMVW4KUNdIIzzMTZSzNiqhy0//6+2MRi2d5NFNsL/AYwBQW8eBnRbTBVRnDVzjaJEUqKaCWjMMdQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.13.tgz", + "integrity": "sha512-fJM50YYjlTiBhgdkGmC1tBpv+FMAyo4annf2X6Aks+0yRlaiVkYW/3f07xAfVVkYIoZ9fFZp+mEThUFnuybhkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.11" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.13.tgz", + "integrity": "sha512-0ve0xMlFA73dgY1oyJRY6RoW+/bncdcOQqg6jjYcVfXBK90OdLXjm9R6lmRetdZVrPjXkKIoMVBqYFPjcAlZ4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.13", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.13", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.11", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.11.tgz", + "integrity": "sha512-WCsemW40Cpc6gouC1SrY6PBaZ+oLRlEVY9XBvZBI7il/HyUrb2nJ505wE1MHEFyDZNnrWDEuy4jL50WVHfTTSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.11", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.3", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.3.tgz", + "integrity": "sha512-Xz4Tpyki7XyrpbUK1jR1AhdAdaXyhhY4lZ3neLodmhpuWfy2PAQN5B46sAiU4liOXGLkHypn/qU+jvfWSCYYLA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/colour": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", + "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.5.tgz", + "integrity": "sha512-QRUlFQ0WxvdWyqqG/WtI3iupfD5rBzmCHXSdPsY91sAtVtTo7Q4cb6zOccZ3gqEqkr0f1As1ehLqmEpDsRf+lg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.5.tgz", + "integrity": "sha512-+BR255RhDlpygUpOc/Jdt1nT6DQ3XG/ERo5wbcdOf5Q320dKtPCKPLR1LJs9VGXRaMa8l1uUa0tkCNOXiAxZUw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.5.tgz", + "integrity": "sha512-Y/z91nEZ4uIBX5X3nfTovjU9lHNKFYbL2lpHCLVNmXQK03VIZvXBBt0KxbPGp2SdGSF+2mQU4e+hQaWOt86iAw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.4.tgz", + "integrity": "sha512-5R89nBYiRdUlSWJxPhO+GVtaXzXSxKnRu/xqMn3KTA3L9EB9Oy/P+Nn2f2vlhPuUdy/Zusb2DarbyTpGCfEDuw==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.4.tgz", + "integrity": "sha512-iR2OKH80yi0U+dUplyh3/xdpFvps6YkCwsXenIJxqxR1v9o+xtKTGbS9H7cps+2Vxjc8B1j96p75NmTGjIhtpQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.4.tgz", + "integrity": "sha512-LmRtTsOHuvM2+wlO2Db37dx5MiZhB0FvSunciw48YjdOkZz9KAiRbm8ujeMOA1INqmei5NapFxYEK1D1ZSidmw==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.4.tgz", + "integrity": "sha512-Y3dgX/6lE2QhQb+Gxy0WZxfg9MEm/JBjamZpS2IklP7xIQoKN4hzAm7KcMVGtaVDt3neE9OKBC7vAfonA/Lr1A==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-ppc64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.4.tgz", + "integrity": "sha512-Le6boB8Tai0Nis+gIxIpKx68UDVVIqdR8Tin5Yf1z2LJJQLDJvCDRqRu+jC2qCoD+eIomonmOwB4smBRxfVpYQ==", + "cpu": [ + "ppc64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-riscv64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.4.tgz", + "integrity": "sha512-aHkkIEHPRdQEegJN20MLmGtxYD9R2wQr3Cwpddnu5+YKMt6Uzax7S9h5gpZTo8wyrGuZSlfQ63OevL5mTyOC7Q==", + "cpu": [ + "riscv64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.4.tgz", + "integrity": "sha512-ra/mB6MikESDUO7Yg+Mi95bFBb9GsObURuhnOv3OqknjGe9sZrG8tCe9q0xSIGrtLgvgw0gKnFWcK4blSgQOuQ==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.4.tgz", + "integrity": "sha512-GJ//SSXbnwSDes02umB3nDJLFcQzw8a18V8fyhqr6tV515tOEMdImjjxj1AoafMRz56F3PHgftnj1QEKSU1zkw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.4.tgz", + "integrity": "sha512-hvulFwtjUcagsis6BBxHwGFwWoNZjgYmULGVrZcyfNbjA8hKILbRxGg15/7w5HDyXHXUos/j6baAWqnCyQ2DWA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.4.tgz", + "integrity": "sha512-6zXKeE/p39I1AmA3cJG35eyBGNqNddLnUXjhwBnsGjFPWqf5VKkDBEqaEkPDoTEtkxwi2vv8Tcr2mDyP4So7Fg==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.5.tgz", + "integrity": "sha512-LEaXK2WdXVK5ykcw0buWyPMsmLLL2vpHLD6yrNSW+JGEL3BZPA4tpKN6iaMc4AxTTAoaX/sU1rOL51lcIz48ZQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.5.tgz", + "integrity": "sha512-LYVx5JTsOM2CBzmxreh+nl64/3H6Xb09iSLknqH47z2T2DFFxDeFLP5y4dJwe6H7uGQlHPyEEtIqyo3DYsRwdQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-ppc64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.5.tgz", + "integrity": "sha512-QVxAAq8evVRI9ia2vqgwrmWucn5Dfv+JdWzj75pD8omHLPSP7f8p20O8jxzjCcuCEQEOtYOZUmX1hkiZ0kdevA==", + "cpu": [ + "ppc64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-ppc64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-riscv64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.5.tgz", + "integrity": "sha512-LtdreXguaavKODPIfzJ4kffx7UNt1omwtK0rch4EBbbSTXPnxWmYSayXdLJw0fJzQ97kHt1gL/yh4tvU+nCyRQ==", + "cpu": [ + "riscv64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-riscv64": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.5.tgz", + "integrity": "sha512-UZasTOFiYzotTsGOCu42BfUzP6Tu6Do/947iRm1RsLKvlllxwGcn4RN27LibGWceix4Y+Pmw3jsnTcCQIgWjqA==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.3.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.5.tgz", + "integrity": "sha512-SxFtLTeJInhAA9Q836kux2vZNeOBQEx658qvbboZScr0wIARym3IcGmW7KpVD5sbVg0Ojy+udFQdayYIZyoNog==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.5.tgz", + "integrity": "sha512-9HbMclmI1zlNkFRs3z9/eBtDjfD0sGlrX1z6b1qwmiFY5ElDLh4BC0LPBdVp7z1DXFiKlIcznf+ZlsuZzLxQqg==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.5.tgz", + "integrity": "sha512-4KOphqB035HrVdqLZfCgMzzERrQkkzOwRhl4OAkRO1YCldbaFjySXMaK534Mo0V+LndnlJk+sbUyLeU0ULyD1A==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.3.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.5.tgz", + "integrity": "sha512-Ptsga1su4tQx+LLF1ECS9U6nz5kmrXKo6XVbtR48Ke3ZRxxgaWBu7IDtEe1quo8hiupwm6WFqxVlXaSf7IINGQ==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.3" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.5.tgz", + "integrity": "sha512-hfhF/FmoQyTUkA0bIKFOtw536BQSeBMe6BF6QyWlrPxT754+TFLaZ7sKKTfvvM0yJgKgaYTwnFCIZ/GuDw5SUA==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/sharp-wasm32": "0.35.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-arm64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.5.tgz", + "integrity": "sha512-X4t7g+7ZA5DKblCBEXGjUqqemj4vczING/5viFwAL8h4N3qYeyjwdCvRLHi4EdOUI+2Z7UFlp1VM+p/AuEtm6Q==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.5.tgz", + "integrity": "sha512-5Zm82LoBc43nhwNybZlG7Y1KO//Zhsn306fQl29ZOuStHLGTo3BWL83q3cznX0poxSAMuYL1On/BHBxkBeKr6A==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.5.tgz", + "integrity": "sha512-x76eH0vEiHlcMQu8Y8IenntaACtddpT6W0wmXtWrnKcnKI7ME5DdgqhAD6SEWOEl1v2zDvkZDhFA9KnURwpfqg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@internationalized/date": { + "version": "3.12.4", + "resolved": "https://registry.npmjs.org/@internationalized/date/-/date-3.12.4.tgz", + "integrity": "sha512-M1dEn4c1U1HsSlaVR8upZtSqvXrTkHDfv18H01uCSJyjVLDxnBR38v/fMxecmlwXKR4i9HeZcmgQAPE6A+aGJQ==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/number": { + "version": "3.6.9", + "resolved": "https://registry.npmjs.org/@internationalized/number/-/number-3.6.9.tgz", + "integrity": "sha512-GgWy2caO/+hV9sT/oCTWGHvbjmJMPT9kkXzoKRLnkEq7adghhR7UprhvY9sXrKfrp3NcDfhF6QQHXR1KfQbB8g==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@internationalized/string": { + "version": "3.2.10", + "resolved": "https://registry.npmjs.org/@internationalized/string/-/string-3.2.10.tgz", + "integrity": "sha512-PDx6//vHSpRnHfxqMqto11zQvhsaU74O3mKv2F/0eicGZcl9NLjQmGlbHz/LsJh5tLKp4A4L7ZVTzN1/MmMTvA==", + "license": "Apache-2.0", + "dependencies": { + "@swc/helpers": "^0.5.0" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@next/env": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.4.3.tgz", + "integrity": "sha512-a7tz1QCcWWkwC/rPJcPMJk/UNRu9CFhDd3OHYE5hddbJrs8BqRTalxI6GlKCWEGzRLHN+S/B7a4PAHCtm25aBQ==", + "license": "MIT" + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.3.tgz", + "integrity": "sha512-4yq5EXBt+VDtAZwTyfijaFp5D2C5oz3Ikw5a2kiX2K9nw72vrZX2q5UuAlkPtUMO1tQz6JTKpaKWjS7OOefTDQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.4.3.tgz", + "integrity": "sha512-1Rvkn/LUhnEF8WxnRkOrqGhbwXui1bkWFxONKIbN+r7C+9KHDSZ+iUoj4CzymVMWUyZIyx9szcb9+kbKhD500A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.4.3.tgz", + "integrity": "sha512-dCKpXErGg3abUM6t5JTJV/ottVBTStJB5lRJTbgdUdQaQ+jNHiK56Hsc0YAEg/rinSh42hYG3HWiYmEMdilnxg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.4.3.tgz", + "integrity": "sha512-WwYbhWkhYBvBtib1K4W0kL8MW2snBD2A5b3SN4vPpqalZbZs5+bQ1yzAdepVoOqrFfEW3dmREy4Zs1MrWcrFbw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.4.3.tgz", + "integrity": "sha512-QymWvfUwDEC5/lpJOsxX5VAbyoR2D13+2Jsjz4be7gT4LPqEgdpkeLIR/HFfCLgKzoMhpJbp6I/JnogyNZHcaA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.4.3.tgz", + "integrity": "sha512-Vmjd9D7DpA4Vv3cUlr82QWgJD/BQZmns801vtC5VWHg6jCIA4a5vMKpQnfrM2pyCrp2eV0ENdUg4PlN3bAc2Yw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.4.3.tgz", + "integrity": "sha512-lG1pPdPLk7gTyGb00n5JIMLdBJgjb+UEhSdZvFbhUMg0DyC6z4B7RnpSKF9VCd4imkLObVbXOFA0kFCpw7yFow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.4.3.tgz", + "integrity": "sha512-NmQC9RkJdnPXRaMxtkCzobB3teVjqF33oSOnwDfi+XYGWWYpt+xlGK3mtktSKLbdjwaWbJu2OkkMiRFGUUncKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@react-types/shared": { + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.37.0.tgz", + "integrity": "sha512-VNlZuifu4tHdxWPfHvoMfKsHA9kVTCGkduv5vKVTdinm8wHFJNhyVB5RzIMiRFj0q3RKoCbP68u2zaoBSiFueg==", + "license": "Apache-2.0", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/@swc/helpers": { + "version": "0.5.24", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.24.tgz", + "integrity": "sha512-6tcXo4kA8HgGO7AVmawB57RYi/piBPYVHVcO6dXjX+SZw8GoHmi262sp4v2Ha2kNP9Cx84qei27yT+fjS1lxgg==", + "license": "Apache-2.0" + }, + "node_modules/@tailwindcss/node": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.3.4.tgz", + "integrity": "sha512-b7QK/QG04v3j04NbpG82XLCedRyulnkuwUDwRa9oK/ypnJIzMapz2Wg+0TltVczjUzDaJJHIa1+jj9aMUOFMTQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.5", + "enhanced-resolve": "^5.24.5", + "jiti": "^2.7.0", + "lightningcss": "1.33.0", + "magic-string": "^1.1.0", + "source-map-js": "^1.2.1", + "tailwindcss": "4.3.4" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.3.4.tgz", + "integrity": "sha512-riqsRw2sLF7B1sNCCkTl3KYXEqvhTiABzZ/4oxxHfZ3c3Td60bCBfOrUuUGOiHagd3E7V+zG46c+XBDWZGFBcg==", + "license": "MIT", + "engines": { + "node": ">= 20" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.3.4", + "@tailwindcss/oxide-darwin-arm64": "4.3.4", + "@tailwindcss/oxide-darwin-x64": "4.3.4", + "@tailwindcss/oxide-freebsd-x64": "4.3.4", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.3.4", + "@tailwindcss/oxide-linux-arm64-gnu": "4.3.4", + "@tailwindcss/oxide-linux-arm64-musl": "4.3.4", + "@tailwindcss/oxide-linux-x64-gnu": "4.3.4", + "@tailwindcss/oxide-linux-x64-musl": "4.3.4", + "@tailwindcss/oxide-wasm32-wasi": "4.3.4", + "@tailwindcss/oxide-win32-arm64-msvc": "4.3.4", + "@tailwindcss/oxide-win32-x64-msvc": "4.3.4" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.3.4.tgz", + "integrity": "sha512-qkjxt+EFocOi3mtsUS3ndlj6yj4SHubwBdvBynLnS8T4mvSzX0Enn0CpypJKmBEtczUAXOHVZKA2zTGzSNU2Sg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.3.4.tgz", + "integrity": "sha512-i/awUA/7mJQAbA3AzCW8aQtUh5goMieUdF1zOoclLaJUCxDvsXQ8VJqpQZY3dfcHS6PJjd3tw25nNneD/mEviQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.3.4.tgz", + "integrity": "sha512-q56V9U6AnoYz/yj/alkDLhSQ9AZoXQSEl93f7WETa0+32CudbqM57BXdYXfMYPKudueXKaA+MG6L/0KRseF7Jg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.3.4.tgz", + "integrity": "sha512-r0W7A+Z9HTHh7bwihJl45E/zZKLjxD50/jToVF/E/RZHSl8+EWO+4yKKJpPMcUooT9hdoip3Zx1uWnbMg4N79g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.3.4.tgz", + "integrity": "sha512-3aih/sRxzeYfi2Bkh+bGYNf2YU9Llamb2mpA7BNF1obYzdgEeWDAMQzK6BSwzLhs/eTdOaSur92qrGfchagUtQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.3.4.tgz", + "integrity": "sha512-ktJSrPGQou4OqL88rDEx5CkeM9BjzrwZvYo8u7B7VL1zkBa2XBm351o4MQrek95qikb2M++26Y7w7vbH6Mgl8g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.3.4.tgz", + "integrity": "sha512-rt3hjUxT7utAtcB+df6wHyaXDyxhE7Zd1x7L/nvB5Jb2qBb8dZwFUoWqeRja1BaF5OlSqkKSjBmVXj9OsevmCQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.3.4.tgz", + "integrity": "sha512-XE3jO2yOOWjttpeGvpwf3/b274NMi3QapQSAI/EzsgrmYNan1Tw8dXT9JQkN7K0OgvuqiC8mcCyNvGzEkpVJPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.3.4.tgz", + "integrity": "sha512-PaAZ2FjJaZk9SQLYtKIfQWiFm/xtl7pX2MKPgCzn0mS/VU54yu+vP457AJdB2XFLxU3gO2abLCt8N7Lg4EbKNg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.3.4.tgz", + "integrity": "sha512-bjO9WmoM687p8AoCIyw1DUqzpAs4JICLvNmIPxIWDmhw8IOsdubtWQLShyJmISnDKtRy1BBApHVOUl6sNFQQJA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.11.3", + "@emnapi/runtime": "^1.11.3", + "@emnapi/wasi-threads": "^1.2.3", + "@napi-rs/wasm-runtime": "^1.2.2", + "@tybys/wasm-util": "^0.10.3", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.11.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.3", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.11.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.2.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@tybys/wasm-util": "^0.10.3" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "inBundle": true, + "license": "0BSD", + "optional": true + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.3.4.tgz", + "integrity": "sha512-jCrwCvWVtt7ruMXp6dFPd5KBNU8o1tWtEdZbTL/w/hvD3lUYZA0E2mmfvTzW4QLHIx5wGQU9HeY7997R1QTrEg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.3.4.tgz", + "integrity": "sha512-cge1wcedn1FrRSXL22lF5sNDYr8c9+VImk7zFYLPs12jkDmchSrZNfzWqOKub+5Hb/aYRdDEqERGGDKONBlOzQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 20" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.3.4.tgz", + "integrity": "sha512-6O0QLY+1CseO8ivlYK2Y7IxNPaQ8D5ypxrV+ws34mbxknWH824J+gEWtJ0gyFiYAudmdEeXdF8kqwgXIaZRyHw==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.3.4", + "@tailwindcss/oxide": "4.3.4", + "postcss": "^8.5.25", + "tailwindcss": "4.3.4" + } + }, + "node_modules/@trivago/prettier-plugin-sort-imports": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-6.0.2.tgz", + "integrity": "sha512-3DgfkukFyC/sE/VuYjaUUWoFfuVjPK55vOFDsxD56XXynFMCZDYFogH2l/hDfOsQAm1myoU/1xByJ3tWqtulXA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@babel/generator": "^7.28.0", + "@babel/parser": "^7.28.0", + "@babel/traverse": "^7.28.0", + "@babel/types": "^7.28.0", + "javascript-natural-sort": "^0.7.1", + "lodash-es": "^4.17.21", + "minimatch": "^9.0.0", + "parse-imports-exports": "^0.2.4" + }, + "engines": { + "node": ">= 20" + }, + "peerDependencies": { + "@vue/compiler-sfc": "3.x", + "prettier": "2.x - 3.x", + "prettier-plugin-ember-template-tag": ">= 2.0.0", + "prettier-plugin-svelte": "3.x", + "svelte": "4.x || 5.x" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + }, + "prettier-plugin-ember-template-tag": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + }, + "svelte": { + "optional": true + } + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/d3-contour": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/@types/d3-contour/-/d3-contour-3.0.6.tgz", + "integrity": "sha512-BjzLgXGnCWjUSYGfH1cpdo41/hgdWETu4YxpezoztawmqsvCeep+8QGfiY6YbDvfgHz/DkjeIkkZVJavB4a3rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-array": "*", + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-geo": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-geo/-/d3-geo-3.1.1.tgz", + "integrity": "sha512-65Emv9fQiQQqphLlRkuQ5ypPsOmWPhtBGCMv61JDPEPMvsx+gzhGf74yw1a78xFKPj6zw4AgQICJoQv0vK9M2w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/geojson": { + "version": "7946.0.16", + "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", + "integrity": "sha512-6C8nqWur3j98U6+lXDfTUWIfgvZU+EumvpHKcYjujKH7woYyLj2sUmff0tRhrqM7BohUw7Pz3ZB1jj2gW9Fvmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/leaflet": { + "version": "1.9.22", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.22.tgz", + "integrity": "sha512-h3lhECYEKDasG7LFHu+GiHqAvsgLuQvlJvVZzJDGONo3sEL+wUOqSFLnwkZlK0qVxnxbuGFW8iBlJNYs5wgndA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/geojson": "*" + } + }, + "node_modules/@types/node": { + "version": "26.6.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-26.6.6.tgz", + "integrity": "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~8.9.0" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@untitledui/icons": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/@untitledui/icons/-/icons-0.0.23.tgz", + "integrity": "sha512-WoBtPCXaxweF0RXS9IuqgEeJkBmT7A9SszPHB+vQJOxeAl0hDTFXHlWXsooYniXQ0JmP4+7RADdhWb39GWFT8g==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz", + "integrity": "sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001820", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz", + "integrity": "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.5.tgz", + "integrity": "sha512-Th8qGXoBalmWsstpkuhmBuTsBQIFqYOJtjlBvNVvBfJ8E3uYusBUwUR8HU20U0qKKQkgCvKyR1YjiozWadk5QA==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.1.tgz", + "integrity": "sha512-lfab8x0Bj0UO7SNsY8dZSbNIA6ipbkE3X0NG9nqjCu6CfCHZseuDnOSJY7hidKrjpP++i1M2qT+FlAdPKYRilA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.3.tgz", + "integrity": "sha512-5DYURc8KwXPOhSUBblU0P9rVcArsSPxPOxsfO0Eud0s28oU0sB2e80aRHRL9AZW3usT8ikFKyIHpHHLNWLdRVA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.3.tgz", + "integrity": "sha512-+F+/49swqmGuQJmSKnVMIDH/QkEFV5li9QRj0xm/KhyAUBv099O5PrvpQFxBp24PYph17Mnvt9mjKQV0ii0nMA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.2.tgz", + "integrity": "sha512-5R8ZgDQorIxmauv7R6gpE/ndz+Z0qAZssigq0aXFdvPHdY41RqtW02jrRUQAL724NdPABYXo1+gQEGBBr7uKNg==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.2.tgz", + "integrity": "sha512-IRzGU62DtxhvsnSTWMQIdnoZ6gA+BCWwBon1EJI6SjoN595799b6z/QRBzXfFhE8iupUPp3042yBNo0STPqnzQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-66cWobCNzcMvlfoaDHCxuIv9TBUWG1BLOWdv9kHHFuCr1JdqAWPvTmsaEYDD5NRMyWZ5+7u0GqiCDfMXMqh3hg==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.1.tgz", + "integrity": "sha512-Ze7rjrhMzdeulh0/hjcfFj7XLAHOfowtEMfsoG6DU866HPynQNW7Wfen6hzXcqKTv8XzxvF6Hzr5XD6bMHr2ug==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.1.tgz", + "integrity": "sha512-22XaqjyCjpqgda20erhsOjGmC3vvYwTNYniKAd45W5UWLDF43moeOuV9qYpRqobIlsnK0UhnKZjUUJm3VgORUA==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.26.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.1.tgz", + "integrity": "sha512-PPnnSJq3azKOIewXR/uppBV/6h98dprBJ/WaQ9Qgj10xX1lJLNfg+XShpbsrSqcDSy2yYYyhfXM1wWhftqgyzw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", + "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/next": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.4.3.tgz", + "integrity": "sha512-OqxKTGi9u80FydydsMNYNjnuje9Au+kvHlt2GZ//ftdpDHm1dxKqAaENoOOTX5wKzn+9vaKUNIfZeyZUuLhCnA==", + "license": "MIT", + "dependencies": { + "@next/env": "16.4.3", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.4.3", + "@next/swc-darwin-x64": "16.4.3", + "@next/swc-linux-arm64-gnu": "16.4.3", + "@next/swc-linux-arm64-musl": "16.4.3", + "@next/swc-linux-x64-gnu": "16.4.3", + "@next/swc-linux-x64-musl": "16.4.3", + "@next/swc-win32-arm64-msvc": "16.4.3", + "@next/swc-win32-x64-msvc": "16.4.3", + "sharp": "^0.35.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.1.tgz", + "integrity": "sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^2.5.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/prettier": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.8.1.tgz", + "integrity": "sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/react": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-aria": { + "version": "3.53.1", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.53.1.tgz", + "integrity": "sha512-dikisyOjJd3D2fFrIDWmVWzqiLz5B86GzZv1zxs7xI4dVc0JIz/1Wa52I20gYvVL2WXeFXlygYsle/QcOMhtcg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.9", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.37.0", + "@swc/helpers": "^0.5.0", + "aria-hidden": "^1.2.3", + "clsx": "^2.0.0", + "react-stately": "3.51.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-aria-components": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.22.1.tgz", + "integrity": "sha512-UTHaYHOaM71bOCjGPF50DcLywlrwAodE63IP64LB4kO+OizatQEL8TesVEOM8+TkBeSgHoflJ45388IlJfa7aw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.37.0", + "@swc/helpers": "^0.5.0", + "client-only": "^0.0.1", + "react-aria": "3.53.1", + "react-stately": "3.51.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-dom": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", + "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.1" + } + }, + "node_modules/react-stately": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.51.0.tgz", + "integrity": "sha512-YJcmkAXu2XOQP97hO/qz/cC3qv9kni3oVhwBlxWr358BHePJUpY15XwL3YSRMKxYt4EkslPqAz11JRirMfANGw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.9", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.37.0", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.6.tgz", + "integrity": "sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, + "node_modules/tailwind-merge": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz", + "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.4.tgz", + "integrity": "sha512-PCJZTFFdG+sL3gvnP1Do9o6GbkvH2q0p04xSs3f7cSvmAeK+sttG+/7ncK1fdRwKFfC7n+wDDJaOMBSefkI9ow==", + "license": "MIT" + }, + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "license": "MIT", + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/tailwindcss-react-aria-components": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tailwindcss-react-aria-components/-/tailwindcss-react-aria-components-2.2.0.tgz", + "integrity": "sha512-iNVDSQd6haXWiu4RakjaYnCUnLw/nOEFSFHEelFeIkT/0f8HSrJ23fwt59F93yNw4FrjjHDXIGHgEtqProNcmQ==", + "license": "Apache-2.0", + "peerDependencies": { + "tailwindcss": "^4.0.0" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tslib": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz", + "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/use-sync-external-store": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..e3bc5b2 --- /dev/null +++ b/package.json @@ -0,0 +1,49 @@ +{ + "name": "contours", + "version": "2.0.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "typecheck": "tsc --noEmit", + "format": "prettier --write ." + }, + "dependencies": { + "@tailwindcss/postcss": "^4.3.3", + "@untitledui/icons": "^0.0.23", + "d3-array": "^3.2.5", + "d3-contour": "^4.0.3", + "d3-geo": "^3.1.2", + "d3-interpolate": "^3.0.2", + "d3-scale": "^4.0.3", + "leaflet": "^1.9.4", + "next": "16.4.3", + "react": "^19.3.0", + "react-aria-components": "^1.22.1", + "react-dom": "^19.3.0", + "tailwind-merge": "^3.7.0", + "tailwindcss": "^4.3.3", + "tailwindcss-animate": "^1.0.7", + "tailwindcss-react-aria-components": "^2.2.0" + }, + "devDependencies": { + "@trivago/prettier-plugin-sort-imports": "^6.0.2", + "@types/d3-array": "^3.2.2", + "@types/d3-contour": "^3.0.6", + "@types/d3-geo": "^3.1.1", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-scale": "^4.0.9", + "@types/geojson": "^7946.0.16", + "@types/leaflet": "^1.9.22", + "@types/node": "^26.6.3", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "postcss": "^8.5.28", + "postcss-loader": "^8.2.1", + "prettier": "^3.9.9", + "prettier-plugin-tailwindcss": "^0.8.1", + "typescript": "^6.0.3" + }, + "description": "Draw and export simple contour maps in the browser." +} diff --git a/palette.png b/palette.png deleted file mode 100644 index 558a7bb53a4e0efc1e0485c88ff46b31315056d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10556 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 TeuW9=L4~fTtDnm{r-UW|Ly|mq diff --git a/src/app/icon.svg b/src/app/icon.svg new file mode 100644 index 0000000..49f5abc --- /dev/null +++ b/src/app/icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..3ee48af --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,32 @@ +import type { Metadata, Viewport } from "next"; +import { Inter } from "next/font/google"; +import { RouteProvider } from "@/providers/router-provider"; +import "@/styles/globals.css"; +import { cx } from "@/utils/cx"; + +const inter = Inter({ + subsets: ["latin"], + display: "swap", + variable: "--font-inter", +}); + +export const metadata: Metadata = { + title: "Contours", + description: "Draw and export simple contour maps of anywhere on Earth. Pick a place, tune the interval and style, then download GeoJSON, SVG or PNG.", +}; + +export const viewport: Viewport = { + themeColor: "#ffffff", + width: "device-width", + initialScale: 1, +}; + +export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { + return ( + + + {children} + + + ); +} diff --git a/src/app/page.tsx b/src/app/page.tsx new file mode 100644 index 0000000..e7c8b02 --- /dev/null +++ b/src/app/page.tsx @@ -0,0 +1,5 @@ +import { ContoursAppLoader } from "@/components/contours/contours-app-loader"; + +export default function Page() { + return ; +} diff --git a/src/components/application/color-picker/color-picker-context.tsx b/src/components/application/color-picker/color-picker-context.tsx new file mode 100644 index 0000000..bd610c8 --- /dev/null +++ b/src/components/application/color-picker/color-picker-context.tsx @@ -0,0 +1,90 @@ +"use client"; + +import type { ReactNode } from "react"; +import { createContext, useContext, useMemo, useState } from "react"; +import type { Color } from "react-aria-components"; +import { ColorPicker as AriaColorPicker, parseColor } from "react-aria-components"; +import type { ColorDisplayFormat } from "./color-picker-utils"; + +/** Color picker state. */ +export interface ColorPickerState { + /** Currently selected color. */ + color: Color; + /** Active color format for the value input. */ + colorFormat: ColorDisplayFormat; +} + +/** Actions for the color picker. */ +export interface ColorPickerActions { + /** Set the active color. */ + setColor: (color: Color) => void; + /** Switch the color format (hex, rgb, css, hsl, hsb). */ + setColorFormat: (format: ColorDisplayFormat) => void; +} + +export interface ColorPickerContextValue { + state: ColorPickerState; + actions: ColorPickerActions; +} + +const ColorPickerContext = createContext(null); +const ColorPickerProvider = ColorPickerContext.Provider; + +export const useColorPicker = (): ColorPickerContextValue => { + const ctx = useContext(ColorPickerContext); + if (!ctx) throw new Error("useColorPicker must be used within "); + return ctx; +}; + +/** Non-throwing version — returns null when outside a Provider. */ +export const useColorPickerSafe = (): ColorPickerContextValue | null => useContext(ColorPickerContext); + +const toHsb = (value: string | Color): Color => (typeof value === "string" ? parseColor(value) : value).toFormat("hsb"); + +/** Props for the color state provider. */ +export interface ProviderProps { + /** Initial color value as a CSS string or React Aria Color object. */ + defaultValue?: string | Color; + /** Controlled color value as a CSS string or React Aria Color object. */ + value?: string | Color; + /** Initial color format for the value input. */ + defaultColorFormat?: ColorDisplayFormat; + /** Called when the selected color changes. */ + onChange?: (color: Color) => void; + /** The color picker parts that read and update the shared color state. */ + children: ReactNode; +} + +/** Color state provider. Wraps children in both context and AriaColorPicker. */ +export const Provider = ({ defaultValue = "#7F56D9", value, defaultColorFormat = "hex", onChange, children }: ProviderProps) => { + const [internalColor, setInternalColor] = useState(() => toHsb(value ?? defaultValue)); + const [colorFormat, setColorFormat] = useState(defaultColorFormat); + + // A controlled value always wins; it is parsed once per value. + const isControlled = value !== undefined; + const controlledColor = useMemo(() => (value === undefined ? undefined : toHsb(value)), [value]); + const color = controlledColor ?? internalColor; + + // onChange fires here, when the color changes, instead of from an effect watching state. + const actions = useMemo( + () => ({ + setColor: (next) => { + const hsb = next.toFormat("hsb"); + if (hsb.toString("hsba") === color.toString("hsba")) return; + if (!isControlled) setInternalColor(hsb); + onChange?.(hsb); + }, + setColorFormat, + }), + [color, isControlled, onChange], + ); + const state = useMemo(() => ({ color, colorFormat }), [color, colorFormat]); + + return ( + + actions.setColor(c)}> + {children} + + + ); +}; diff --git a/src/components/application/color-picker/color-picker-utils.ts b/src/components/application/color-picker/color-picker-utils.ts new file mode 100644 index 0000000..477a360 --- /dev/null +++ b/src/components/application/color-picker/color-picker-utils.ts @@ -0,0 +1,5 @@ +import type React from "react"; + +export type ColorDisplayFormat = "hex" | "rgb" | "css" | "hsl" | "hsb"; + +export const selectAllOnFocus: React.FocusEventHandler = (e) => e.target.select(); diff --git a/src/components/application/color-picker/color-picker.tsx b/src/components/application/color-picker/color-picker.tsx new file mode 100644 index 0000000..54cae87 --- /dev/null +++ b/src/components/application/color-picker/color-picker.tsx @@ -0,0 +1,452 @@ +"use client"; + +import type React from "react"; +import type { ReactNode } from "react"; +import { useRef, useState } from "react"; +import { Dropper, Plus } from "@untitledui/icons"; +import type { + ColorAreaProps as AriaColorAreaProps, + ColorSliderProps as AriaColorSliderProps, + ColorSwatchPickerItemProps as AriaColorSwatchPickerItemProps, + ColorThumbProps as AriaColorThumbProps, + Color, + ColorSpace, +} from "react-aria-components"; +import { + ColorArea as AriaColorArea, + ColorField as AriaColorField, + ColorSlider as AriaColorSlider, + ColorSwatch as AriaColorSwatch, + ColorSwatchPicker as AriaColorSwatchPicker, + ColorSwatchPickerItem as AriaColorSwatchPickerItem, + ColorThumb as AriaColorThumb, + Input as AriaInput, + SliderTrack as AriaSliderTrack, + parseColor, +} from "react-aria-components"; +import { Button, type ButtonProps } from "@/components/base/buttons/button"; +import { Select, type SelectProps } from "@/components/base/select/select"; +import { cx } from "@/utils/cx"; +import { Provider, useColorPicker } from "./color-picker-context"; +import type { ColorDisplayFormat } from "./color-picker-utils"; +import { selectAllOnFocus } from "./color-picker-utils"; + +const COLOR_FORMAT_ITEMS = [ + { id: "hex", label: "Hex" }, + { id: "rgb", label: "RGB" }, + { id: "css", label: "CSS" }, + { id: "hsl", label: "HSL" }, + { id: "hsb", label: "HSB" }, +]; + +const ColorThumb = ({ className, ...props }: AriaColorThumbProps) => ( + + cx( + "size-5 cursor-grab rounded-full border-[3px] border-fg-white shadow-md outline-0 outline-focus-ring focus-visible:outline-2 focus-visible:outline-offset-2", + state.isDragging && "cursor-grabbing", + typeof className === "function" ? className(state) : className, + ) + } + {...props} + /> +); + +const ColorPickerArea = ({ style, className, ...props }: AriaColorAreaProps) => ( + ({ ...defaultStyle, borderRadius: 8, ...style })} + className={(state) => + cx( + "relative aspect-square w-full shrink-0 rounded-lg ring-[0.5px] ring-alpha-black/10 ring-inset", + typeof className === "function" ? className(state) : className, + ) + } + > + + +); + +const HueSlider = ({ className, ...props }: Omit) => ( + cx("w-full", typeof className === "function" ? className(state) : className)} + > + ({ ...defaultStyle, borderRadius: 9999 })} + className="relative h-3 w-full cursor-pointer rounded-full ring-[0.5px] ring-alpha-black/10 ring-inset" + > + ({ ...defaultStyle, top: "50%" })} /> + + +); + +const AlphaSlider = ({ className, ...props }: Omit) => ( + cx("w-full", typeof className === "function" ? className(state) : className)} + > + ({ + ...defaultStyle, + borderRadius: 9999, + background: `${defaultStyle.background}, repeating-conic-gradient(#ccc 0% 25%, white 0% 50%) 50% / 8px 8px`, + })} + className="relative h-3 w-full cursor-pointer rounded-full ring-[0.5px] ring-alpha-black/10 ring-inset" + > + ({ ...defaultStyle, top: "50%" })} /> + + +); + +const EyeDropperButton = (props: ButtonProps) => { + const { actions } = useColorPicker(); + + const isSupported = typeof window !== "undefined" && "EyeDropper" in window; + if (!isSupported) return null; + + return ( + + )} +
-
-

Contours

- -
- - by Axis Maps - -
- -
- - - -
- -
-
-

Download

- -
-

Roads and labels are only there to help you find your area. They are not included in any download.

- -
-

GeoJSON

-
-

Contours for GIS or mapping software, as overlapping MultiPolygon features with an elevation property.

-

Geodata only. Styles are not included.

- -
-
- -
-

PNG image

-
-

A raster image of the map at screen size, with your chosen styles.

- -
-
- -
-

SVG

-
-

Contours as vector graphics, one path per elevation.

-

Illuminated contours export as plain polygons. Recreate the lighting effect in your graphics editor.

- -
-
-
- -
-
-

Style

- -
- - - -
-

Map type

-
- - -
-
- -
-

Contour interval

-
-
-
- Every - -
- - -
-
-
-
- Index line every - -
-
-
- -
-

Contour style

-
-
-
Contour color
-
Line weight
-
Index line weight
-
- -
-
- -
-

Background

-
- - -
- Fill color -
- -
-
- Low color -
-
- High color -
-
-
-
- -
-

Bathymetry

-
- - -
- Fill color -
- -
-
- Deep color -
-
- Shallow color -
-
-
-
- -

Elevation comes from Mapzen's open terrain tiles, so contours cover the whole world. How it works

-
- -
-
+ {isSolid ? ( + onSelect?.(color.toString("hex"))}> + {colors.map((c) => ( + + ))} + + ) : ( +
+ {colors.map((c, i) => ( +
+ )} +
+ ); +}; + +/** All-in-one solid color picker layout */ +const ColorPickerSolid = () => ( +
+ +
+
+ +
+ + +
+
+
+ + +
+
+
+); + +/** Card container for the color picker. */ +const ColorPickerDialog = ({ + className, + children, +}: { + /** Additional class name for the card. Useful for overriding the default fixed width. */ + className?: string; + /** The picker sections to render inside the card. */ + children?: ReactNode; +}) =>
{children}
; + +/** Compound export */ +const ColorPicker = Object.assign(ColorPickerSolid, { + Dialog: ColorPickerDialog, + Provider, + Area: ColorPickerArea, + HueSlider, + AlphaSlider, + EyeDropper: EyeDropperButton, + ColorFormatSelect, + ColorValueInput, + SavedColors, + SwatchItem, +}); + +export { ColorPicker }; diff --git a/src/components/application/loading-indicator/loading-indicator.tsx b/src/components/application/loading-indicator/loading-indicator.tsx new file mode 100644 index 0000000..51e2c6b --- /dev/null +++ b/src/components/application/loading-indicator/loading-indicator.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { cx } from "@/utils/cx"; + +const styles = { + sm: { + root: "gap-4", + label: "text-sm font-medium", + spinner: "size-8", + }, + md: { + root: "gap-4", + label: "text-sm font-medium", + spinner: "size-12", + }, + lg: { + root: "gap-4", + label: "text-lg font-medium", + spinner: "size-14", + }, + xl: { + root: "gap-5", + label: "text-lg font-medium", + spinner: "size-16", + }, +}; + +interface LoadingIndicatorProps { + /** + * The visual style of the loading indicator. + * @default 'line-simple' + */ + type?: "line-simple" | "line-spinner" | "dot-circle"; + /** + * The size of the loading indicator. + * @default 'sm' + */ + size?: "sm" | "md" | "lg" | "xl"; + /** + * Optional text label displayed below the indicator. + */ + label?: string; +} + +export const LoadingIndicator = ({ type = "line-simple", size = "sm", label }: LoadingIndicatorProps) => { + const renderSpinner = () => { + if (type === "line-spinner") { + return ( + + + + ); + } + + if (type === "dot-circle") { + return ( + + + + + + + + + + + + + + + ); + } + + // Default case: type === "line-simple" + return ( + + + + + ); + }; + + return ( +
+ {renderSpinner()} + {label && {label}} +
+ ); +}; diff --git a/src/components/base/avatar/avatar.tsx b/src/components/base/avatar/avatar.tsx new file mode 100644 index 0000000..f4d6033 --- /dev/null +++ b/src/components/base/avatar/avatar.tsx @@ -0,0 +1,174 @@ +"use client"; + +import { type FC, type ReactNode, useState } from "react"; +import { User01 } from "@untitledui/icons"; +import { cx } from "@/utils/cx"; +import { AvatarOnlineIndicator, VerifiedTick } from "./base-components"; +import { AvatarCount } from "./base-components/avatar-count"; + +export interface AvatarProps { + /** + * The dimensions of the avatar, which also scale the initials, placeholder icon and status indicator. + */ + size?: "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; + /** + * The class name for the outer wrapper of the avatar. + */ + className?: string; + /** + * The class name for the main child of the avatar. + */ + contentClassName?: string; + /** + * The URL of the avatar image. Falls back to the initials, placeholder icon or placeholder if it is missing or fails to load. + */ + src?: string | null; + /** + * The alternative text for the avatar image. + */ + alt?: string; + /** + * Display an inner contrast border around the avatar image. + */ + contrastBorder?: boolean; + /** + * Whether the avatar should be rounded. + * @default true + */ + rounded?: boolean; + /** + * Display an outer border around the avatar. + */ + border?: boolean; + /** + * Display a badge (i.e. company logo). + */ + badge?: ReactNode; + /** + * Display a status indicator. + */ + status?: "online" | "offline"; + /** + * Display a verified tick icon. + * + * @default false + */ + verified?: boolean; + /** + * Display a count badge. + */ + count?: number; + /** + * The initials of the user to display if no image is available. + */ + initials?: string; + /** + * An icon to display if no image is available. + */ + placeholderIcon?: FC<{ className?: string }>; + /** + * A placeholder to display if no image is available. + */ + placeholder?: ReactNode; + + /** + * Whether the avatar should show a focus ring when the parent group is in focus. + * For example, when the avatar is wrapped inside a link. + * + * @default false + */ + focusable?: boolean; +} + +const styles = { + xs: { root: "size-6", rootWithBorder: "p-px", initials: "text-xs font-semibold", icon: "size-4" }, + sm: { root: "size-8", rootWithBorder: "p-px", initials: "text-sm font-semibold", icon: "size-5" }, + md: { root: "size-10", rootWithBorder: "p-px", initials: "text-md font-semibold", icon: "size-6" }, + lg: { root: "size-12", rootWithBorder: "p-[1.5px]", initials: "text-lg font-semibold", icon: "size-7" }, + xl: { root: "size-14", rootWithBorder: "p-0.5", initials: "text-xl font-semibold", icon: "size-8" }, + "2xl": { root: "size-16", rootWithBorder: "p-0.5", initials: "text-display-xs font-semibold", icon: "size-8" }, +}; + +export const Avatar = ({ + size = "md", + src, + alt, + initials, + placeholder, + placeholderIcon: PlaceholderIcon, + border, + badge, + status, + verified, + count, + focusable = false, + rounded = true, + className, + contentClassName, +}: AvatarProps) => { + const [isFailed, setIsFailed] = useState(false); + + const canShowImage = src && !isFailed; + + const renderMainContent = () => { + if (canShowImage) { + return {alt} setIsFailed(true)} />; + } + + if (initials) { + return {initials}; + } + + if (PlaceholderIcon) { + return ; + } + + return placeholder || ; + }; + + const renderBadgeContent = () => { + if (status) { + return ; + } + + if (verified) { + return ; + } + + if (count) { + return ; + } + + return badge; + }; + + return ( +
+
+ {renderMainContent()} +
+ {renderBadgeContent()} +
+ ); +}; diff --git a/src/components/base/avatar/base-components/avatar-add-button.tsx b/src/components/base/avatar/base-components/avatar-add-button.tsx new file mode 100644 index 0000000..f3da7dc --- /dev/null +++ b/src/components/base/avatar/base-components/avatar-add-button.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { Plus } from "@untitledui/icons"; +import type { ButtonProps as AriaButtonProps } from "react-aria-components"; +import { Tooltip as AriaTooltip, TooltipTrigger as AriaTooltipTrigger } from "@/components/base/tooltip/tooltip"; +import { cx } from "@/utils/cx"; + +const sizes = { + xs: { root: "size-6", icon: "size-4" }, + sm: { root: "size-8", icon: "size-4" }, + md: { root: "size-10", icon: "size-5" }, +}; + +interface AvatarAddButtonProps extends AriaButtonProps { + size: "xs" | "sm" | "md"; + title?: string; + className?: string; +} + +export const AvatarAddButton = ({ size, className, title = "Add user", ...props }: AvatarAddButtonProps) => ( + + + + + +); diff --git a/src/components/base/avatar/base-components/avatar-company-icon.tsx b/src/components/base/avatar/base-components/avatar-company-icon.tsx new file mode 100644 index 0000000..be7cade --- /dev/null +++ b/src/components/base/avatar/base-components/avatar-company-icon.tsx @@ -0,0 +1,26 @@ +"use client"; + +import { cx } from "@/utils/cx"; + +const sizes = { + xs: "size-2", + sm: "size-3", + md: "size-3.5", + lg: "size-4", + xl: "size-4.5", + "2xl": "size-5 ring-[1.67px]", +}; + +interface AvatarCompanyIconProps { + size: "xs" | "sm" | "md" | "lg" | "xl" | "2xl"; + src: string; + alt?: string; +} + +export const AvatarCompanyIcon = ({ size, src, alt }: AvatarCompanyIconProps) => ( + {alt} +); diff --git a/src/components/base/avatar/base-components/avatar-count.tsx b/src/components/base/avatar/base-components/avatar-count.tsx new file mode 100644 index 0000000..d088944 --- /dev/null +++ b/src/components/base/avatar/base-components/avatar-count.tsx @@ -0,0 +1,16 @@ +"use client"; + +import { cx } from "@/utils/cx"; + +interface AvatarCountProps { + count: number; + className?: string; +} + +export const AvatarCount = ({ count, className }: AvatarCountProps) => ( +
+
+ {count} +
+
+); diff --git a/src/components/base/avatar/base-components/avatar-online-indicator.tsx b/src/components/base/avatar/base-components/avatar-online-indicator.tsx new file mode 100644 index 0000000..15d770a --- /dev/null +++ b/src/components/base/avatar/base-components/avatar-online-indicator.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { cx } from "@/utils/cx"; + +const sizes = { + xs: "size-1.5", + sm: "size-2", + md: "size-2.5", + lg: "size-3", + xl: "size-3.5", + "2xl": "size-4", + "3xl": "size-4.5", + "4xl": "size-5", +}; + +interface AvatarOnlineIndicatorProps { + size: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl"; + status: "online" | "offline"; + className?: string; +} + +export const AvatarOnlineIndicator = ({ size, status, className }: AvatarOnlineIndicatorProps) => ( + + {/* Reflection */} + + + + + + + + + + +); diff --git a/src/components/base/avatar/base-components/index.tsx b/src/components/base/avatar/base-components/index.tsx new file mode 100644 index 0000000..4054d80 --- /dev/null +++ b/src/components/base/avatar/base-components/index.tsx @@ -0,0 +1,4 @@ +export * from "./avatar-add-button"; +export * from "./avatar-company-icon"; +export * from "./avatar-online-indicator"; +export * from "./verified-tick"; diff --git a/src/components/base/avatar/base-components/verified-tick.tsx b/src/components/base/avatar/base-components/verified-tick.tsx new file mode 100644 index 0000000..06cebff --- /dev/null +++ b/src/components/base/avatar/base-components/verified-tick.tsx @@ -0,0 +1,34 @@ +"use client"; + +import { cx } from "@/utils/cx"; + +const sizes = { + xs: "size-2.5", + sm: "size-3", + md: "size-3.5", + lg: "size-4", + xl: "size-4.5", + "2xl": "size-5", + "3xl": "size-6", + "4xl": "size-8", +}; + +interface VerifiedTickProps { + size: "xs" | "sm" | "md" | "lg" | "xl" | "2xl" | "3xl" | "4xl"; + className?: string; +} + +export const VerifiedTick = ({ size, className }: VerifiedTickProps) => ( + + + + +); diff --git a/src/components/base/button-group/button-group.tsx b/src/components/base/button-group/button-group.tsx new file mode 100644 index 0000000..e5a0aa6 --- /dev/null +++ b/src/components/base/button-group/button-group.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { type FC, type PropsWithChildren, type ReactNode, type RefAttributes, createContext, isValidElement, useContext } from "react"; +import { + ToggleButton as AriaToggleButton, + ToggleButtonGroup as AriaToggleButtonGroup, + type ToggleButtonGroupProps, + type ToggleButtonProps, +} from "react-aria-components"; +import { cx, sortCx } from "@/utils/cx"; +import { isReactComponent } from "@/utils/is-react-component"; + +export const styles = sortCx({ + common: { + root: [ + "group/button-group inline-flex h-max cursor-pointer items-center bg-primary font-semibold whitespace-nowrap text-secondary shadow-skeuomorphic ring-1 ring-primary outline-brand transition duration-100 ease-linear ring-inset", + // Hover and focus styles + "hover:bg-primary_hover hover:text-secondary_hover focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2", + // Disabled styles + "disabled:cursor-not-allowed disabled:text-secondary/50 disabled:*:opacity-50", + // Selected styles + "selected:bg-primary_hover selected:text-secondary_hover", + ].join(" "), + icon: "pointer-events-none text-fg-quaternary transition-[inherit] group-hover/button-group:text-fg-quaternary_hover group-selected/button-group:text-fg-quaternary_hover", + }, + + sizes: { + sm: { + root: "gap-1.5 px-3.5 py-2 text-sm not-last:pr-[calc(calc(var(--spacing)*3.5)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-3 data-icon-only:px-2.5", + icon: "size-5", + }, + md: { + root: "gap-1.5 px-4 py-2.5 text-sm not-last:pr-[calc(calc(var(--spacing)*4)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-3.5 data-icon-only:px-3", + icon: "size-5", + }, + lg: { + root: "gap-2 px-4.5 py-2.5 text-md not-last:pr-[calc(calc(var(--spacing)*4.5)+1px)] first:rounded-l-lg last:rounded-r-lg data-icon-leading:pl-4 data-icon-only:px-3.5", + icon: "size-5", + }, + }, +}); + +type ButtonSize = keyof typeof styles.sizes; + +const ButtonGroupContext = createContext<{ size: ButtonSize }>({ size: "md" }); + +interface ButtonGroupItemProps extends ToggleButtonProps, RefAttributes { + iconLeading?: FC<{ className?: string }> | ReactNode; + iconTrailing?: FC<{ className?: string }> | ReactNode; + onClick?: () => void; + className?: string; +} + +export const ButtonGroupItem = ({ + iconLeading: IconLeading, + iconTrailing: IconTrailing, + children, + className, + ...otherProps +}: PropsWithChildren) => { + const context = useContext(ButtonGroupContext); + + if (!context) { + throw new Error("ButtonGroupItem must be used within a ButtonGroup component"); + } + + const { size } = context; + + const isIcon = (IconLeading || IconTrailing) && !children; + + return ( + + {isReactComponent(IconLeading) && } + {isValidElement(IconLeading) && IconLeading} + + {children} + + {isReactComponent(IconTrailing) && } + {isValidElement(IconTrailing) && IconTrailing} + + ); +}; + +interface ButtonGroupProps extends Omit, RefAttributes { + size?: ButtonSize; + className?: string; +} + +export const ButtonGroup = ({ children, size = "md", className, ...otherProps }: ButtonGroupProps) => { + return ( + + + {children} + + + ); +}; diff --git a/src/components/base/buttons/button.tsx b/src/components/base/buttons/button.tsx new file mode 100644 index 0000000..f01fbca --- /dev/null +++ b/src/components/base/buttons/button.tsx @@ -0,0 +1,261 @@ +"use client"; + +import type { FC, ReactElement, ReactNode } from "react"; +import { isValidElement } from "react"; +import type { ButtonProps as AriaButtonProps, LinkProps as AriaLinkProps } from "react-aria-components"; +import { Button as AriaButton, Link as AriaLink } from "react-aria-components"; +import { cx, sortCx } from "@/utils/cx"; +import { isReactComponent } from "@/utils/is-react-component"; + +export const styles = sortCx({ + common: { + root: [ + "group relative inline-flex h-max cursor-pointer items-center justify-center whitespace-nowrap outline-brand transition duration-100 ease-linear before:absolute focus-visible:outline-2 focus-visible:outline-offset-2", + // When button is used within `InputGroup` + "in-data-input-wrapper:shadow-xs in-data-input-wrapper:focus:!z-50 in-data-input-wrapper:in-data-leading:-mr-px in-data-input-wrapper:in-data-leading:rounded-r-none in-data-input-wrapper:in-data-leading:before:rounded-r-none in-data-input-wrapper:in-data-trailing:-ml-px in-data-input-wrapper:in-data-trailing:rounded-l-none in-data-input-wrapper:in-data-trailing:before:rounded-l-none", + // Disabled styles + "disabled:cursor-not-allowed disabled:opacity-50 in-data-input-wrapper:disabled:opacity-100", + // Same as `icon` but for SSR icons that cannot be passed to the client as functions. + "*:data-icon:pointer-events-none *:data-icon:size-5 *:data-icon:shrink-0 *:data-icon:transition-inherit-all", + ].join(" "), + icon: "pointer-events-none size-5 shrink-0 transition-inherit-all", + }, + sizes: { + xs: { + root: [ + "gap-1 rounded-lg px-2.5 py-1.5 text-sm font-semibold before:rounded-[7px] data-icon-only:p-2", + "in-data-input-wrapper:px-3.5 in-data-input-wrapper:py-2.5 in-data-input-wrapper:data-icon-only:p-2.5", + "*:data-icon:size-4 *:data-icon:stroke-[2.25px]", + ].join(" "), + linkRoot: "gap-1 *:data-text:underline-offset-3", + }, + sm: { + root: [ + "gap-1 rounded-lg px-3 py-2 text-sm font-semibold before:rounded-[7px] data-icon-only:p-2", + "in-data-input-wrapper:px-3.5 in-data-input-wrapper:py-2.5 in-data-input-wrapper:data-icon-only:p-2.5", + ].join(" "), + linkRoot: "gap-1 *:data-text:underline-offset-3", + }, + md: { + root: [ + "gap-1 rounded-lg px-3.5 py-2.5 text-sm font-semibold before:rounded-[7px] data-icon-only:p-2.5", + "in-data-input-wrapper:gap-1.5 in-data-input-wrapper:px-4 in-data-input-wrapper:text-md in-data-input-wrapper:data-icon-only:p-3", + ].join(" "), + linkRoot: "gap-1 *:data-text:underline-offset-4", + }, + lg: { + root: "gap-1.5 rounded-lg px-4 py-2.5 text-md font-semibold before:rounded-[7px] data-icon-only:p-3", + linkRoot: "gap-1.5 *:data-text:underline-offset-4", + }, + xl: { + root: "gap-1.5 rounded-lg px-4.5 py-3 text-md font-semibold before:rounded-[7px] data-icon-only:p-3.5", + linkRoot: "gap-1.5 *:data-text:underline-offset-4", + }, + }, + + colors: { + primary: { + root: [ + "bg-brand-solid text-white shadow-xs-skeuomorphic ring-1 ring-transparent ring-inset hover:bg-brand-solid_hover data-loading:bg-brand-solid_hover", + // Inner border gradient + "before:absolute before:inset-px before:border before:border-white/12 before:mask-b-from-0%", + // Icon styles + "*:data-icon:text-white/60 hover:*:data-icon:text-white/70", + ].join(" "), + }, + secondary: { + root: [ + "bg-primary text-secondary shadow-xs-skeuomorphic ring-1 ring-primary ring-inset hover:bg-primary_hover hover:text-secondary_hover data-loading:bg-primary_hover", + // Icon styles + "*:data-icon:text-fg-quaternary hover:*:data-icon:text-fg-quaternary_hover", + ].join(" "), + }, + tertiary: { + root: [ + "text-tertiary hover:bg-primary_hover hover:text-tertiary_hover data-loading:bg-primary_hover", + // Icon styles + "*:data-icon:text-fg-quaternary hover:*:data-icon:text-fg-quaternary_hover", + ].join(" "), + }, + "link-color": { + root: [ + "justify-normal rounded p-0! text-brand-secondary hover:text-brand-secondary_hover", + // Inner text underline + "*:data-text:underline *:data-text:decoration-transparent hover:*:data-text:decoration-fg-brand-secondary_alt", + // Icon styles + "*:data-icon:text-fg-brand-secondary_alt hover:*:data-icon:text-fg-brand-secondary_hover", + ].join(" "), + }, + "link-gray": { + root: [ + "justify-normal rounded p-0! text-tertiary hover:text-tertiary_hover", + // Inner text underline + "*:data-text:underline *:data-text:decoration-transparent hover:*:data-text:decoration-fg-quaternary", + // Icon styles + "*:data-icon:text-fg-quaternary hover:*:data-icon:text-fg-quaternary_hover", + ].join(" "), + }, + "primary-destructive": { + root: [ + "bg-error-solid text-white shadow-xs-skeuomorphic ring-1 ring-transparent outline-error ring-inset hover:bg-error-solid_hover data-loading:bg-error-solid_hover", + // Inner border gradient + "before:absolute before:inset-px before:border before:border-white/12 before:mask-b-from-0%", + // Icon styles + "*:data-icon:text-white/60 hover:*:data-icon:text-white/70", + ].join(" "), + }, + "secondary-destructive": { + root: [ + "bg-primary text-error-primary shadow-xs-skeuomorphic ring-1 ring-error_subtle outline-error ring-inset hover:bg-error-primary hover:text-error-primary_hover data-loading:bg-error-primary", + // Icon styles + "*:data-icon:text-fg-error-secondary hover:*:data-icon:text-fg-error-primary", + ].join(" "), + }, + "tertiary-destructive": { + root: [ + "text-error-primary outline-error hover:bg-error-primary hover:text-error-primary_hover data-loading:bg-error-primary", + // Icon styles + "*:data-icon:text-fg-error-secondary hover:*:data-icon:text-fg-error-primary", + ].join(" "), + }, + "link-destructive": { + root: [ + "justify-normal rounded p-0! text-error-primary outline-error hover:text-error-primary_hover", + // Inner text underline + "*:data-text:underline *:data-text:decoration-transparent *:data-text:underline-offset-2 hover:*:data-text:decoration-current", + // Icon styles + "*:data-icon:text-fg-error-secondary hover:*:data-icon:text-fg-error-primary", + ].join(" "), + }, + }, +}); + +/** + * Common props shared between button and anchor variants + */ +export interface CommonProps { + /** Disables the button and shows a disabled state */ + isDisabled?: boolean; + /** Shows a loading spinner and disables the button */ + isLoading?: boolean; + /** The size variant of the button */ + size?: keyof typeof styles.sizes; + /** The color variant of the button */ + color?: keyof typeof styles.colors; + /** Icon component or element to show before the text */ + iconLeading?: FC<{ className?: string }> | ReactNode; + /** Icon component or element to show after the text */ + iconTrailing?: FC<{ className?: string }> | ReactNode; + /** Removes horizontal padding from the text content */ + noTextPadding?: boolean; + /** When true, keeps the text visible during loading state */ + showTextWhileLoading?: boolean; + + /** The button label. Omitting it while passing an icon renders a square, icon-only button. */ + children?: ReactNode; + /** The class name applied to the button. */ + className?: string; +} + +/** + * Props for the button variant (non-link) + */ +export interface ButtonProps extends CommonProps, Omit {} +/** + * Props for the link variant (anchor tag) + */ +interface LinkProps extends CommonProps, Omit { + href: NonNullable; +} + +/** Union type of button and link props */ +export type Props = ButtonProps | LinkProps; + +export const Button: { + (props: LinkProps): ReactElement; + (props: ButtonProps): ReactElement; +} = ({ + size = "sm", + color = "primary", + children, + className, + noTextPadding, + iconLeading: IconLeading, + iconTrailing: IconTrailing, + isDisabled: disabled, + isLoading: loading, + showTextWhileLoading, + ...props +}) => { + const href = "href" in props ? props.href : undefined; + + const isIcon = (IconLeading || IconTrailing) && !children; + const isLinkType = ["link-gray", "link-color", "link-destructive"].includes(color); + + noTextPadding = isLinkType || noTextPadding; + + const commonChildren = ( + <> + {/* Leading icon */} + {isValidElement(IconLeading) && IconLeading} + {isReactComponent(IconLeading) && } + + {loading && ( + + {/* Background circle */} + + {/* Spinning circle */} + + + )} + + {children && ( + + {children} + + )} + + {/* Trailing icon */} + {isValidElement(IconTrailing) && IconTrailing} + {isReactComponent(IconTrailing) && } + + ); + + const commonProps = { + "data-loading": loading ? true : undefined, + "data-icon-only": isIcon ? true : undefined, + ...props, + isDisabled: disabled, + className: cx( + styles.common.root, + styles.sizes[size].root, + styles.colors[color].root, + isLinkType && styles.sizes[size].linkRoot, + (loading || (href && (disabled || loading))) && "pointer-events-none", + // If in `loading` state, hide everything except the loading icon (and text if `showTextWhileLoading` is true). + loading && (showTextWhileLoading ? "[&>*:not([data-icon=loading]):not([data-text])]:hidden" : "[&>*:not([data-icon=loading])]:invisible"), + className, + ), + children: commonChildren, + }; + + if ("href" in commonProps) { + return ; + } + + return ; +}; diff --git a/src/components/base/buttons/close-button.tsx b/src/components/base/buttons/close-button.tsx new file mode 100644 index 0000000..bbab986 --- /dev/null +++ b/src/components/base/buttons/close-button.tsx @@ -0,0 +1,51 @@ +"use client"; + +import { X as CloseIcon } from "@untitledui/icons"; +import { Button as AriaButton, type ButtonProps as AriaButtonProps } from "react-aria-components"; +import { cx } from "@/utils/cx"; + +const sizes = { + xs: { root: "size-7", icon: "size-4" }, + sm: { root: "size-9", icon: "size-5" }, + md: { root: "size-10", icon: "size-5" }, + lg: { root: "size-11", icon: "size-6" }, +}; + +const themes = { + light: "text-fg-quaternary hover:bg-primary_hover hover:text-fg-quaternary_hover focus-visible:outline-2 focus-visible:outline-offset-2 outline-focus-ring", + dark: "text-fg-white/70 hover:text-fg-white hover:bg-white/20 focus-visible:outline-2 focus-visible:outline-offset-2 outline-focus-ring", +}; + +interface CloseButtonProps extends AriaButtonProps { + theme?: "light" | "dark"; + size?: "xs" | "sm" | "md" | "lg"; + label?: string; + /** + * The React Aria slot the button fills. Defaults to `"close"` so it + * automatically closes a parent `Dialog`/`Modal` without an explicit handler. + * Pass `null` to opt out (e.g. when used as a `SearchField` clear button it + * is picked up regardless of slot). + * @default "close" + */ + slot?: string | null; +} + +export const CloseButton = ({ label, className, size = "sm", theme = "light", slot = "close", ...otherProps }: CloseButtonProps) => { + return ( + + cx( + "flex cursor-pointer items-center justify-center rounded-lg p-2 transition duration-100 ease-linear focus:outline-hidden", + sizes[size].root, + themes[theme], + typeof className === "function" ? className(state) : className, + ) + } + > + + ); +}; diff --git a/src/components/base/checkbox/checkbox.tsx b/src/components/base/checkbox/checkbox.tsx new file mode 100644 index 0000000..54c193f --- /dev/null +++ b/src/components/base/checkbox/checkbox.tsx @@ -0,0 +1,129 @@ +"use client"; + +import type { ReactNode, Ref } from "react"; +import { Checkbox as AriaCheckbox, type CheckboxProps as AriaCheckboxProps } from "react-aria-components"; +import { cx } from "@/utils/cx"; + +export interface CheckboxBaseProps { + /** Controls the size and corner radius of the box, and the size of the tick and dash icons inside it. */ + size?: "sm" | "md"; + /** Additional classes merged onto the box element. */ + className?: string; + /** Whether to draw the keyboard focus ring around the box. Normally passed down by `Checkbox` from React Aria's render state rather than set by hand. */ + isFocusVisible?: boolean; + /** Whether to render the checked appearance: brand-filled box with the tick faded in. Normally passed down by `Checkbox` rather than set by hand. */ + isSelected?: boolean; + /** Whether to render the dimmed, not-allowed appearance. Normally passed down by `Checkbox` rather than set by hand. */ + isDisabled?: boolean; + /** Whether to render the mixed appearance: brand-filled box showing a dash, which is drawn instead of the tick even when `isSelected` is set. Normally passed down by `Checkbox` rather than set by hand. */ + isIndeterminate?: boolean; +} + +export const CheckboxBase = ({ className, isSelected, isDisabled, isIndeterminate, size = "sm", isFocusVisible = false }: CheckboxBaseProps) => { + return ( +
+ + + +
+ ); +}; +CheckboxBase.displayName = "CheckboxBase"; + +interface CheckboxProps extends AriaCheckboxProps { + /** Ref to the underlying label element that wraps the box and the text. */ + ref?: Ref; + /** Controls the box size, the gap between it and the text, and the label and hint typography. */ + size?: "sm" | "md"; + /** Content rendered beside the box. It sits inside the checkbox's label element, so it also acts as the accessible name and toggles the checkbox when clicked. */ + label?: ReactNode; + /** Supporting text rendered under the label. Clicks on it are stopped so reading the hint does not toggle the checkbox. */ + hint?: ReactNode; +} + +export const Checkbox = ({ label, hint, size = "sm", className, ...ariaCheckboxProps }: CheckboxProps) => { + const sizes = { + sm: { + root: "gap-2", + textWrapper: "", + label: "text-sm font-medium", + hint: "text-sm", + }, + md: { + root: "gap-3", + textWrapper: "gap-0.5", + label: "text-md font-medium", + hint: "text-md", + }, + }; + + return ( + + cx( + "relative flex items-start", + state.isDisabled && "cursor-not-allowed", + sizes[size].root, + typeof className === "function" ? className(state) : className, + ) + } + > + {({ isSelected, isIndeterminate, isDisabled, isFocusVisible }) => ( + <> + + {(label || hint) && ( +
+ {label &&

{label}

} + {hint && ( + event.stopPropagation()}> + {hint} + + )} +
+ )} + + )} +
+ ); +}; +Checkbox.displayName = "Checkbox"; diff --git a/src/components/base/input/hint-text.tsx b/src/components/base/input/hint-text.tsx new file mode 100644 index 0000000..be55f7c --- /dev/null +++ b/src/components/base/input/hint-text.tsx @@ -0,0 +1,41 @@ +"use client"; + +import type { ReactNode, Ref } from "react"; +import type { TextProps as AriaTextProps } from "react-aria-components"; +import { Text as AriaText } from "react-aria-components"; +import { cx } from "@/utils/cx"; + +interface HintTextProps extends AriaTextProps { + /** Indicates that the hint text is an error message. */ + isInvalid?: boolean; + /** Ref to the underlying text element. */ + ref?: Ref; + /** Controls the hint's text size; `sm` renders it one step smaller. The text also shrinks automatically inside a small input field. */ + size?: "sm" | "md"; + /** The helper text, or the error message when `isInvalid` is set. */ + children: ReactNode; +} + +export const HintText = ({ isInvalid, className, size = "md", ...props }: HintTextProps) => { + return ( + + ); +}; + +HintText.displayName = "HintText"; diff --git a/src/components/base/input/input-group.tsx b/src/components/base/input/input-group.tsx new file mode 100644 index 0000000..215cf59 --- /dev/null +++ b/src/components/base/input/input-group.tsx @@ -0,0 +1,157 @@ +"use client"; + +import { type HTMLAttributes, type ReactNode } from "react"; +import { HintText } from "@/components/base/input/hint-text"; +import type { TextFieldProps } from "@/components/base/input/input"; +import { TextField } from "@/components/base/input/input"; +import { Label } from "@/components/base/input/label"; +import { cx, sortCx } from "@/utils/cx"; + +interface InputPrefixProps extends HTMLAttributes { + /** The position of the prefix. */ + position?: "leading" | "trailing"; + /** Indicates that the prefix is disabled. */ + isDisabled?: boolean; +} + +export const InputPrefix = ({ children, ...props }: InputPrefixProps) => ( + + {children} + +); + +// `${string}ClassName` is used to omit any className prop that ends with a `ClassName` suffix +interface InputGroupProps extends TextFieldProps { + /** A prefix text that is displayed in the same box as the input.*/ + prefix?: string; + /** A leading addon that is displayed with visual separation from the input. */ + leadingAddon?: ReactNode; + /** A trailing addon that is displayed with visual separation from the input. */ + trailingAddon?: ReactNode; + /** The class name to apply to the input group. */ + className?: string; + /** The children of the input group (i.e ``) */ + children: ReactNode; + /** Label text for the input */ + label?: string; + /** Helper text displayed below the input */ + hint?: ReactNode; + /** Whether to hide the required indicator from the label. */ + hideRequiredIndicator?: boolean; +} + +export const InputGroup = ({ size = "md", prefix, leadingAddon, trailingAddon, label, hint, hideRequiredIndicator, children, ...props }: InputGroupProps) => { + const hasLeading = !!leadingAddon; + const hasTrailing = !!trailingAddon; + + const paddings = sortCx({ + sm: { + input: cx( + // Apply padding styles when select element is passed as a child + hasLeading && "group-has-[&>select]:pr-9 group-has-[&>select]:pl-2", + hasTrailing && (prefix ? "group-has-[&>select]:pr-6 group-has-[&>select]:pl-0" : "group-has-[&>select]:pr-6 group-has-[&>select]:pl-3"), + ), + leadingText: "pr-1.5 pl-3", + }, + md: { + input: cx( + // Apply padding styles when select element is passed as a child + hasLeading && "group-has-[&>select]:pr-9 group-has-[&>select]:pl-2.5", + hasTrailing && (prefix ? "group-has-[&>select]:pr-6 group-has-[&>select]:pl-0" : "group-has-[&>select]:pr-6 group-has-[&>select]:pl-3"), + ), + leadingText: "pr-2 pl-3", + }, + lg: { + input: cx( + // Apply padding styles when select element is passed as a child + hasLeading && "group-has-[&>select]:pr-9.5 group-has-[&>select]:pl-3", + hasTrailing && (prefix ? "group-has-[&>select]:pr-6 group-has-[&>select]:pl-0" : "group-has-[&>select]:pr-6 group-has-[&>select]:pl-3"), + ), + leadingText: "pr-2 pl-3.5", + }, + }); + + return ( + select]:right-0")} + wrapperClassName={cx( + "z-10", + // Apply styles based on the presence of leading or trailing elements + hasLeading && "rounded-l-none", + hasTrailing && "rounded-r-none", + // When select element is passed as a child + "group-has-[&>select]:bg-transparent group-has-[&>select]:shadow-none group-has-[&>select]:ring-0 group-has-[&>select]:focus-within:ring-0", + )} + {...props} + > + {({ isDisabled, isInvalid, isRequired }) => ( + <> + {label && } + +
select]:shadow-xs has-[&>select]:ring-1 has-[&>select]:ring-border-primary has-[&>select]:ring-inset has-[&>select]:has-[input:focus]:ring-2 has-[&>select]:has-[input:focus]:ring-border-brand", + + isDisabled && "cursor-not-allowed", + isInvalid && "has-[&>select]:ring-border-error_subtle has-[&>select]:has-[input:focus]:ring-border-error", + )} + > + {leadingAddon && ( +
+ {leadingAddon} +
+ )} + + {prefix && ( + +

{prefix}

+
+ )} + + {children} + + {trailingAddon && ( +
+ {trailingAddon} +
+ )} +
+ + {hint && ( + + {hint} + + )} + + )} +
+ ); +}; + +InputGroup.Prefix = InputPrefix; + +InputGroup.displayName = "InputGroup"; diff --git a/src/components/base/input/input.tsx b/src/components/base/input/input.tsx new file mode 100644 index 0000000..d31cf19 --- /dev/null +++ b/src/components/base/input/input.tsx @@ -0,0 +1,306 @@ +"use client"; + +import { type ComponentType, type HTMLAttributes, type ReactNode, type Ref, createContext, useContext, useState } from "react"; +import { Eye, EyeOff, HelpCircle, InfoCircle } from "@untitledui/icons"; +import type { InputProps as AriaInputProps, TextFieldProps as AriaTextFieldProps } from "react-aria-components"; +import { Button as AriaButton, Group as AriaGroup, Input as AriaInput, TextField as AriaTextField } from "react-aria-components"; +import { HintText } from "@/components/base/input/hint-text"; +import { Label } from "@/components/base/input/label"; +import { Tooltip, TooltipTrigger } from "@/components/base/tooltip/tooltip"; +import { cx, sortCx } from "@/utils/cx"; + +export interface InputBaseProps extends Omit { + /** Tooltip message on hover. */ + tooltip?: string; + /** Whether the input is invalid. */ + isInvalid?: boolean; + /** Whether the input is disabled. */ + isDisabled?: boolean; + /** Whether the input is required. */ + isRequired?: boolean; + /** + * Input size. + * @default "sm" + */ + size?: "sm" | "md" | "lg"; + /** Placeholder text. */ + placeholder?: string; + /** Class name for the icon. */ + iconClassName?: string; + /** Class name for the input. */ + inputClassName?: string; + /** Class name for the input wrapper. */ + wrapperClassName?: string; + /** Class name for the tooltip. */ + tooltipClassName?: string; + /** Keyboard shortcut to display. */ + shortcut?: string | boolean; + ref?: Ref; + groupRef?: Ref; + /** Icon component to display on the left side of the input. */ + icon?: ComponentType>; +} + +export const InputBase = ({ + ref, + tooltip, + shortcut, + groupRef, + size = "md", + isInvalid, + isDisabled, + isRequired, + icon: Icon, + placeholder, + wrapperClassName, + tooltipClassName, + inputClassName, + iconClassName, + type = "text", + ...inputProps +}: InputBaseProps) => { + const [isPasswordVisible, setIsPasswordVisible] = useState(false); + + // Check if the input has a leading icon or tooltip + const hasTrailingIcon = tooltip || isInvalid; + const hasLeadingIcon = Icon; + + // If the input is inside a `TextFieldContext`, use its context to simplify applying styles + const context = useContext(TextFieldContext); + + const inputSize = context?.size || size; + + const sizes = sortCx({ + sm: { + root: cx("px-3 py-2 text-sm", hasLeadingIcon && "pl-9", hasTrailingIcon && "pr-9"), + iconLeading: "left-3 size-4 stroke-[2.25px]", + iconTrailing: "right-3", + shortcut: "pr-1.5", + }, + md: { + root: cx("px-3 py-2 text-md", hasLeadingIcon && "pl-10", hasTrailingIcon && "pr-9"), + iconLeading: "left-3 size-5", + iconTrailing: "right-3", + shortcut: "pr-2", + }, + lg: { + root: cx("px-3.5 py-2.5 text-md", hasLeadingIcon && "pl-10.5", hasTrailingIcon && "pr-9.5"), + iconLeading: "left-3.5 size-5", + iconTrailing: "right-3.5", + shortcut: "pr-2.5", + }, + }); + + return ( + + cx( + "group/input relative flex w-full flex-row place-content-center place-items-center rounded-lg bg-primary shadow-xs ring-1 ring-primary transition-shadow duration-100 ease-linear ring-inset", + + isFocusWithin && !isDisabled && "ring-2 ring-brand", + + // Disabled state styles + isDisabled && "cursor-not-allowed opacity-50", + "group-disabled:cursor-not-allowed group-disabled:opacity-50", + + // Invalid state styles + isInvalid && "ring-error_subtle", + "group-invalid:ring-error_subtle", + + // Invalid state with focus-within styles + isInvalid && isFocusWithin && "ring-2 ring-error", + isFocusWithin && "group-invalid:ring-2 group-invalid:ring-error", + + context?.wrapperClassName, + wrapperClassName, + ) + } + > + {/* Leading icon and Payment icon */} + {Icon && ( + + )} + + {/* Input field */} + + + {/* Tooltip and help icon */} + {tooltip && type !== "password" && ( + + + + + + )} + + {/* Invalid icon */} + {type !== "password" && ( + + ); +}; + +InputBase.displayName = "InputBase"; + +interface TextFieldContextProps extends Partial> {} + +const TextFieldContext = createContext({}); + +export interface TextFieldProps extends AriaTextFieldProps, TextFieldContextProps {} + +export const TextField = ({ className, size = "md", inputClassName, wrapperClassName, iconClassName, tooltipClassName, ...props }: TextFieldProps) => { + return ( + + + cx("group flex h-max w-full flex-col items-start justify-start gap-1.5", typeof className === "function" ? className(state) : className) + } + /> + + ); +}; + +TextField.displayName = "TextField"; + +export interface InputProps + extends + AriaTextFieldProps, + Pick< + InputBaseProps, + | "ref" + | "placeholder" + | "icon" + | "shortcut" + | "tooltip" + | "groupRef" + | "size" + | "wrapperClassName" + | "inputClassName" + | "iconClassName" + | "tooltipClassName" + > { + /** Label text for the input */ + label?: string; + /** Helper text displayed below the input */ + hint?: ReactNode; + /** Whether to hide required indicator from label */ + hideRequiredIndicator?: boolean; +} + +export const Input = ({ + size = "md", + placeholder, + icon: Icon, + label, + hint, + shortcut, + hideRequiredIndicator, + className, + ref, + groupRef, + tooltip, + iconClassName, + inputClassName, + wrapperClassName, + tooltipClassName, + type = "text", + ...props +}: InputProps) => { + return ( + + {({ isRequired, isInvalid }) => ( + <> + {label && ( + + )} + + + + {hint && {hint}} + + )} + + ); +}; + +Input.displayName = "Input"; diff --git a/src/components/base/input/label.tsx b/src/components/base/input/label.tsx new file mode 100644 index 0000000..66c2678 --- /dev/null +++ b/src/components/base/input/label.tsx @@ -0,0 +1,68 @@ +"use client"; + +import type { ReactNode, Ref } from "react"; +import { HelpCircle } from "@untitledui/icons"; +import type { LabelProps as AriaLabelProps } from "react-aria-components"; +import { Label as AriaLabel } from "react-aria-components"; +import { Tooltip, TooltipTrigger } from "@/components/base/tooltip/tooltip"; +import { cx } from "@/utils/cx"; + +interface LabelProps extends AriaLabelProps { + /** The label text, rendered before the required asterisk and the tooltip trigger. */ + children: ReactNode; + /** Whether the required asterisk is tinted with the error color. When left undefined, it follows the invalid state of the surrounding field instead. */ + isInvalid?: boolean; + /** Whether the required asterisk is shown. When left undefined, it appears only if the surrounding field is marked required. */ + isRequired?: boolean; + /** Title text for a help tooltip. Setting it renders a help icon after the label that stays interactive even when the field is disabled. */ + tooltip?: string; + /** Supporting text shown under the tooltip title. Only rendered when `tooltip` is set. */ + tooltipDescription?: string; + /** Ref to the underlying label element. */ + ref?: Ref; +} + +export const Label = ({ isInvalid, isRequired, tooltip, tooltipDescription, className, ...props }: LabelProps) => { + return ( + + // or + // + data-label="true" + {...props} + className={cx("flex cursor-default items-center gap-0.5 text-sm font-medium text-secondary", className)} + > + {props.children} + + + * + + + {tooltip && ( + + + + + + )} + + ); +}; + +Label.displayName = "Label"; diff --git a/src/components/base/radio-buttons/radio-buttons.tsx b/src/components/base/radio-buttons/radio-buttons.tsx new file mode 100644 index 0000000..f2b418d --- /dev/null +++ b/src/components/base/radio-buttons/radio-buttons.tsx @@ -0,0 +1,123 @@ +"use client"; + +import { type ReactNode, type Ref, createContext, useContext } from "react"; +import { + Radio as AriaRadio, + RadioGroup as AriaRadioGroup, + type RadioGroupProps as AriaRadioGroupProps, + type RadioProps as AriaRadioProps, +} from "react-aria-components"; +import { cx } from "@/utils/cx"; + +export interface RadioGroupContextType { + size?: "sm" | "md"; +} + +const RadioGroupContext = createContext(null); + +export interface RadioButtonBaseProps { + size?: "sm" | "md"; + className?: string; + isFocusVisible?: boolean; + isSelected?: boolean; + isDisabled?: boolean; +} + +export const RadioButtonBase = ({ className, isFocusVisible, isSelected, isDisabled, size = "sm" }: RadioButtonBaseProps) => { + return ( +
+
+
+ ); +}; +RadioButtonBase.displayName = "RadioButtonBase"; + +interface RadioButtonProps extends AriaRadioProps { + size?: "sm" | "md"; + label?: ReactNode; + hint?: ReactNode; + ref?: Ref; +} + +export const RadioButton = ({ label, hint, className, size = "sm", ...ariaRadioProps }: RadioButtonProps) => { + const context = useContext(RadioGroupContext); + + size = context?.size ?? size; + + const sizes = { + sm: { + root: "gap-2", + textWrapper: "", + label: "text-sm font-medium", + hint: "text-sm", + }, + md: { + root: "gap-3", + textWrapper: "gap-0.5", + label: "text-md font-medium", + hint: "text-md", + }, + }; + + return ( + + cx( + "relative flex items-start", + state.isDisabled && "cursor-not-allowed", + sizes[size].root, + typeof className === "function" ? className(state) : className, + ) + } + > + {({ isSelected, isDisabled, isFocusVisible }) => ( + <> + + {(label || hint) && ( +
+ {label &&

{label}

} + {hint && ( + event.stopPropagation()}> + {hint} + + )} +
+ )} + + )} +
+ ); +}; +RadioButton.displayName = "RadioButton"; + +interface RadioGroupProps extends RadioGroupContextType, AriaRadioGroupProps { + children: ReactNode; + className?: string; +} + +export const RadioGroup = ({ children, className, size = "sm", ...props }: RadioGroupProps) => { + return ( + + + {children} + + + ); +}; diff --git a/src/components/base/select/combobox.tsx b/src/components/base/select/combobox.tsx new file mode 100644 index 0000000..b1eb89b --- /dev/null +++ b/src/components/base/select/combobox.tsx @@ -0,0 +1,190 @@ +"use client"; + +import type { FC, FocusEventHandler, PointerEventHandler, ReactNode, Ref, RefAttributes } from "react"; +import { isValidElement, useCallback, useContext, useRef, useState } from "react"; +import { SearchLg } from "@untitledui/icons"; +import type { ComboBoxProps as AriaComboBoxProps, GroupProps as AriaGroupProps, ListBoxProps as AriaListBoxProps } from "react-aria-components"; +import { ComboBox as AriaComboBox, Group as AriaGroup, Input as AriaInput, ListBox as AriaListBox, ComboBoxStateContext } from "react-aria-components"; +import { HintText } from "@/components/base/input/hint-text"; +import { Label } from "@/components/base/input/label"; +import { Popover } from "@/components/base/select/popover"; +import { type CommonProps, SelectContext, type SelectItemType, sizes } from "@/components/base/select/select-shared"; +import { useResizeObserver } from "@/hooks/use-resize-observer"; +import { cx } from "@/utils/cx"; +import { isReactComponent } from "@/utils/is-react-component"; + +interface ComboBoxProps extends Omit, "children" | "items">, RefAttributes, CommonProps { + /** Whether to pin a ⌘K key hint to the right edge of the input. It is hidden below the medium breakpoint. */ + shortcut?: boolean; + /** The options to render in the dropdown. When `children` is a function it is called once per item to render it. */ + items?: SelectItemType[]; + /** Additional classes merged onto the dropdown popover, which is sized to match the input. */ + popoverClassName?: string; + /** Additional classes merged onto the ⌘K hint container, useful for adjusting its fade and spacing. */ + shortcutClassName?: string; + /** Leading icon component displayed before the input. */ + icon?: FC | ReactNode; + /** The dropdown contents: either `Select.Item` elements, or a render function called with each entry of `items`. */ + children: AriaListBoxProps["children"]; +} + +interface ComboBoxValueProps extends AriaGroupProps { + /** Controls the field's padding, text size, and icon size. */ + size: "sm" | "md" | "lg"; + /** Whether to pin the ⌘K key hint to the right edge of the field. */ + shortcut: boolean; + /** Text shown in the input while it is empty. */ + placeholder?: string; + /** Additional classes merged onto the ⌘K hint container. */ + shortcutClassName?: string; + /** Leading icon rendered before the input. Falls back to a search icon when omitted. */ + icon?: FC | ReactNode; + /** Fires when focus moves into the field. Used to re-measure the field so the popover matches its width. */ + onFocus?: FocusEventHandler; + /** Fires when the pointer enters the field. Used to re-measure the field so the popover matches its width. */ + onPointerEnter?: PointerEventHandler; + /** Ref to the field wrapper. It anchors the popover and provides the width it is sized to. */ + ref?: Ref; +} + +const ComboBoxValue = ({ size, shortcut, placeholder, shortcutClassName, icon: IconProp, ref, ...otherProps }: ComboBoxValueProps) => { + const state = useContext(ComboBoxStateContext); + + const value = state?.selectedItem?.value || null; + const inputValue = state?.inputValue || null; + + const first = inputValue?.split(value?.supportingText)?.[0] || ""; + const last = inputValue?.split(first)[1]; + + return ( + + cx( + "relative flex w-full items-center gap-2 rounded-lg bg-primary shadow-xs ring-1 ring-primary outline-hidden transition-shadow duration-100 ease-linear ring-inset", + isDisabled && "cursor-not-allowed opacity-50", + isFocusWithin && "ring-2 ring-brand", + + // Icon styles + "*:data-icon:shrink-0 *:data-icon:text-fg-quaternary", + + sizes[size].root, + ) + } + > + {isReactComponent(IconProp) ? ( + + ); +}; + +export const ComboBox = ({ + placeholder = "Search", + shortcut = true, + size = "md", + children, + items, + shortcutClassName, + icon, + hideRequiredIndicator, + ...otherProps +}: ComboBoxProps) => { + const placeholderRef = useRef(null); + const [popoverWidth, setPopoverWidth] = useState(""); + + // Resize observer for popover width + const onResize = useCallback(() => { + if (!placeholderRef.current) return; + + const divRect = placeholderRef.current?.getBoundingClientRect(); + + setPopoverWidth(divRect.width + "px"); + }, [placeholderRef, setPopoverWidth]); + + useResizeObserver({ + ref: placeholderRef, + box: "border-box", + onResize, + }); + + return ( + + + {(state) => ( +
+ {otherProps.label && ( + + )} + + + + + + {children} + + + + {otherProps.hint && ( + + {otherProps.hint} + + )} +
+ )} +
+
+ ); +}; diff --git a/src/components/base/select/popover.tsx b/src/components/base/select/popover.tsx new file mode 100644 index 0000000..5ba404a --- /dev/null +++ b/src/components/base/select/popover.tsx @@ -0,0 +1,38 @@ +"use client"; + +import type { RefAttributes } from "react"; +import type { PopoverProps as AriaPopoverProps } from "react-aria-components"; +import { Popover as AriaPopover } from "react-aria-components"; +import { cx } from "@/utils/cx"; + +interface PopoverProps extends AriaPopoverProps, RefAttributes { + /** Caps how tall the popover can grow before its list starts scrolling: 14rem for `sm`, 16rem for `md`, and 20rem for `lg`. */ + size: "sm" | "md" | "lg"; +} + +export const Popover = (props: PopoverProps) => { + return ( + + cx( + "w-(--trigger-width) origin-(--trigger-anchor-point) overflow-x-hidden overflow-y-auto rounded-lg bg-primary py-1 shadow-lg ring-1 ring-secondary_alt outline-hidden will-change-transform", + + state.isEntering && + "duration-150 ease-out animate-in fade-in placement-right:slide-in-from-left-0.5 placement-top:slide-in-from-bottom-0.5 placement-bottom:slide-in-from-top-0.5", + state.isExiting && + "duration-100 ease-in animate-out fade-out placement-right:slide-out-to-left-0.5 placement-top:slide-out-to-bottom-0.5 placement-bottom:slide-out-to-top-0.5", + + props.size === "sm" && "max-h-56!", + props.size === "md" && "max-h-64!", + props.size === "lg" && "max-h-80!", + + typeof props.className === "function" ? props.className(state) : props.className, + ) + } + /> + ); +}; diff --git a/src/components/base/select/select-item.tsx b/src/components/base/select/select-item.tsx new file mode 100644 index 0000000..1d27259 --- /dev/null +++ b/src/components/base/select/select-item.tsx @@ -0,0 +1,135 @@ +"use client"; + +import { isValidElement, useContext } from "react"; +import { Check } from "@untitledui/icons"; +import type { ListBoxItemProps as AriaListBoxItemProps } from "react-aria-components"; +import { ListBoxItem as AriaListBoxItem, Text as AriaText } from "react-aria-components"; +import { Avatar } from "@/components/base/avatar/avatar"; +import { CheckboxBase } from "@/components/base/checkbox/checkbox"; +import { cx } from "@/utils/cx"; +import { isReactComponent } from "@/utils/is-react-component"; +import type { SelectItemType } from "./select-shared"; +import { SelectContext } from "./select-shared"; + +const sizes = { + sm: { + root: "p-2 pr-2.5 gap-2 *:data-icon:size-4 *:data-icon:stroke-[2.25px]", + text: "text-sm", + textContainer: "gap-x-1.5", + check: "size-4 stroke-[2.25px]", + checkbox: "sm" as const, + }, + md: { + root: "p-2 pr-2.5 gap-2 *:data-icon:size-5", + text: "text-md", + textContainer: "gap-x-2", + check: "size-5", + checkbox: "sm" as const, + }, + lg: { + root: "p-2.5 pl-2 gap-2 *:data-icon:size-5", + text: "text-md", + textContainer: "gap-x-2", + check: "size-5", + checkbox: "md" as const, + }, +}; + +interface SelectItemProps extends Omit, "id">, SelectItemType { + /** The selection indicator to be displayed on the item. */ + selectionIndicator?: "checkmark" | "checkbox" | "none"; + /** The alignment of the selection indicator. */ + selectionIndicatorAlign?: "left" | "right"; +} + +export const SelectItem = ({ + label, + id, + value, + avatarUrl, + supportingText, + isDisabled, + icon: Icon, + className, + children, + selectionIndicator = "checkmark", + selectionIndicatorAlign = "right", + ...props +}: SelectItemProps) => { + const { size } = useContext(SelectContext); + + const labelOrChildren = label || (typeof children === "string" ? children : ""); + const textValue = supportingText ? labelOrChildren + " " + supportingText : labelOrChildren; + + const isLeft = selectionIndicatorAlign === "left"; + + return ( + + cx("w-full py-px outline-hidden", size === "sm" ? "px-1" : "px-1.5", typeof className === "function" ? className(state) : className) + } + > + {(state) => ( +
+ {isLeft && selectionIndicator === "checkbox" && ( + + )} + + {avatarUrl ? ( +
+ )} +
+ ); +}; diff --git a/src/components/base/select/select-shared.tsx b/src/components/base/select/select-shared.tsx new file mode 100644 index 0000000..47abfa8 --- /dev/null +++ b/src/components/base/select/select-shared.tsx @@ -0,0 +1,51 @@ +"use client"; + +import type { FC, ReactNode } from "react"; +import { createContext } from "react"; + +export type SelectItemType = { + /** Unique identifier for the item. */ + id: string | number; + /** The primary display text. */ + label?: string; + /** Avatar image URL. */ + avatarUrl?: string; + /** Whether the item is disabled. */ + isDisabled?: boolean; + /** Secondary text displayed alongside the label. */ + supportingText?: string; + /** Leading icon component or element. */ + icon?: FC | ReactNode; +}; + +export interface CommonProps { + /** Helper text displayed below the input. */ + hint?: string; + /** Field label displayed above the input. */ + label?: string; + /** Tooltip text for the help icon next to the label. */ + tooltip?: string; + /** + * The size of the component. + * @default "md" + */ + size?: "sm" | "md" | "lg"; + /** Placeholder text when no value is selected. */ + placeholder?: string; + /** Whether to hide the required indicator from the label. */ + hideRequiredIndicator?: boolean; +} + +export const sizes = { + sm: { + root: "py-2 pl-3 pr-2.5 gap-2 *:data-icon:size-4 *:data-icon:stroke-[2.25px]", + withIcon: "", + text: "text-sm", + textContainer: "gap-x-1.5", + shortcut: "pr-2.5", + }, + md: { root: "py-2 px-3 gap-2 *:data-icon:size-5", withIcon: "", text: "text-md", textContainer: "gap-x-1.5", shortcut: "pr-2.5" }, + lg: { root: "py-2.5 px-3.5 gap-2 *:data-icon:size-5", withIcon: "", text: "text-md", textContainer: "gap-x-1.5", shortcut: "pr-3" }, +}; + +export const SelectContext = createContext<{ size: "sm" | "md" | "lg" }>({ size: "md" }); diff --git a/src/components/base/select/select.tsx b/src/components/base/select/select.tsx new file mode 100644 index 0000000..7129bbf --- /dev/null +++ b/src/components/base/select/select.tsx @@ -0,0 +1,159 @@ +"use client"; + +import type { FC, ReactNode, Ref, RefAttributes } from "react"; +import { isValidElement } from "react"; +import { ChevronDown } from "@untitledui/icons"; +import type { SelectProps as AriaSelectProps } from "react-aria-components"; +import { Button as AriaButton, ListBox as AriaListBox, Select as AriaSelect, SelectValue as AriaSelectValue } from "react-aria-components"; +import { Avatar } from "@/components/base/avatar/avatar"; +import { HintText } from "@/components/base/input/hint-text"; +import { Label } from "@/components/base/input/label"; +import { cx } from "@/utils/cx"; +import { isReactComponent } from "@/utils/is-react-component"; +import { ComboBox } from "./combobox"; +import { Popover } from "./popover"; +import { SelectItem } from "./select-item"; +import { type CommonProps, SelectContext, type SelectItemType, sizes } from "./select-shared"; + +export { SelectContext, sizes, type CommonProps, type SelectItemType } from "./select-shared"; + +export interface SelectProps extends Omit, "children" | "items">, RefAttributes, CommonProps { + /** The options to render in the dropdown. When `children` is a function it is called once per item to render it. */ + items?: SelectItemType[]; + /** Additional classes merged onto the dropdown popover. */ + popoverClassName?: string; + /** Leading icon shown in the trigger, used as a fallback when the selected item has no icon of its own. Accepts a component reference or a rendered element. */ + icon?: FC | ReactNode; + /** The dropdown contents: either `Select.Item` elements, or a render function called with each entry of `items`. */ + children: ReactNode | ((item: SelectItemType) => ReactNode); +} + +interface SelectValueProps { + /** Whether the dropdown is open, which keeps the trigger in its focused ring style. */ + isOpen: boolean; + /** Controls the trigger's padding, text size, and icon size. */ + size: "sm" | "md" | "lg"; + /** Whether the trigger is focused, which draws the brand focus ring. */ + isFocused: boolean; + /** Whether the trigger is dimmed and rejects pointer interaction. */ + isDisabled: boolean; + /** Text shown in the trigger while no item is selected. */ + placeholder?: string; + /** Ref to the trigger button element. */ + ref?: Ref; + /** Leading icon shown in the trigger when the selected item has no icon of its own. */ + icon?: FC | ReactNode; +} + +const SelectValue = ({ isOpen, isFocused, isDisabled, size, placeholder, icon, ref }: SelectValueProps) => { + return ( + + + className={(state) => + cx( + "flex h-max w-full items-center justify-start truncate text-left align-middle", + + sizes[size].root, + + // With icon + (state.selectedItems[0]?.icon || icon) && sizes[size].withIcon, + + // Icon styles + "*:data-icon:shrink-0 *:data-icon:text-fg-quaternary", + ) + } + > + {(state) => { + const selectedItem = state.selectedItems[0]; + const Icon = selectedItem?.icon || icon; + + return ( + <> + {selectedItem?.avatarUrl ? ( + + ) : isReactComponent(Icon) ? ( + + ); +}; + +const Select = ({ + placeholder = "Select", + icon, + size = "md", + children, + items, + label, + hint, + tooltip, + hideRequiredIndicator, + className, + ...rest +}: SelectProps) => { + return ( + + cx("flex flex-col gap-1.5", typeof className === "function" ? className(state) : className)}> + {(state) => ( + <> + {label && ( + + )} + + + + + + {children} + + + + {hint && ( + + {hint} + + )} + + )} + + + ); +}; + +const _Select = Select as typeof Select & { + ComboBox: typeof ComboBox; + Item: typeof SelectItem; +}; +_Select.ComboBox = ComboBox; +_Select.Item = SelectItem; + +export { _Select as Select }; diff --git a/src/components/base/toggle/toggle.tsx b/src/components/base/toggle/toggle.tsx new file mode 100644 index 0000000..c5b4c4a --- /dev/null +++ b/src/components/base/toggle/toggle.tsx @@ -0,0 +1,139 @@ +"use client"; + +import type { ReactNode } from "react"; +import type { SwitchProps as AriaSwitchProps } from "react-aria-components"; +import { Switch as AriaSwitch } from "react-aria-components"; +import { cx } from "@/utils/cx"; + +interface ToggleBaseProps { + size?: "sm" | "md"; + slim?: boolean; + className?: string; + isHovered?: boolean; + isFocusVisible?: boolean; + isSelected?: boolean; + isDisabled?: boolean; +} + +export const ToggleBase = ({ className, isHovered, isDisabled, isFocusVisible, isSelected, slim, size = "sm" }: ToggleBaseProps) => { + const styles = { + default: { + sm: { + root: "h-5 w-9 p-0.5", + switch: cx("size-4", isSelected && "translate-x-4"), + }, + md: { + root: "h-6 w-11 p-0.5", + switch: cx("size-5", isSelected && "translate-x-5"), + }, + }, + slim: { + sm: { + root: "h-4 w-8", + switch: cx("size-4", isSelected && "translate-x-4"), + }, + md: { + root: "h-5 w-10", + switch: cx("size-5", isSelected && "translate-x-5"), + }, + }, + }; + + const classes = slim ? styles.slim[size] : styles.default[size]; + + return ( +
+
+
+ ); +}; + +const styles = { + sm: { + root: "gap-2", + textWrapper: "", + label: "text-sm font-medium", + hint: "text-sm", + }, + md: { + root: "gap-3", + textWrapper: "gap-0.5", + label: "text-md font-medium", + hint: "text-md", + }, +}; + +interface ToggleProps extends AriaSwitchProps { + size?: "sm" | "md"; + label?: string; + hint?: ReactNode; + slim?: boolean; +} + +export const Toggle = ({ label, hint, className, size = "sm", slim, ...ariaSwitchProps }: ToggleProps) => { + return ( + + cx( + "relative flex w-max items-start", + state.isDisabled && "cursor-not-allowed", + styles[size].root, + typeof className === "function" ? className(state) : className, + ) + } + > + {({ isSelected, isDisabled, isFocusVisible, isHovered }) => ( + <> + + + {(label || hint) && ( +
+ {label &&

{label}

} + {hint && ( + event.stopPropagation()}> + {hint} + + )} +
+ )} + + )} +
+ ); +}; diff --git a/src/components/base/tooltip/tooltip.tsx b/src/components/base/tooltip/tooltip.tsx new file mode 100644 index 0000000..368f340 --- /dev/null +++ b/src/components/base/tooltip/tooltip.tsx @@ -0,0 +1,109 @@ +"use client"; + +import type { ReactNode } from "react"; +import type { + ButtonProps as AriaButtonProps, + TooltipProps as AriaTooltipProps, + TooltipTriggerComponentProps as AriaTooltipTriggerComponentProps, +} from "react-aria-components"; +import { Button as AriaButton, OverlayArrow as AriaOverlayArrow, Tooltip as AriaTooltip, TooltipTrigger as AriaTooltipTrigger } from "react-aria-components"; +import { cx } from "@/utils/cx"; + +export interface TooltipProps extends AriaTooltipTriggerComponentProps, Omit { + /** + * The title of the tooltip. + */ + title: ReactNode; + /** + * The description of the tooltip. + */ + description?: ReactNode; + /** + * Whether to show the arrow on the tooltip. + * + * @default false + */ + arrow?: boolean; + /** + * Delay in milliseconds before the tooltip is shown. + * + * @default 300 + */ + delay?: number; +} + +export const Tooltip = ({ + title, + description, + children, + arrow = false, + delay = 300, + closeDelay = 0, + trigger, + isDisabled, + isOpen, + defaultOpen, + offset = 6, + crossOffset, + placement = "top", + onOpenChange, + ...tooltipProps +}: TooltipProps) => { + const isTopOrBottomLeft = ["top left", "top end", "bottom left", "bottom end"].includes(placement); + const isTopOrBottomRight = ["top right", "top start", "bottom right", "bottom start"].includes(placement); + // Set negative cross offset for left and right placement to visually balance the tooltip. + const calculatedCrossOffset = isTopOrBottomLeft ? -12 : isTopOrBottomRight ? 12 : 0; + + return ( + + {children} + + cx(isEntering && "ease-out animate-in", isExiting && "ease-in animate-out")} + > + {({ isEntering, isExiting }) => ( +
+ {title} + + {description && {description}} + + {arrow && ( + + + + + + )} +
+ )} +
+
+ ); +}; + +type TooltipTriggerProps = AriaButtonProps; + +export const TooltipTrigger = ({ children, className, ...buttonProps }: TooltipTriggerProps) => { + return ( + cx("h-max w-max outline-hidden", typeof className === "function" ? className(values) : className)}> + {children} + + ); +}; diff --git a/src/components/contours/app-header.tsx b/src/components/contours/app-header.tsx new file mode 100644 index 0000000..b103646 --- /dev/null +++ b/src/components/contours/app-header.tsx @@ -0,0 +1,35 @@ +"use client"; + +import { Button } from "@/components/base/buttons/button"; +import { AXIS_MAPS_LOGO_URL, AXIS_MAPS_URL } from "@/lib/contours/constants"; +import type { PlaceResult } from "@/lib/contours/geocode"; +import { ContoursLogo } from "./logo"; +import { PlaceSearch } from "./place-search"; + +export const AppHeader = ({ onSelectPlace }: { onSelectPlace: (place: PlaceResult) => void }) => ( +
+
+ +

Contours

+
+ +
+ +
+ + +
+); diff --git a/src/components/contours/color-field.tsx b/src/components/contours/color-field.tsx new file mode 100644 index 0000000..e5e68a4 --- /dev/null +++ b/src/components/contours/color-field.tsx @@ -0,0 +1,93 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { + Button as AriaButton, + ColorSwatch as AriaColorSwatch, + Dialog as AriaDialog, + DialogTrigger as AriaDialogTrigger, + Popover as AriaPopover, +} from "react-aria-components"; +import { ColorPicker } from "@/components/application/color-picker/color-picker"; +import { InputBase } from "@/components/base/input/input"; +import { InputGroup } from "@/components/base/input/input-group"; +import { normalizeHex } from "@/lib/contours/color"; +import { cx } from "@/utils/cx"; + +interface ColorFieldProps { + label: string; + /** `#rrggbb` */ + value: string; + onChange: (hex: string) => void; + className?: string; +} + +/** + * A color setting as an input group: a swatch that opens the color picker, + * then the hex value. The two stay in sync. + */ +export const ColorField = ({ label, value, onChange, className }: ColorFieldProps) => { + const [draft, setDraft] = useState(value); + + useEffect(() => { + setDraft((current) => (normalizeHex(current) === value ? current : value)); + }, [value]); + + const swatch = ( + + + defaultStyle} /> + + + cx( + "outline-hidden", + isEntering && "duration-150 ease-out animate-in fade-in zoom-in-95", + isExiting && "duration-100 ease-in animate-out fade-out zoom-out-95", + ) + } + > + + + onChange(color.toString("hex").toLowerCase())}> +
+ + +
+ + +
+
+
+
+
+
+
+ ); + + return ( + { + setDraft(text); + const hex = normalizeHex(text); + if (hex && hex !== value) onChange(hex); + }} + onBlur={() => { + if (!normalizeHex(draft)) setDraft(value); + }} + > + + + ); +}; diff --git a/src/components/contours/contours-app-loader.tsx b/src/components/contours/contours-app-loader.tsx new file mode 100644 index 0000000..6848a20 --- /dev/null +++ b/src/components/contours/contours-app-loader.tsx @@ -0,0 +1,9 @@ +"use client"; + +import dynamic from "next/dynamic"; + +/** Leaflet touches `window` on import, so the whole app is loaded client-side only. */ +export const ContoursAppLoader = dynamic(() => import("./contours-app").then((m) => m.ContoursApp), { + ssr: false, + loading: () =>
, +}); diff --git a/src/components/contours/contours-app.tsx b/src/components/contours/contours-app.tsx new file mode 100644 index 0000000..730b532 --- /dev/null +++ b/src/components/contours/contours-app.tsx @@ -0,0 +1,55 @@ +"use client"; + +import { useCallback, useState } from "react"; +import { useContourMap } from "@/hooks/use-contour-map"; +import { DEFAULT_SETTINGS } from "@/lib/contours/constants"; +import type { PlaceResult } from "@/lib/contours/geocode"; +import type { ContourSettings } from "@/lib/contours/types"; +import { AppHeader } from "./app-header"; +import { DownloadPanel } from "./download-panel"; +import { MapView } from "./map-view"; +import { SidePanel } from "./side-panel"; +import { StylePanel } from "./style-panel"; +import { type PanelId, ToolRail } from "./tool-rail"; + +/** Open the Style panel on first load where it won't cover the map. */ +const initialPanel = (): PanelId | null => (typeof window !== "undefined" && window.matchMedia("(min-width: 720px)").matches ? "style" : null); + +export const ContoursApp = () => { + const [settings, setSettings] = useState(DEFAULT_SETTINGS); + const [panel, setPanel] = useState(initialPanel); + const map = useContourMap(settings); + + const updateSettings = useCallback((patch: Partial) => setSettings((current) => ({ ...current, ...patch })), []); + const togglePanel = useCallback((id: PanelId) => setPanel((current) => (current === id ? null : id)), []); + const closePanel = useCallback(() => setPanel(null), []); + + const goToPlace = useCallback( + (place: PlaceResult) => { + if (place.bbox) map.actions.fitBounds(place.bbox); + else map.actions.setView({ lng: place.center[0], lat: place.center[1], zoom: 11 }); + }, + [map.actions], + ); + + return ( +
+ + +
+ + +
+ + + + + + + + +
+
+
+ ); +}; diff --git a/src/components/contours/download-panel.tsx b/src/components/contours/download-panel.tsx new file mode 100644 index 0000000..3d51fbe --- /dev/null +++ b/src/components/contours/download-panel.tsx @@ -0,0 +1,65 @@ +"use client"; + +import { useState } from "react"; +import { Download01, InfoCircle } from "@untitledui/icons"; +import { Button } from "@/components/base/buttons/button"; +import type { ContourMapActions } from "@/hooks/use-contour-map"; +import { PanelSection } from "./panel-section"; + +const Code = ({ children }: { children: string }) => {children}; + +export const DownloadPanel = ({ actions }: { actions: ContourMapActions }) => { + const [savingPng, setSavingPng] = useState(false); + + return ( + <> +
+
+ + + Contours for GIS or mapping software, as overlapping MultiPolygon features with an elevation property. Geodata + only, no styles. + + } + > + + + + + + + + + + + + ); +}; diff --git a/src/components/contours/logo.tsx b/src/components/contours/logo.tsx new file mode 100644 index 0000000..6bed65b --- /dev/null +++ b/src/components/contours/logo.tsx @@ -0,0 +1,15 @@ +import { cx } from "@/utils/cx"; + +/** Nested contour rings on a brand tile: the app mark. */ +export const ContoursLogo = ({ className }: { className?: string }) => ( + +); diff --git a/src/components/contours/map-view.tsx b/src/components/contours/map-view.tsx new file mode 100644 index 0000000..de6b257 --- /dev/null +++ b/src/components/contours/map-view.tsx @@ -0,0 +1,24 @@ +"use client"; + +import type { RefObject } from "react"; +import { LoadingIndicator } from "@/components/application/loading-indicator/loading-indicator"; + +interface MapViewProps { + containerRef: RefObject; + isDrawing: boolean; +} + +/** The Leaflet container plus a floating "Drawing contours" status card. */ +export const MapView = ({ containerRef, isDrawing }: MapViewProps) => ( + <> +
+
+ {isDrawing && ( +
+ + Drawing contours… +
+ )} +
+ +); diff --git a/src/components/contours/number-field.tsx b/src/components/contours/number-field.tsx new file mode 100644 index 0000000..b5ed71c --- /dev/null +++ b/src/components/contours/number-field.tsx @@ -0,0 +1,64 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { Input, InputBase } from "@/components/base/input/input"; +import { InputGroup } from "@/components/base/input/input-group"; + +interface NumberFieldProps { + label: string; + value: number; + onChange: (value: number) => void; + /** Smallest value that will be committed. */ + min?: number; + /** Unit shown after the field, such as "px". */ + suffix?: string; + isDisabled?: boolean; + className?: string; +} + +/** + * A decimal field. The draft is committed as the user types whenever it + * parses to a valid number, and snaps back to the last good value on blur. + */ +export const NumberField = ({ label, value, onChange, min = 0, suffix, isDisabled, className }: NumberFieldProps) => { + const [draft, setDraft] = useState(String(value)); + + useEffect(() => { + setDraft((current) => (Number(current) === value ? current : String(value))); + }, [value]); + + const parse = (text: string) => { + const n = Number(text.trim()); + return text.trim() !== "" && Number.isFinite(n) && n >= min ? n : null; + }; + + const fieldProps = { + value: draft, + isDisabled, + onChange: (text: string) => { + setDraft(text); + const n = parse(text); + if (n !== null && n !== value) onChange(n); + }, + onBlur: () => { + if (parse(draft) === null) setDraft(String(value)); + }, + }; + + if (suffix) { + return ( + {suffix}} + {...fieldProps} + > + + + ); + } + + return ; +}; diff --git a/src/components/contours/panel-section.tsx b/src/components/contours/panel-section.tsx new file mode 100644 index 0000000..8d4dbbf --- /dev/null +++ b/src/components/contours/panel-section.tsx @@ -0,0 +1,23 @@ +import type { ReactNode } from "react"; +import { cx } from "@/utils/cx"; + +/** A titled block of a side panel. */ +export const PanelSection = ({ + title, + description, + children, + className, +}: { + title: string; + description?: ReactNode; + children: ReactNode; + className?: string; +}) => ( +
+
+

{title}

+ {description &&

{description}

} +
+ {children} +
+); diff --git a/src/components/contours/place-search.tsx b/src/components/contours/place-search.tsx new file mode 100644 index 0000000..e3f1a37 --- /dev/null +++ b/src/components/contours/place-search.tsx @@ -0,0 +1,106 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { SearchLg } from "@untitledui/icons"; +import { ComboBox as AriaComboBox, Group as AriaGroup, Input as AriaInput, ListBox as AriaListBox } from "react-aria-components"; +import { Popover } from "@/components/base/select/popover"; +import { SelectItem } from "@/components/base/select/select-item"; +import { MAPBOX_TOKEN, SEARCH_DELAY_MS } from "@/lib/contours/constants"; +import { type PlaceResult, geocodePlace } from "@/lib/contours/geocode"; +import { cx } from "@/utils/cx"; + +interface PlaceSearchProps { + onSelect: (place: PlaceResult) => void; +} + +/** Debounced Mapbox place search. Enter picks the first result when none is highlighted. */ +export const PlaceSearch = ({ onSelect }: PlaceSearchProps) => { + const [query, setQuery] = useState(""); + const [results, setResults] = useState([]); + const [isOpen, setIsOpen] = useState(false); + const triggerRef = useRef(null); + + useEffect(() => { + const trimmed = query.trim(); + if (trimmed.length < 2 || !MAPBOX_TOKEN) { + setResults([]); + setIsOpen(false); + return; + } + const controller = new AbortController(); + const timer = window.setTimeout(async () => { + try { + const found = await geocodePlace(trimmed, MAPBOX_TOKEN, controller.signal); + if (controller.signal.aborted) return; + setResults(found); + setIsOpen(found.length > 0); + } catch (error) { + if (!controller.signal.aborted) { + console.error(error); + setResults([]); + setIsOpen(false); + } + } + }, SEARCH_DELAY_MS); + return () => { + window.clearTimeout(timer); + controller.abort(); + }; + }, [query]); + + const choose = (place: PlaceResult | undefined) => { + if (!place) return; + onSelect(place); + setQuery(""); + setResults([]); + setIsOpen(false); + (document.activeElement as HTMLElement | null)?.blur(); + }; + + return ( + choose(results.find((r) => r.id === key))} + allowsEmptyCollection + allowsCustomValue + menuTrigger="input" + isDisabled={!MAPBOX_TOKEN} + className="w-full" + > + + cx( + "relative flex w-full items-center rounded-lg bg-primary shadow-xs ring-1 ring-primary transition-shadow duration-100 ease-linear ring-inset", + isFocusWithin && "ring-2 ring-brand", + isDisabled && "cursor-not-allowed opacity-50", + ) + } + > + + + 0} onOpenChange={setIsOpen} isNonModal> + {(place: PlaceResult) => } + + + ); +}; diff --git a/src/components/contours/side-panel.tsx b/src/components/contours/side-panel.tsx new file mode 100644 index 0000000..49b3258 --- /dev/null +++ b/src/components/contours/side-panel.tsx @@ -0,0 +1,41 @@ +"use client"; + +import type { ReactNode } from "react"; +import { CloseButton } from "@/components/base/buttons/close-button"; +import { cx } from "@/utils/cx"; +import type { PanelId } from "./tool-rail"; + +interface SidePanelProps { + id: PanelId; + title: string; + description: string; + isOpen: boolean; + onClose: () => void; + children: ReactNode; +} + +/** + * A panel that slides out beside the rail, styled like the slideout menu. + * On phones it takes the rest of the width. Closed, it is off-screen and inert. + */ +export const SidePanel = ({ id, title, description, isOpen, onClose, children }: SidePanelProps) => ( +
+
+

+ {title} +

+

{description}

+ +
+ {children} +
+); diff --git a/src/components/contours/style-panel.tsx b/src/components/contours/style-panel.tsx new file mode 100644 index 0000000..fc95782 --- /dev/null +++ b/src/components/contours/style-panel.tsx @@ -0,0 +1,164 @@ +"use client"; + +import { ButtonGroup, ButtonGroupItem } from "@/components/base/button-group/button-group"; +import { RadioButton, RadioGroup } from "@/components/base/radio-buttons/radio-buttons"; +import { Select } from "@/components/base/select/select"; +import { Toggle } from "@/components/base/toggle/toggle"; +import { BLOG_POST_URL } from "@/lib/contours/constants"; +import type { BackgroundType, BathymetryType, ContourSettings, IndexEvery, MapType, Unit } from "@/lib/contours/types"; +import { ColorField } from "./color-field"; +import { NumberField } from "./number-field"; +import { PanelSection } from "./panel-section"; + +interface StylePanelProps { + settings: ContourSettings; + onChange: (patch: Partial) => void; + /** Whether the current view has elevations below sea level; bathymetry options only show then. */ + hasBathymetry: boolean; +} + +const INDEX_OPTIONS: IndexEvery[] = [0, 2, 5, 10]; + +export const StylePanel = ({ settings, onChange, hasBathymetry }: StylePanelProps) => { + const illuminated = settings.mapType === "illuminated"; + const indexItems = INDEX_OPTIONS.map((n) => ({ id: String(n), label: n === 0 ? "None" : `${n * settings.interval} ${settings.unit}` })); + + return ( + <> + + onChange({ showLabels })} + /> + + + + onChange({ mapType: mapType as MapType })}> + + + + + + +
+ onChange({ interval })} + min={Number.MIN_VALUE} + className="flex-1" + /> + { + const unit = [...keys][0]; + if (unit === "m" || unit === "ft") onChange({ unit: unit as Unit }); + }} + > + m + ft + +
+ +
+ + + {illuminated ? ( + <> + onChange({ highlightColor })} /> + onChange({ shadowColor })} /> + onChange({ shadowWidth })} /> + + ) : ( + <> + onChange({ lineColor })} /> +
+ onChange({ lineWidth })} /> + onChange({ indexLineWidth })} + isDisabled={settings.indexEvery === 0} + /> +
+ + )} +
+ + + onChange({ background: background as BackgroundType })} + > + + + {settings.background === "solid" && ( + onChange({ solidColor })} /> + )} + + {settings.background === "hypso" && ( +
+ onChange({ hypsoLowColor })} /> + onChange({ hypsoHighColor })} /> +
+ )} +
+
+ + {hasBathymetry && ( + + onChange({ bathymetry: bathymetry as BathymetryType })} + > + + + {settings.bathymetry === "solid" && ( + onChange({ oceanColor })} /> + )} + + {settings.bathymetry === "bathy" && ( +
+ onChange({ bathyDeepColor })} /> + onChange({ bathyShallowColor })} + /> +
+ )} +
+
+ )} + +

+ Elevation comes from Mapzen's open terrain tiles, so contours cover the whole world.{" "} + + How it works + +

+ + ); +}; diff --git a/src/components/contours/tool-rail.tsx b/src/components/contours/tool-rail.tsx new file mode 100644 index 0000000..048e488 --- /dev/null +++ b/src/components/contours/tool-rail.tsx @@ -0,0 +1,49 @@ +"use client"; + +import type { FC } from "react"; +import { Brush01, Download01 } from "@untitledui/icons"; +import { ToggleButton as AriaToggleButton } from "react-aria-components"; +import { Tooltip } from "@/components/base/tooltip/tooltip"; +import { cx } from "@/utils/cx"; + +export type PanelId = "style" | "download"; + +interface ToolRailProps { + active: PanelId | null; + onToggle: (panel: PanelId) => void; +} + +/** Same look as the slim sidebar's `NavButton`, as a real toggle button. */ +const RailButton = ({ id, label, icon: Icon, active, onToggle }: { id: PanelId; label: string; icon: FC<{ className?: string }> } & ToolRailProps) => { + const isSelected = active === id; + return ( + + onToggle(id)} + className={cx( + "group/item relative flex size-10 cursor-pointer items-center justify-center rounded-md bg-primary outline-focus-ring transition duration-100 ease-linear select-none hover:bg-primary_hover focus-visible:z-10 focus-visible:outline-2 focus-visible:outline-offset-2", + isSelected && "bg-secondary hover:bg-secondary_hover", + )} + > + + + ); +}; + +/** The slim icon rail on the left with the panel toggles. */ +export const ToolRail = (props: ToolRailProps) => ( + +); diff --git a/src/hooks/use-contour-map.ts b/src/hooks/use-contour-map.ts new file mode 100644 index 0000000..34d0e22 --- /dev/null +++ b/src/hooks/use-contour-map.ts @@ -0,0 +1,300 @@ +"use client"; + +import { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react"; +import L from "leaflet"; +import { computeContours } from "@/lib/contours/compute"; +import { CANVAS_BUFFER, ESRI_ATTRIBUTION, ESRI_PLACES_URL, ESRI_ROADS_URL, MAX_ZOOM, REDRAW_DELAY_MS, randomExampleLocation } from "@/lib/contours/constants"; +import { contoursToSvg, drawContours, hasBathymetry } from "@/lib/contours/draw"; +import { canvasToPngBlob, contoursToGeoJson, downloadBlob, downloadText } from "@/lib/contours/export"; +import { formatHash, parseHash } from "@/lib/contours/hash"; +import { TerrariumLayer } from "@/lib/contours/terrarium-layer"; +import type { ContourData, ContourSettings, MapPosition } from "@/lib/contours/types"; + +/** + * How much work a pending redraw needs. A heavier request always wins over a + * lighter one that is already waiting, so a style tweak can't cancel the + * re-rasterization a pan asked for. + */ +const Work = { Draw: 0, Contours: 1, Relief: 2 } as const; +type Work = (typeof Work)[keyof typeof Work]; + +export interface ContourMapActions { + setView: (position: MapPosition) => void; + fitBounds: (bbox: [number, number, number, number]) => void; + downloadGeoJson: () => void; + downloadPng: () => Promise; + downloadSvg: () => void; +} + +export interface ContourMap { + containerRef: RefObject; + /** True while elevation data is being rasterized or contours computed. */ + isDrawing: boolean; + /** True when the current view contains elevations below sea level. */ + hasBathymetry: boolean; + actions: ContourMapActions; +} + +/** Settings that change the computed contours rather than just how they're drawn. */ +const needsRecompute = (a: ContourSettings, b: ContourSettings) => a.interval !== b.interval || a.unit !== b.unit; + +/** + * Owns the Leaflet map, the hidden elevation layer, the reference overlay and + * the contour canvas. Re-runs the elevation → contours → canvas pipeline, + * debounced, whenever the view or the settings change. + */ +export function useContourMap(settings: ContourSettings): ContourMap { + const containerRef = useRef(null); + const mapRef = useRef(null); + const demLayerRef = useRef(null); + const referenceLayerRef = useRef(null); + const contourCanvasRef = useRef(null); + const demCanvasRef = useRef(null); + const demPixelsRef = useRef(null); + const dataRef = useRef(null); + + const settingsRef = useRef(settings); + const previousSettingsRef = useRef(null); + const timerRef = useRef(null); + const pendingWorkRef = useRef(Work.Draw); + + const [isDrawing, setIsDrawing] = useState(false); + const [bathymetry, setBathymetry] = useState(false); + + const gridSize = () => { + const size = mapRef.current?.getSize() ?? L.point(0, 0); + return { width: size.x + 2 * CANVAS_BUFFER, height: size.y + 2 * CANVAS_BUFFER }; + }; + + /** Pin the contour canvas back to the top-left of the viewport after the map pane has moved. */ + const resetCanvasPosition = () => { + const map = mapRef.current; + const canvas = contourCanvasRef.current; + if (!map || !canvas) return; + L.DomUtil.setPosition(canvas, map.containerPointToLayerPoint([-CANVAS_BUFFER, -CANVAS_BUFFER])); + }; + + const draw = () => { + const canvas = contourCanvasRef.current; + const ctx = canvas?.getContext("2d"); + const data = dataRef.current; + if (!canvas || !ctx) return; + if (!data) { + ctx.clearRect(0, 0, canvas.width, canvas.height); + return; + } + drawContours(ctx, data, settingsRef.current); + }; + + const recomputeContours = () => { + const pixels = demPixelsRef.current; + if (!pixels) return; + const { width, height } = gridSize(); + const { interval, unit } = settingsRef.current; + dataRef.current = computeContours(pixels, width, height, interval, unit); + setBathymetry(dataRef.current ? hasBathymetry(dataRef.current) : false); + draw(); + }; + + const rasterizeRelief = () => { + const map = mapRef.current; + const demLayer = demLayerRef.current; + const demCanvas = demCanvasRef.current; + const container = containerRef.current; + const ctx = demCanvas?.getContext("2d", { willReadFrequently: true }); + if (!map || !demLayer || !demCanvas || !ctx || !container) return; + + const { width, height } = gridSize(); + ctx.clearRect(0, 0, width, height); + resetCanvasPosition(); + demLayer.rasterize(ctx, container, map.getZoom(), CANVAS_BUFFER); + demPixelsRef.current = ctx.getImageData(0, 0, width, height).data; + recomputeContours(); + }; + + const runWork = (work: Work) => { + if (work === Work.Relief) rasterizeRelief(); + else if (work === Work.Contours) recomputeContours(); + else draw(); + }; + + const cancelPending = () => { + if (timerRef.current !== null) { + window.clearTimeout(timerRef.current); + timerRef.current = null; + } + }; + + /** Debounce a redraw; the heaviest requested level of work is what runs. */ + const schedule = (work: Work) => { + pendingWorkRef.current = Math.max(pendingWorkRef.current, work) as Work; + cancelPending(); + timerRef.current = window.setTimeout(() => { + timerRef.current = null; + const pending = pendingWorkRef.current; + pendingWorkRef.current = Work.Draw; + setIsDrawing(true); + // Two frames so the "Drawing contours" state paints before the heavy work blocks the thread. + requestAnimationFrame(() => + requestAnimationFrame(() => { + try { + runWork(pending); + } finally { + setIsDrawing(false); + } + }), + ); + }, REDRAW_DELAY_MS); + }; + + // Keep the latest settings reachable from the scheduled callbacks. + settingsRef.current = settings; + + // Create the map once. + useEffect(() => { + const container = containerRef.current; + if (!container || mapRef.current) return; + + const map = L.map(container, { scrollWheelZoom: false, zoomControl: false, maxZoom: MAX_ZOOM }); + mapRef.current = map; + L.control.zoom({ position: "topright" }).addTo(map); + + const demLayer = new TerrariumLayer(() => schedule(Work.Relief)); + demLayerRef.current = demLayer; + demLayer.addTo(map); + + const contourPane = map.createPane("contour"); + contourPane.style.zIndex = "400"; + const contourCanvas = document.createElement("canvas"); + contourCanvas.id = "contours"; + contourCanvas.style.position = "absolute"; + contourCanvas.style.top = "0"; + contourCanvas.style.left = "0"; + contourPane.appendChild(contourCanvas); + contourCanvasRef.current = contourCanvas; + demCanvasRef.current = document.createElement("canvas"); + + const labelPane = map.createPane("labels"); + labelPane.style.zIndex = "450"; + labelPane.style.pointerEvents = "none"; + const roads = L.tileLayer(ESRI_ROADS_URL, { maxZoom: MAX_ZOOM, pane: "labels", opacity: 0.5, attribution: ESRI_ATTRIBUTION }); + const places = L.tileLayer(ESRI_PLACES_URL, { maxZoom: MAX_ZOOM, pane: "labels" }); + const referenceLayer = L.layerGroup([roads, places]); + referenceLayerRef.current = referenceLayer; + if (settingsRef.current.showLabels) referenceLayer.addTo(map); + + const resizeCanvases = () => { + const { width, height } = gridSize(); + for (const canvas of [contourCanvas, demCanvasRef.current]) { + if (!canvas) continue; + canvas.width = width; + canvas.height = height; + } + }; + resizeCanvases(); + + const syncHash = () => { + const center = map.getCenter(); + const hash = formatHash({ zoom: map.getZoom(), lat: center.lat, lng: center.lng }); + if (window.location.hash !== hash) window.history.replaceState(null, "", hash); + }; + const onHashChange = () => { + const position = parseHash(window.location.hash); + if (!position) return; + const center = map.getCenter(); + const unchanged = map.getZoom() === position.zoom && Math.abs(center.lat - position.lat) < 1e-6 && Math.abs(center.lng - position.lng) < 1e-6; + if (!unchanged) map.setView([position.lat, position.lng], position.zoom); + }; + + map.on("move", cancelPending); + map.on("moveend", () => { + // Clear right away; stale contours sliding around are worse than a blank map. + contourCanvas.getContext("2d")?.clearRect(0, 0, contourCanvas.width, contourCanvas.height); + syncHash(); + schedule(Work.Relief); + }); + map.on("resize", () => { + resizeCanvases(); + schedule(Work.Relief); + }); + window.addEventListener("hashchange", onHashChange); + + const start = parseHash(window.location.hash) ?? randomExampleLocation(); + map.setView([start.lat, start.lng], start.zoom); + resetCanvasPosition(); + + return () => { + cancelPending(); + window.removeEventListener("hashchange", onHashChange); + map.remove(); + mapRef.current = null; + demLayerRef.current = null; + referenceLayerRef.current = null; + contourCanvasRef.current = null; + demCanvasRef.current = null; + demPixelsRef.current = null; + dataRef.current = null; + }; + // The map is created once; later changes flow through refs and the settings effect. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + // React to setting changes. + useEffect(() => { + const previous = previousSettingsRef.current; + previousSettingsRef.current = settings; + if (!previous) return; + + const map = mapRef.current; + const referenceLayer = referenceLayerRef.current; + if (map && referenceLayer && previous.showLabels !== settings.showLabels) { + if (settings.showLabels) referenceLayer.addTo(map); + else referenceLayer.removeFrom(map); + } + + if (needsRecompute(previous, settings)) schedule(Work.Contours); + else if (previous !== settings) schedule(Work.Draw); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [settings]); + + const setView = useCallback(({ lat, lng, zoom }: MapPosition) => { + mapRef.current?.setView([lat, lng], zoom); + }, []); + + const fitBounds = useCallback(([west, south, east, north]: [number, number, number, number]) => { + mapRef.current?.fitBounds([ + [south, west], + [north, east], + ]); + }, []); + + const downloadGeoJson = useCallback(() => { + const map = mapRef.current; + const data = dataRef.current; + if (!map || !data) return; + const geojson = contoursToGeoJson(data, (x, y) => { + const ll = map.containerPointToLatLng([x - CANVAS_BUFFER, y - CANVAS_BUFFER]); + return [ll.lng, ll.lat]; + }); + downloadText(JSON.stringify(geojson), "contours.geojson", "application/geo+json"); + }, []); + + const downloadPng = useCallback(async () => { + const canvas = contourCanvasRef.current; + if (!canvas) return; + downloadBlob(await canvasToPngBlob(canvas, CANVAS_BUFFER), "contours.png"); + }, []); + + const downloadSvg = useCallback(() => { + const data = dataRef.current; + if (!data) return; + downloadText(contoursToSvg(data, settingsRef.current, CANVAS_BUFFER), "contours.svg", "image/svg+xml;charset=utf-8"); + }, []); + + const actions = useMemo( + () => ({ setView, fitBounds, downloadGeoJson, downloadPng, downloadSvg }), + [setView, fitBounds, downloadGeoJson, downloadPng, downloadSvg], + ); + + return { containerRef, isDrawing, hasBathymetry: bathymetry, actions }; +} diff --git a/src/hooks/use-resize-observer.ts b/src/hooks/use-resize-observer.ts new file mode 100644 index 0000000..948c70c --- /dev/null +++ b/src/hooks/use-resize-observer.ts @@ -0,0 +1,67 @@ +import { useEffect } from "react"; +import type { RefObject } from "react"; + +/** + * Checks if the ResizeObserver API is supported. + * @returns True if the ResizeObserver API is supported, false otherwise. + */ +function hasResizeObserver() { + return typeof window.ResizeObserver !== "undefined"; +} + +/** + * The options for the useResizeObserver hook. + */ +type useResizeObserverOptionsType = { + /** + * The ref to the element to observe. + */ + ref: RefObject | undefined; + /** + * The box to observe. + */ + box?: ResizeObserverBoxOptions; + /** + * The callback function to call when the size changes. + */ + onResize: () => void; +}; + +/** + * A hook that observes the size of an element and calls a callback function when the size changes. + * @param options - The options for the hook. + */ +export function useResizeObserver(options: useResizeObserverOptionsType) { + const { ref, box, onResize } = options; + + useEffect(() => { + const element = ref?.current; + if (!element) { + return; + } + + if (!hasResizeObserver()) { + window.addEventListener("resize", onResize, false); + + return () => { + window.removeEventListener("resize", onResize, false); + }; + } else { + const resizeObserverInstance = new window.ResizeObserver((entries) => { + if (!entries.length) { + return; + } + + onResize(); + }); + + resizeObserverInstance.observe(element, { box }); + + return () => { + if (element) { + resizeObserverInstance.unobserve(element); + } + }; + } + }, [onResize, ref, box]); +} diff --git a/src/lib/contours/color.ts b/src/lib/contours/color.ts new file mode 100644 index 0000000..f4aa2c6 --- /dev/null +++ b/src/lib/contours/color.ts @@ -0,0 +1,32 @@ +import { interpolateHclLong } from "d3-interpolate"; +import { type ScaleLinear, scaleLinear } from "d3-scale"; + +const HEX_RE = /^#?([0-9a-f]{3}|[0-9a-f]{6})$/i; + +/** Accepts `#abc`, `abc`, `#aabbcc` or `aabbcc`; returns lowercase `#aabbcc`, or null if it isn't a hex color. */ +export function normalizeHex(input: string): string | null { + const match = HEX_RE.exec(input.trim()); + if (!match) return null; + let hex = match[1].toLowerCase(); + if (hex.length === 3) hex = hex.replace(/./g, (c) => c + c); + return `#${hex}`; +} + +/** `#rrggbb` → `rgba(r,g,b,alpha)`. */ +export function hexWithAlpha(hex: string, alpha: number): string { + const normalized = normalizeHex(hex) ?? "#000000"; + const r = parseInt(normalized.slice(1, 3), 16); + const g = parseInt(normalized.slice(3, 5), 16); + const b = parseInt(normalized.slice(5, 7), 16); + return `rgba(${r},${g},${b},${alpha})`; +} + +export type ColorScale = ScaleLinear; + +/** Hypsometric tints: low → high elevation, interpolated the long way round the HCL hue circle. */ +export const makeHypsoScale = (low: string, high: string, domain: [number, number]): ColorScale => + scaleLinear().domain(domain).range([low, high]).interpolate(interpolateHclLong); + +/** Bathymetric tints: deep → shallow, plain RGB interpolation. */ +export const makeBathyScale = (deep: string, shallow: string, domain: [number, number]): ColorScale => + scaleLinear().domain(domain).range([deep, shallow]); diff --git a/src/lib/contours/compute.ts b/src/lib/contours/compute.ts new file mode 100644 index 0000000..b60bd28 --- /dev/null +++ b/src/lib/contours/compute.ts @@ -0,0 +1,20 @@ +import { type ContourMultiPolygon, contours as d3Contours } from "d3-contour"; +import { decodeElevationGrid } from "./elevation"; +import { computeThresholds } from "./thresholds"; +import type { ContourData, Unit } from "./types"; + +/** + * Compute contour polygons for a viewport-sized grid of Terrarium pixels. + * Returns null when no usable elevation data is present (for example before + * any tile has loaded). + */ +export function computeContours(pixels: Uint8ClampedArray, width: number, height: number, interval: number, unit: Unit): ContourData | null { + const values = decodeElevationGrid(pixels, width, height, unit); + const range = computeThresholds(values, interval); + if (!range) return null; + + // d3-contour's typings ask for number[], but it reads any array-like at runtime. + const contours: ContourMultiPolygon[] = d3Contours().size([width, height]).thresholds(range.thresholds)(values as unknown as number[]); + + return { contours, min: range.min, max: range.max, interval, unit, width, height }; +} diff --git a/src/lib/contours/constants.ts b/src/lib/contours/constants.ts new file mode 100644 index 0000000..bd5e1cc --- /dev/null +++ b/src/lib/contours/constants.ts @@ -0,0 +1,67 @@ +import type { ContourSettings, MapPosition } from "./types"; + +/** Mapzen Terrarium elevation tiles, hosted on AWS. Keyless, max zoom 15. */ +export const TERRARIUM_TILE_URL = "https://s3.amazonaws.com/elevation-tiles-prod/terrarium/{z}/{x}/{y}.png"; +export const TERRARIUM_ATTRIBUTION = 'Elevation: Terrain Tiles (Mapzen / AWS)'; + +/** Esri's keyless reference overlays: roads, then boundaries and place labels. */ +export const ESRI_ROADS_URL = "https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Transportation/MapServer/tile/{z}/{y}/{x}"; +export const ESRI_PLACES_URL = "https://server.arcgisonline.com/ArcGIS/rest/services/Reference/World_Boundaries_and_Places/MapServer/tile/{z}/{y}/{x}"; +export const ESRI_ATTRIBUTION = 'Roads & labels © Esri, HERE, Garmin, OpenStreetMap contributors'; + +export const MAX_ZOOM = 15; + +/** Extra pixels computed around the viewport so contour lines don't stop short of the edges. */ +export const CANVAS_BUFFER = 5; + +/** Elevation bands with fewer pixels than this are treated as junk when choosing thresholds. */ +export const MIN_PIXELS_PER_BAND = 50; + +/** Wait this long after the last move, tile load or setting change before recomputing. */ +export const REDRAW_DELAY_MS = 500; +export const SEARCH_DELAY_MS = 250; + +export const METERS_TO_FEET = 3.28084; + +export const AXIS_MAPS_URL = "https://www.axismaps.com/"; +export const AXIS_MAPS_LOGO_URL = "https://www.axismaps.com/axis-logo.png"; +export const BLOG_POST_URL = "https://www.axismaps.com/blog/contour-maps-in-a-web-browser"; + +export const MAPBOX_TOKEN = process.env.NEXT_PUBLIC_MAPBOX_TOKEN ?? ""; + +export const EXAMPLE_LOCATIONS: ReadonlyArray<{ name: string } & MapPosition> = [ + { name: "Mount Fuji", lat: 35.3577, lng: 138.7331, zoom: 13 }, + { name: "Big Island, Hawaii", lat: 19.6801, lng: -155.5132, zoom: 9 }, + { name: "Grand Canyon", lat: 36.0469, lng: -113.8416, zoom: 13 }, + { name: "Mount Everest", lat: 27.9885, lng: 86.9233, zoom: 12 }, + { name: "Mount Rainier", lat: 46.8358, lng: -121.7663, zoom: 11 }, + { name: "White Mountains", lat: 44.0859, lng: -71.4441, zoom: 11 }, +]; + +export const randomExampleLocation = (): MapPosition => EXAMPLE_LOCATIONS[Math.floor(Math.random() * EXAMPLE_LOCATIONS.length)]; + +export const DEFAULT_SETTINGS: ContourSettings = { + showLabels: true, + mapType: "lines", + interval: 500, + unit: "ft", + indexEvery: 0, + + lineColor: "#8c7556", + lineWidth: 0.75, + indexLineWidth: 1.5, + + highlightColor: "#fffbec", + shadowColor: "#5b5143", + shadowWidth: 2, + + background: "none", + solidColor: "#fffcfa", + hypsoLowColor: "#79a483", + hypsoHighColor: "#ffdecb", + + bathymetry: "none", + oceanColor: "#d5f2ff", + bathyDeepColor: "#315d9b", + bathyShallowColor: "#d5f2ff", +}; diff --git a/src/lib/contours/draw.ts b/src/lib/contours/draw.ts new file mode 100644 index 0000000..71d42a3 --- /dev/null +++ b/src/lib/contours/draw.ts @@ -0,0 +1,154 @@ +import type { ContourMultiPolygon } from "d3-contour"; +import { geoPath } from "d3-geo"; +import { type ColorScale, hexWithAlpha, makeBathyScale, makeHypsoScale } from "./color"; +import type { BathymetryType, ContourData, ContourSettings } from "./types"; + +/** Fixed stroke and shadow used below sea level in illuminated mode. */ +const UNDERWATER_SHADOW = "#4e5c66"; +const UNDERWATER_HIGHLIGHT = "rgba(224,242,255,0.5)"; +/** In illuminated mode the fill can't be transparent, so "none" is white. */ +const ILLUMINATED_DEFAULT_FILL = "#fff"; + +interface ResolvedStyle { + settings: ContourSettings; + /** Bathymetry only applies when the view actually dips below sea level. */ + bathymetry: BathymetryType; + hypso: ColorScale; + bathy: ColorScale; + /** Elevation step between index lines, or 0 for none. */ + indexInterval: number; + highlight: string; +} + +export const hasBathymetry = (data: ContourData): boolean => data.min < 0; + +export function resolveStyle(data: ContourData, settings: ContourSettings): ResolvedStyle { + const bathymetry = hasBathymetry(data) ? settings.bathymetry : "none"; + // When the sea floor is colored separately the land tints start at sea level. + const hypsoDomain: [number, number] = bathymetry !== "none" ? [0, data.max] : [data.min, data.max]; + return { + settings, + bathymetry, + hypso: makeHypsoScale(settings.hypsoLowColor, settings.hypsoHighColor, hypsoDomain), + bathy: makeBathyScale(settings.bathyDeepColor, settings.bathyShallowColor, [data.min, -1]), + indexInterval: settings.mapType === "lines" ? settings.indexEvery * settings.interval : 0, + highlight: hexWithAlpha(settings.highlightColor, 0.5), + }; +} + +/** The fill for one contour polygon, or null for no fill. */ +export function fillFor(value: number, style: ResolvedStyle): string | null { + const { settings, bathymetry } = style; + if (value >= 0 || bathymetry === "none") { + if (settings.background === "hypso") return style.hypso(value); + if (settings.background === "solid") return settings.solidColor; + // Mask the sea floor with white when it is colored but the land isn't. + return bathymetry !== "none" ? "#fff" : null; + } + if (bathymetry === "bathy") return style.bathy(value); + if (bathymetry === "solid") return settings.oceanColor; + return null; +} + +const isIndexLine = (value: number, indexInterval: number) => indexInterval !== 0 && value % indexInterval === 0; + +/** Draw the contours onto a canvas the size of `data.width` × `data.height`. */ +export function drawContours(ctx: CanvasRenderingContext2D, data: ContourData, settings: ContourSettings): void { + const style = resolveStyle(data, settings); + const path = geoPath().context(ctx); + const { contours } = data; + + ctx.clearRect(0, 0, data.width, data.height); + ctx.save(); + ctx.lineJoin = "round"; + + if (settings.mapType === "illuminated") { + // A light stroke first, then the fill with a drop shadow: one edge of each + // band reads as lit and the other as shaded, so the terrain looks raised. + ctx.lineWidth = settings.shadowWidth + 1; + ctx.shadowBlur = settings.shadowWidth; + ctx.shadowOffsetX = settings.shadowWidth; + ctx.shadowOffsetY = settings.shadowWidth; + + for (const c of contours) { + const underwater = c.value < 0 && style.bathymetry !== "none"; + ctx.beginPath(); + ctx.shadowColor = underwater ? UNDERWATER_SHADOW : settings.shadowColor; + ctx.strokeStyle = underwater ? UNDERWATER_HIGHLIGHT : style.highlight; + ctx.fillStyle = fillFor(c.value, style) ?? ILLUMINATED_DEFAULT_FILL; + path(c); + ctx.stroke(); + ctx.fill(); + } + } else { + ctx.lineWidth = settings.lineWidth; + ctx.strokeStyle = settings.lineColor; + + if (settings.background !== "hypso" && style.bathymetry === "none") { + // No fill or one solid fill: every contour can go into a single path. + ctx.beginPath(); + for (const c of contours) { + if (!isIndexLine(c.value, style.indexInterval)) path(c); + } + if (settings.background === "solid") { + ctx.fillStyle = settings.solidColor; + ctx.fill(); + } + ctx.stroke(); + } else { + // Tints differ per contour, so fill and stroke them one at a time. + for (const c of contours) { + ctx.beginPath(); + path(c); + const fill = fillFor(c.value, style); + if (fill) { + ctx.fillStyle = fill; + ctx.fill(); + } + ctx.stroke(); + } + } + + if (style.indexInterval !== 0) { + ctx.lineWidth = settings.indexLineWidth; + ctx.beginPath(); + for (const c of contours) { + if (isIndexLine(c.value, style.indexInterval)) path(c); + } + ctx.stroke(); + } + } + + ctx.restore(); +} + +const escapeAttribute = (value: string) => value.replace(/&/g, "&").replace(/"/g, """).replace(/` per contour with the stroke and fill of the current style. + */ +export function contoursToSvg(data: ContourData, settings: ContourSettings, buffer: number): string { + const style = resolveStyle(data, settings); + const path = geoPath(); + const width = data.width - 2 * buffer; + const height = data.height - 2 * buffer; + const lines = settings.mapType === "lines"; + + const paths = data.contours.map((c) => { + const d = path(c); + if (!d) return ""; + const strokeWidth = lines ? (isIndexLine(c.value, style.indexInterval) ? settings.indexLineWidth : settings.lineWidth) : settings.shadowWidth; + const stroke = lines ? settings.lineColor : style.highlight; + const fill = fillFor(c.value, style) ?? "none"; + return ``; + }); + + return [ + ``, + ``, + ...paths, + "", + "", + ].join("\n"); +} diff --git a/src/lib/contours/elevation.ts b/src/lib/contours/elevation.ts new file mode 100644 index 0000000..104586c --- /dev/null +++ b/src/lib/contours/elevation.ts @@ -0,0 +1,22 @@ +import { METERS_TO_FEET } from "./constants"; +import type { Unit } from "./types"; + +/** Decode one Terrarium pixel (red, green, blue) to meters above sea level. */ +export const terrariumToMeters = (r: number, g: number, b: number): number => r * 256 + g + b / 256 - 32768; + +/** + * Turn the RGBA pixels of a canvas that has Terrarium tiles drawn on it into a + * row-major grid of rounded elevations in the requested unit, laid out the way + * d3-contour expects (`x + y * width`). + */ +export function decodeElevationGrid(pixels: Uint8ClampedArray, width: number, height: number, unit: Unit): Int32Array { + const factor = unit === "ft" ? METERS_TO_FEET : 1; + const values = new Int32Array(width * height); + for (let y = 0; y < height; y++) { + for (let x = 0; x < width; x++) { + const i = (y * width + x) * 4; + values[y * width + x] = Math.round(terrariumToMeters(pixels[i], pixels[i + 1], pixels[i + 2]) * factor); + } + } + return values; +} diff --git a/src/lib/contours/export.ts b/src/lib/contours/export.ts new file mode 100644 index 0000000..4b67d1b --- /dev/null +++ b/src/lib/contours/export.ts @@ -0,0 +1,50 @@ +import type { Feature, FeatureCollection, MultiPolygon, Position } from "geojson"; +import type { ContourData } from "./types"; + +export type PixelToLngLat = (x: number, y: number) => [number, number]; + +/** + * Convert the contours to GeoJSON: one MultiPolygon feature per contour with + * an `elevation` property, using `toLngLat` to turn canvas pixels into + * longitude/latitude. + */ +export function contoursToGeoJson(data: ContourData, toLngLat: PixelToLngLat): FeatureCollection { + const features: Feature[] = data.contours.map((c) => ({ + type: "Feature", + properties: { elevation: c.value }, + geometry: { + type: "MultiPolygon", + coordinates: c.coordinates.map((polygon) => polygon.map((ring) => ring.map(([x, y]): Position => toLngLat(x, y)))), + }, + })); + return { type: "FeatureCollection", features }; +} + +/** Copy the canvas without its buffer margin and encode it as a PNG. */ +export function canvasToPngBlob(source: HTMLCanvasElement, buffer: number): Promise { + const cropped = document.createElement("canvas"); + cropped.width = source.width - 2 * buffer; + cropped.height = source.height - 2 * buffer; + const ctx = cropped.getContext("2d"); + if (!ctx) return Promise.reject(new Error("Canvas 2D context unavailable")); + ctx.drawImage(source, -buffer, -buffer); + return new Promise((resolve, reject) => { + cropped.toBlob((blob) => (blob ? resolve(blob) : reject(new Error("PNG encoding failed"))), "image/png"); + }); +} + +/** Trigger a browser download of a blob. */ +export function downloadBlob(blob: Blob, filename: string): void { + const url = URL.createObjectURL(blob); + const link = document.createElement("a"); + link.style.display = "none"; + link.href = url; + link.download = filename; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + // Give the browser a moment to start the download before releasing the URL. + setTimeout(() => URL.revokeObjectURL(url), 1000); +} + +export const downloadText = (text: string, filename: string, mime: string): void => downloadBlob(new Blob([text], { type: mime }), filename); diff --git a/src/lib/contours/geocode.ts b/src/lib/contours/geocode.ts new file mode 100644 index 0000000..d876904 --- /dev/null +++ b/src/lib/contours/geocode.ts @@ -0,0 +1,28 @@ +export interface PlaceResult { + id: string; + name: string; + /** [lng, lat] */ + center: [number, number]; + /** [west, south, east, north] */ + bbox?: [number, number, number, number]; +} + +interface MapboxFeature { + id: string; + place_name: string; + center: [number, number]; + bbox?: [number, number, number, number]; +} + +/** Forward-geocode a place name with the Mapbox Geocoding API. */ +export async function geocodePlace(query: string, token: string, signal?: AbortSignal): Promise { + const url = new URL(`https://api.mapbox.com/geocoding/v5/mapbox.places/${encodeURIComponent(query)}.json`); + url.searchParams.set("language", "en"); + url.searchParams.set("types", "place,locality,neighborhood,poi"); + url.searchParams.set("access_token", token); + + const response = await fetch(url, { signal }); + if (!response.ok) throw new Error(`Geocoding failed: ${response.status}`); + const json = (await response.json()) as { features?: MapboxFeature[] }; + return (json.features ?? []).map((f) => ({ id: f.id, name: f.place_name, center: f.center, bbox: f.bbox })); +} diff --git a/src/lib/contours/hash.ts b/src/lib/contours/hash.ts new file mode 100644 index 0000000..c641f66 --- /dev/null +++ b/src/lib/contours/hash.ts @@ -0,0 +1,19 @@ +import type { MapPosition } from "./types"; + +/** Parse a `#zoom/lat/lng` hash. Returns null for anything else. */ +export function parseHash(hash: string): MapPosition | null { + const parts = hash.replace(/^#/, "").split("/"); + if (parts.length !== 3) return null; + const zoom = parseInt(parts[0], 10); + const lat = parseFloat(parts[1]); + const lng = parseFloat(parts[2]); + if (!Number.isFinite(zoom) || !Number.isFinite(lat) || !Number.isFinite(lng)) return null; + if (Math.abs(lat) > 90) return null; + return { zoom, lat, lng }; +} + +/** Format a position as `#zoom/lat/lng`, with precision that suits the zoom. */ +export function formatHash({ zoom, lat, lng }: MapPosition): string { + const precision = Math.max(0, Math.ceil(Math.log(zoom) / Math.LN2)); + return `#${parseFloat(zoom.toFixed(5))}/${lat.toFixed(precision)}/${lng.toFixed(precision)}`; +} diff --git a/src/lib/contours/terrarium-layer.ts b/src/lib/contours/terrarium-layer.ts new file mode 100644 index 0000000..f4d38c0 --- /dev/null +++ b/src/lib/contours/terrarium-layer.ts @@ -0,0 +1,57 @@ +import L from "leaflet"; +import { MAX_ZOOM, TERRARIUM_ATTRIBUTION, TERRARIUM_TILE_URL } from "./constants"; + +/** Shape of Leaflet's private per-tile record, which is the only way to walk the loaded tiles. */ +interface InternalTile { + el: HTMLElement; + coords: L.Coords; + current: boolean; +} + +/** + * A grid layer that fetches Mapzen Terrarium PNGs but never shows them. Each + * tile is an empty div; the decoded image is kept aside so it can be drawn to + * an offscreen canvas and read back as elevation data. + */ +export class TerrariumLayer extends L.GridLayer { + private readonly images = new WeakMap(); + + constructor(private readonly onTileSettled: () => void) { + super({ maxZoom: MAX_ZOOM, attribution: TERRARIUM_ATTRIBUTION }); + } + + protected createTile(coords: L.Coords, done: L.DoneCallback): HTMLElement { + const tile = L.DomUtil.create("div", "leaflet-tile"); + const img = new Image(); + img.crossOrigin = "anonymous"; + this.images.set(tile, img); + img.onload = () => { + done(undefined, tile); + this.onTileSettled(); + }; + img.onerror = () => { + // A missing tile must not stall the redraw. + done(new Error(`Tile ${coords.z}/${coords.x}/${coords.y} failed`), tile); + this.onTileSettled(); + }; + img.src = L.Util.template(TERRARIUM_TILE_URL, { z: coords.z, x: coords.x, y: coords.y }); + return tile; + } + + /** + * Draw every loaded tile of the given zoom onto `ctx`, positioned relative to + * the map container and shifted by `buffer` pixels. + */ + rasterize(ctx: CanvasRenderingContext2D, container: HTMLElement, zoom: number, buffer: number): void { + const tiles = (this as unknown as { _tiles: Record })._tiles; + const mapRect = container.getBoundingClientRect(); + for (const key in tiles) { + const tile = tiles[key]; + if (tile.coords.z !== zoom) continue; + const img = this.images.get(tile.el); + if (!img || !img.complete || !img.naturalWidth) continue; + const rect = tile.el.getBoundingClientRect(); + ctx.drawImage(img, rect.left - mapRect.left + buffer, rect.top - mapRect.top + buffer, rect.width, rect.height); + } + } +} diff --git a/src/lib/contours/thresholds.ts b/src/lib/contours/thresholds.ts new file mode 100644 index 0000000..192440b --- /dev/null +++ b/src/lib/contours/thresholds.ts @@ -0,0 +1,50 @@ +import { MIN_PIXELS_PER_BAND } from "./constants"; + +export interface ThresholdRange { + min: number; + max: number; + thresholds: number[]; +} + +/** + * Pick the contour thresholds for a grid of elevations: multiples of `interval` + * from the lowest to the highest band that holds a meaningful number of pixels. + * Bands with fewer than `minPixels` pixels at either end are treated as junk + * (tile seams, no-data pixels) and dropped. + */ +export function computeThresholds(values: ArrayLike, interval: number, minPixels = MIN_PIXELS_PER_BAND): ThresholdRange | null { + if (!Number.isFinite(interval) || interval <= 0 || values.length === 0) return null; + + let rawMin = Infinity; + let rawMax = -Infinity; + for (let i = 0; i < values.length; i++) { + const v = values[i]; + if (v < rawMin) rawMin = v; + if (v > rawMax) rawMax = v; + } + + const min = Math.floor(rawMin / interval) * interval; + const max = Math.ceil(rawMax / interval) * interval; + const bandCount = Math.round((max - min) / interval) + 1; + if (!Number.isFinite(bandCount) || bandCount < 1) return null; + + const counts = new Uint32Array(bandCount); + for (let i = 0; i < values.length; i++) { + counts[Math.floor((values[i] - min) / interval)]++; + } + + let first = -1; + let last = -1; + for (let b = 0; b < bandCount; b++) { + if (counts[b] > minPixels) { + if (first === -1) first = b; + last = b; + } + } + if (first === -1) return null; + + const thresholds: number[] = []; + for (let b = first; b <= last; b++) thresholds.push(min + b * interval); + + return { min: thresholds[0], max: thresholds[thresholds.length - 1], thresholds }; +} diff --git a/src/lib/contours/types.ts b/src/lib/contours/types.ts new file mode 100644 index 0000000..006b3c8 --- /dev/null +++ b/src/lib/contours/types.ts @@ -0,0 +1,56 @@ +import type { ContourMultiPolygon } from "d3-contour"; + +export type MapType = "lines" | "illuminated"; +export type Unit = "ft" | "m"; +export type BackgroundType = "none" | "solid" | "hypso"; +export type BathymetryType = "none" | "solid" | "bathy"; +export type IndexEvery = 0 | 2 | 5 | 10; + +/** Everything the user can change in the Style panel. */ +export interface ContourSettings { + showLabels: boolean; + mapType: MapType; + /** Contour interval in `unit`. */ + interval: number; + unit: Unit; + /** Draw a heavier index line every N intervals (0 = none). Lines mode only. */ + indexEvery: IndexEvery; + + lineColor: string; + lineWidth: number; + indexLineWidth: number; + + /** Illuminated mode: light stroke (drawn at 50% alpha), dark drop shadow and its width. */ + highlightColor: string; + shadowColor: string; + shadowWidth: number; + + background: BackgroundType; + solidColor: string; + hypsoLowColor: string; + hypsoHighColor: string; + + bathymetry: BathymetryType; + oceanColor: string; + bathyDeepColor: string; + bathyShallowColor: string; +} + +/** The result of one contour computation over the current viewport. */ +export interface ContourData { + contours: ContourMultiPolygon[]; + /** Lowest and highest contour threshold, in `unit`. */ + min: number; + max: number; + interval: number; + unit: Unit; + /** Size of the pixel grid the contours were computed on (viewport plus buffer on each side). */ + width: number; + height: number; +} + +export interface MapPosition { + lat: number; + lng: number; + zoom: number; +} diff --git a/src/providers/router-provider.tsx b/src/providers/router-provider.tsx new file mode 100644 index 0000000..9a1aa66 --- /dev/null +++ b/src/providers/router-provider.tsx @@ -0,0 +1,17 @@ +"use client"; + +import type { PropsWithChildren } from "react"; +import { useRouter } from "next/navigation"; +import { RouterProvider } from "react-aria-components"; + +declare module "react-aria-components" { + interface RouterConfig { + routerOptions: NonNullable["push"]>[1]>; + } +} + +export const RouteProvider = ({ children }: PropsWithChildren) => { + const router = useRouter(); + + return {children}; +}; diff --git a/src/styles/globals.css b/src/styles/globals.css new file mode 100755 index 0000000..4b02011 --- /dev/null +++ b/src/styles/globals.css @@ -0,0 +1,62 @@ +@import "tailwindcss"; +@import "./theme.css"; +@import "leaflet/dist/leaflet.css"; +@import "./map.css"; + +@plugin "tailwindcss-react-aria-components"; +@plugin "tailwindcss-animate"; + +@custom-variant dark (&:where(.dark-mode, .dark-mode *)); +@custom-variant label (& [data-label]); +@custom-variant focus-input-within (&:has(input:focus)); + +@utility scrollbar-hide { + /* For Webkit-based browsers (Chrome, Safari and Opera) */ + &::-webkit-scrollbar { + display: none; + -webkit-appearance: none; + } + + /* For IE, Edge and Firefox */ + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +@utility transition-inherit-all { + transition-property: inherit; + transition-duration: inherit; + transition-timing-function: inherit; +} + +html, +body { + font-family: var(--font-body); + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-font-variant-ligatures: contextual; + font-variant-ligatures: contextual; + -webkit-font-kerning: normal; + font-kerning: normal; +} + +/* Hide the default expand arrow on Safari. */ +details summary::-webkit-details-marker { + display: none; +} + +/* Hide default arrows from number inputs. */ +/* Chrome, Safari, Edge, Opera */ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} +/* Firefox */ +input[type="number"] { + -moz-appearance: textfield; +} + +/* Hide the default clear button (X) from search inputs. */ +input[type="search"]::-webkit-search-cancel-button { + -webkit-appearance: none; +} diff --git a/src/styles/map.css b/src/styles/map.css new file mode 100644 index 0000000..549a53d --- /dev/null +++ b/src/styles/map.css @@ -0,0 +1,77 @@ +/* Leaflet chrome, restyled with the design tokens. */ + +.leaflet-container { + background: var(--color-bg-primary); + font-family: var(--font-body); +} + +canvas#contours { + position: absolute; + top: 0; + left: 0; +} + +.leaflet-top { + top: calc(var(--spacing) * 4); +} +.leaflet-right { + right: calc(var(--spacing) * 4); +} +.leaflet-top .leaflet-control { + margin: 0; +} + +/* Zoom control as a vertical button group. */ +.leaflet-bar { + border: 0 !important; + border-radius: var(--radius-lg) !important; + overflow: hidden; + background: var(--color-bg-primary); + box-shadow: + var(--shadow-xs), + 0 0 0 1px var(--color-border-primary) !important; +} +.leaflet-bar a { + width: 36px !important; + height: 36px !important; + line-height: 36px !important; + color: var(--color-fg-quaternary) !important; + background: var(--color-bg-primary) !important; + border-bottom: 1px solid var(--color-border-secondary) !important; + font-weight: 500; + transition: + background-color 100ms linear, + color 100ms linear; +} +.leaflet-bar a:last-child { + border-bottom: 0 !important; +} +.leaflet-bar a:hover { + color: var(--color-fg-quaternary_hover) !important; + background: var(--color-bg-primary_hover) !important; +} +.leaflet-bar a:focus-visible { + outline: 2px solid var(--color-focus-ring); + outline-offset: -2px; +} +.leaflet-bar a.leaflet-disabled { + opacity: 0.5; +} + +.leaflet-control-attribution { + font-family: var(--font-body); + font-size: var(--text-xs); + color: var(--color-text-tertiary) !important; + background: color-mix(in srgb, var(--color-bg-primary) 85%, transparent) !important; + padding: 2px 8px !important; + border-top-left-radius: var(--radius-md); +} +.leaflet-control-attribution a { + color: var(--color-text-secondary) !important; +} + +@media (prefers-reduced-motion: reduce) { + .leaflet-zoom-anim .leaflet-zoom-animated { + transition: none; + } +} diff --git a/src/styles/theme.css b/src/styles/theme.css new file mode 100644 index 0000000..3f028e1 --- /dev/null +++ b/src/styles/theme.css @@ -0,0 +1,834 @@ +@theme { + /* FONT FAMILY */ + --font-body: var(--font-inter, "Inter"), -apple-system, "Segoe UI", Roboto, Arial, sans-serif; + --font-display: var(--font-inter, "Inter"), -apple-system, "Segoe UI", Roboto, Arial, sans-serif; + --font-mono: ui-monospace, "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + + /* FONT SIZE */ + --text-xs: calc(var(--spacing) * 3); + --text-xs--line-height: calc(var(--spacing) * 4.5); + + --text-sm: calc(var(--spacing) * 3.5); + --text-sm--line-height: calc(var(--spacing) * 5); + + --text-md: calc(var(--spacing) * 4); + --text-md--line-height: calc(var(--spacing) * 6); + + --text-lg: calc(var(--spacing) * 4.5); + --text-lg--line-height: calc(var(--spacing) * 7); + + --text-xl: calc(var(--spacing) * 5); + --text-xl--line-height: calc(var(--spacing) * 7.5); + + --text-display-xs: calc(var(--spacing) * 6); + --text-display-xs--line-height: calc(var(--spacing) * 8); + + --text-display-sm: calc(var(--spacing) * 7.5); + --text-display-sm--line-height: calc(var(--spacing) * 9.5); + + --text-display-md: calc(var(--spacing) * 9); + --text-display-md--line-height: calc(var(--spacing) * 11); + --text-display-md--letter-spacing: -0.72px; + + --text-display-lg: calc(var(--spacing) * 12); + --text-display-lg--line-height: calc(var(--spacing) * 15); + --text-display-lg--letter-spacing: -0.96px; + + --text-display-xl: calc(var(--spacing) * 15); + --text-display-xl--line-height: calc(var(--spacing) * 18); + --text-display-xl--letter-spacing: -1.2px; + + --text-display-2xl: calc(var(--spacing) * 18); + --text-display-2xl--line-height: calc(var(--spacing) * 22.5); + --text-display-2xl--letter-spacing: -1.44px; + + /* MAX WIDTH */ + --max-width-container: 1280px; + + /* BREAKPOINTS */ + --breakpoint-xxs: 320px; + /* This must match the breakpoint in Sonner: https://github.com/emilkowalski/sonner/blob/main/src/styles.css */ + --breakpoint-xs: 600px; + + /* SHADOW */ + --shadow-xs: 0px 1px 2px rgba(0, 0, 0, 0.05); + --shadow-sm: 0px 1px 3px rgba(0, 0, 0, 0.1), 0px 1px 2px -1px rgba(0, 0, 0, 0.1); + --shadow-md: 0px 4px 6px -1px rgba(0, 0, 0, 0.1), 0px 2px 4px -2px rgba(0, 0, 0, 0.06); + --shadow-lg: 0px 12px 16px -4px rgba(0, 0, 0, 0.08), 0px 4px 6px -2px rgba(0, 0, 0, 0.03), 0px 2px 2px -1px rgba(0, 0, 0, 0.04); + --shadow-xl: 0px 20px 24px -4px rgba(0, 0, 0, 0.08), 0px 8px 8px -4px rgba(0, 0, 0, 0.03), 0px 3px 3px -1.5px rgba(0, 0, 0, 0.04); + --shadow-2xl: 0px 24px 48px -12px rgba(0, 0, 0, 0.18), 0px 4px 4px -2px rgba(0, 0, 0, 0.04); + --shadow-3xl: 0px 32px 64px -12px rgba(0, 0, 0, 0.14), 0px 5px 5px -2.5px rgba(0, 0, 0, 0.04); + + --shadow-skeuomorphic: 0px 0px 0px 1px rgba(0, 0, 0, 0.18) inset, 0px -2px 0px 0px rgba(0, 0, 0, 0.05) inset; + --shadow-xs-skeuomorphic: var(--shadow-skeuomorphic), var(--shadow-xs); + + --shadow-modern-mockup-inner-lg: 0px 0px 3.765px 1.255px rgba(10, 13, 18, 0.08) inset, 0px 0px 2.51px 1.255px rgba(10, 13, 18, 0.03) inset; + --shadow-modern-mockup-inner-md: 0px 0px 1.692px 0.564px rgba(10, 13, 18, 0.08) inset, 0px 0px 1.128px 0.564px rgba(10, 13, 18, 0.03) inset; + --shadow-modern-mockup-inner-sm: 0px 0px 4.48px 1.493px rgba(10, 13, 18, 0.08) inset, 0px 0px 2.987px 1.493px rgba(10, 13, 18, 0.03) inset; + + --shadow-modern-mockup-outer-lg: + 0px 7.529px 10.039px -2.51px rgba(10, 13, 18, 0.08), 0px 2.51px 3.765px -1.255px rgba(10, 13, 18, 0.03), + 0px 1.255px 1.255px -0.627px rgba(10, 13, 18, 0.04); + --shadow-modern-mockup-outer-md: + 0px 3.385px 4.513px -1.128px rgba(10, 13, 18, 0.08), 0px 1.128px 1.692px -0.564px rgba(10, 13, 18, 0.03), + 0px 0.564px 0.564px -0.282px rgba(10, 13, 18, 0.04); + + --drop-shadow-iphone-mockup: 20px 12px 18px rgba(16, 24, 40, 0.2); + + /* ANIMATIONS */ + --animate-marquee: marquee 60s linear infinite; + --animate-caret-blink: caret-blink 1s infinite; + + @keyframes marquee { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(-100%); + } + } + + @keyframes caret-blink { + 0%, + 50% { + opacity: 1; + } + 51%, + 100% { + opacity: 0; + } + } + + /* BASE COLOR */ + --color-transparent: rgb(0 0 0 / 0); + + /* These will be inverted in dark mode. */ + --color-alpha-white: rgb(255 255 255); + --color-alpha-black: rgb(0 0 0); + + /* Brand: the default contour brown (#8c7556 at 600), so the UI matches the map. */ + --color-brand-50: rgb(248 245 240); + --color-brand-100: rgb(239 232 223); + --color-brand-200: rgb(226 214 198); + --color-brand-300: rgb(205 187 163); + --color-brand-400: rgb(179 154 122); + --color-brand-500: rgb(156 130 96); + --color-brand-600: rgb(140 117 86); + --color-brand-700: rgb(115 95 69); + --color-brand-800: rgb(94 76 53); + --color-brand-900: rgb(74 60 43); + --color-brand-950: rgb(43 34 24); + /* LIGHT MODE VARIABLES */ + + --color-alpha-white: rgb(255 255 255); + --color-alpha-black: rgb(0 0 0); + + /* UTILITY COLORS */ + --color-utility-blue-50: var(--color-blue-50); + --color-utility-blue-100: var(--color-blue-100); + --color-utility-blue-200: var(--color-blue-200); + --color-utility-blue-300: var(--color-blue-300); + --color-utility-blue-400: var(--color-blue-400); + --color-utility-blue-500: var(--color-blue-500); + --color-utility-blue-600: var(--color-blue-600); + --color-utility-blue-700: var(--color-blue-700); + + --color-utility-brand-50: var(--color-brand-50); + --color-utility-brand-50_alt: var(--color-brand-50); + --color-utility-brand-100: var(--color-brand-100); + --color-utility-brand-100_alt: var(--color-brand-100); + --color-utility-brand-200: var(--color-brand-200); + --color-utility-brand-200_alt: var(--color-brand-200); + --color-utility-brand-300: var(--color-brand-300); + --color-utility-brand-300_alt: var(--color-brand-300); + --color-utility-brand-400: var(--color-brand-400); + --color-utility-brand-400_alt: var(--color-brand-400); + --color-utility-brand-500: var(--color-brand-500); + --color-utility-brand-500_alt: var(--color-brand-500); + --color-utility-brand-600: var(--color-brand-600); + --color-utility-brand-600_alt: var(--color-brand-600); + --color-utility-brand-700: var(--color-brand-700); + --color-utility-brand-700_alt: var(--color-brand-700); + --color-utility-brand-800: var(--color-brand-800); + --color-utility-brand-800_alt: var(--color-brand-800); + --color-utility-brand-900: var(--color-brand-900); + --color-utility-brand-900_alt: var(--color-brand-900); + + --color-utility-neutral-50: var(--color-stone-50); + --color-utility-neutral-100: var(--color-stone-100); + --color-utility-neutral-200: var(--color-stone-200); + --color-utility-neutral-300: var(--color-stone-300); + --color-utility-neutral-400: var(--color-stone-400); + --color-utility-neutral-500: var(--color-stone-500); + --color-utility-neutral-600: var(--color-stone-600); + --color-utility-neutral-700: var(--color-stone-700); + --color-utility-neutral-800: var(--color-stone-800); + --color-utility-neutral-900: var(--color-stone-900); + + --color-utility-red-50: var(--color-red-50); + --color-utility-red-100: var(--color-red-100); + --color-utility-red-200: var(--color-red-200); + --color-utility-red-300: var(--color-red-300); + --color-utility-red-400: var(--color-red-400); + --color-utility-red-500: var(--color-red-500); + --color-utility-red-600: var(--color-red-600); + --color-utility-red-700: var(--color-red-700); + + --color-utility-yellow-50: var(--color-yellow-50); + --color-utility-yellow-100: var(--color-yellow-100); + --color-utility-yellow-200: var(--color-yellow-200); + --color-utility-yellow-300: var(--color-yellow-300); + --color-utility-yellow-400: var(--color-yellow-400); + --color-utility-yellow-500: var(--color-yellow-500); + --color-utility-yellow-600: var(--color-yellow-600); + --color-utility-yellow-700: var(--color-yellow-700); + + --color-utility-green-50: var(--color-green-50); + --color-utility-green-100: var(--color-green-100); + --color-utility-green-200: var(--color-green-200); + --color-utility-green-300: var(--color-green-300); + --color-utility-green-400: var(--color-green-400); + --color-utility-green-500: var(--color-green-500); + --color-utility-green-600: var(--color-green-600); + --color-utility-green-700: var(--color-green-700); + + --color-utility-orange-50: var(--color-orange-50); + --color-utility-orange-100: var(--color-orange-100); + --color-utility-orange-200: var(--color-orange-200); + --color-utility-orange-300: var(--color-orange-300); + --color-utility-orange-400: var(--color-orange-400); + --color-utility-orange-500: var(--color-orange-500); + --color-utility-orange-600: var(--color-orange-600); + --color-utility-orange-700: var(--color-orange-700); + + --color-utility-indigo-50: var(--color-indigo-50); + --color-utility-indigo-100: var(--color-indigo-100); + --color-utility-indigo-200: var(--color-indigo-200); + --color-utility-indigo-300: var(--color-indigo-300); + --color-utility-indigo-400: var(--color-indigo-400); + --color-utility-indigo-500: var(--color-indigo-500); + --color-utility-indigo-600: var(--color-indigo-600); + --color-utility-indigo-700: var(--color-indigo-700); + + --color-utility-fuchsia-50: var(--color-fuchsia-50); + --color-utility-fuchsia-100: var(--color-fuchsia-100); + --color-utility-fuchsia-200: var(--color-fuchsia-200); + --color-utility-fuchsia-300: var(--color-fuchsia-300); + --color-utility-fuchsia-400: var(--color-fuchsia-400); + --color-utility-fuchsia-500: var(--color-fuchsia-500); + --color-utility-fuchsia-600: var(--color-fuchsia-600); + --color-utility-fuchsia-700: var(--color-fuchsia-700); + + --color-utility-pink-50: var(--color-pink-50); + --color-utility-pink-100: var(--color-pink-100); + --color-utility-pink-200: var(--color-pink-200); + --color-utility-pink-300: var(--color-pink-300); + --color-utility-pink-400: var(--color-pink-400); + --color-utility-pink-500: var(--color-pink-500); + --color-utility-pink-600: var(--color-pink-600); + --color-utility-pink-700: var(--color-pink-700); + + --color-utility-purple-50: var(--color-purple-50); + --color-utility-purple-100: var(--color-purple-100); + --color-utility-purple-200: var(--color-purple-200); + --color-utility-purple-300: var(--color-purple-300); + --color-utility-purple-400: var(--color-purple-400); + --color-utility-purple-500: var(--color-purple-500); + --color-utility-purple-600: var(--color-purple-600); + --color-utility-purple-700: var(--color-purple-700); + + --color-utility-sky-50: var(--color-sky-50); + --color-utility-sky-100: var(--color-sky-100); + --color-utility-sky-200: var(--color-sky-200); + --color-utility-sky-300: var(--color-sky-300); + --color-utility-sky-400: var(--color-sky-400); + --color-utility-sky-500: var(--color-sky-500); + --color-utility-sky-600: var(--color-sky-600); + --color-utility-sky-700: var(--color-sky-700); + + --color-utility-slate-50: var(--color-slate-50); + --color-utility-slate-100: var(--color-slate-100); + --color-utility-slate-200: var(--color-slate-200); + --color-utility-slate-300: var(--color-slate-300); + --color-utility-slate-400: var(--color-slate-400); + --color-utility-slate-500: var(--color-slate-500); + --color-utility-slate-600: var(--color-slate-600); + --color-utility-slate-700: var(--color-slate-700); + + --color-utility-emerald-50: var(--color-emerald-50); + --color-utility-emerald-100: var(--color-emerald-100); + --color-utility-emerald-200: var(--color-emerald-200); + --color-utility-emerald-300: var(--color-emerald-300); + --color-utility-emerald-400: var(--color-emerald-400); + --color-utility-emerald-500: var(--color-emerald-500); + --color-utility-emerald-600: var(--color-emerald-600); + --color-utility-emerald-700: var(--color-emerald-700); + + --color-utility-amber-50: var(--color-amber-50); + --color-utility-amber-100: var(--color-amber-100); + --color-utility-amber-200: var(--color-amber-200); + --color-utility-amber-300: var(--color-amber-300); + --color-utility-amber-400: var(--color-amber-400); + --color-utility-amber-500: var(--color-amber-500); + --color-utility-amber-600: var(--color-amber-600); + --color-utility-amber-700: var(--color-amber-700); + + /* TEXT COLORS */ + --color-text-primary: var(--color-stone-900); + --color-text-tertiary: var(--color-stone-600); + --color-text-error-primary: var(--color-red-600); + --color-text-warning-primary: var(--color-yellow-600); + --color-text-success-primary: var(--color-green-600); + --color-text-secondary: var(--color-stone-700); + --color-text-secondary_hover: var(--color-stone-800); + --color-text-tertiary_hover: var(--color-stone-700); + --color-text-brand-secondary: var(--color-brand-700); + --color-text-placeholder: var(--color-stone-500); + --color-text-brand-tertiary: var(--color-brand-600); + --color-text-editor-icon-fg: var(--color-stone-400); + --color-text-editor-icon-fg_active: var(--color-stone-500); + --color-text-quaternary: var(--color-stone-500); + --color-text-brand-primary: var(--color-brand-900); + --color-text-primary_on-brand: var(--color-white); + --color-text-secondary_on-brand: var(--color-brand-200); + --color-text-tertiary_on-brand: var(--color-brand-200); + --color-text-quaternary_on-brand: var(--color-brand-300); + --color-text-brand-tertiary_alt: var(--color-brand-600); + --color-text-error-primary_hover: var(--color-red-700); + --color-text-brand-secondary_hover: var(--color-brand-800); + + /* BORDER COLORS */ + --color-border-primary: var(--color-stone-300); + --color-border-secondary: var(--color-stone-200); + --color-border-secondary_alt: rgb(0 0 0 / 0.1); + --color-border-tertiary: var(--color-stone-100); + --color-border-error: var(--color-red-500); + --color-border-error_subtle: var(--color-red-300); + --color-border-brand: var(--color-brand-500); + --color-border-brand_alt: var(--color-brand-600); + + /* FOREGROUND COLORS */ + --color-fg-secondary: var(--color-stone-700); + --color-fg-warning-primary: var(--color-yellow-600); + --color-fg-success-primary: var(--color-green-600); + --color-fg-white: var(--color-white); + --color-fg-success-secondary: var(--color-green-500); + --color-fg-secondary_hover: var(--color-stone-800); + --color-fg-primary: var(--color-stone-900); + --color-fg-brand-secondary: var(--color-brand-500); + --color-fg-brand-primary: var(--color-brand-600); + --color-fg-quaternary: var(--color-stone-400); + --color-fg-quaternary_hover: var(--color-stone-500); + --color-fg-error-primary: var(--color-red-600); + --color-fg-warning-secondary: var(--color-yellow-500); + --color-fg-error-secondary: var(--color-red-500); + --color-fg-tertiary: var(--color-stone-600); + --color-fg-tertiary_hover: var(--color-stone-700); + --color-fg-brand-primary_alt: var(--color-fg-brand-primary); + --color-fg-brand-secondary_alt: var(--color-fg-brand-secondary); + --color-fg-brand-secondary_hover: var(--color-brand-600); + + /* BACKGROUND COLORS */ + --color-bg-primary: var(--color-white); + --color-bg-tertiary: var(--color-stone-100); + --color-bg-brand-primary: var(--color-brand-50); + --color-bg-error-secondary: var(--color-red-100); + --color-bg-warning-primary: var(--color-yellow-50); + --color-bg-warning-secondary: var(--color-yellow-100); + --color-bg-success-primary: var(--color-green-50); + --color-bg-success-secondary: var(--color-green-100); + --color-bg-brand-solid: var(--color-brand-600); + --color-bg-secondary-solid: var(--color-stone-600); + --color-bg-error-solid: var(--color-red-600); + --color-bg-warning-solid: var(--color-yellow-600); + --color-bg-success-solid: var(--color-green-600); + --color-bg-secondary_hover: var(--color-stone-100); + --color-bg-primary_hover: var(--color-stone-50); + --color-bg-brand-solid_hover: var(--color-brand-700); + --color-bg-error-primary: var(--color-red-50); + --color-bg-brand-secondary: var(--color-brand-100); + --color-bg-secondary: var(--color-stone-50); + --color-bg-quaternary: var(--color-stone-200); + --color-bg-primary_alt: var(--color-white); + --color-bg-brand-primary_alt: var(--color-brand-50); + --color-bg-secondary_alt: var(--color-stone-50); + --color-bg-overlay: var(--color-stone-950); + --color-bg-brand-section: var(--color-brand-800); + --color-bg-brand-section_subtle: var(--color-brand-700); + --color-bg-primary-solid: var(--color-stone-950); + --color-bg-error-solid_hover: var(--color-red-700); + + /* COMPONENT COLORS */ + --color-app-store-badge-border: 166 166 166 1; + --color-avatar-styles-bg-neutral: 224 224 224 1; + --color-featured-icon-light-fg-brand: var(--color-brand-600); + --color-featured-icon-light-fg-error: var(--color-red-600); + --color-featured-icon-light-fg-gray: var(--color-stone-500); + --color-featured-icon-light-fg-success: var(--color-green-600); + --color-featured-icon-light-fg-warning: var(--color-yellow-600); + --color-focus-ring-error: var(--color-red-500); + --color-focus-ring: var(--color-brand-500); + --color-footer-button-fg: var(--color-brand-200); + --color-footer-button-fg_hover: var(--color-white); + --color-icon-fg-brand: var(--color-brand-600); + --color-icon-fg-brand_on-brand: var(--color-brand-200); + --color-screen-mockup-border: var(--color-stone-900); + + --color-slider-handle-bg: var(--color-white); + --color-slider-handle-border: var(--color-brand-600); + --color-toggle-border: var(--color-stone-300); + --color-toggle-slim-border_pressed-hover: var(--color-bg-brand-solid_hover); + --color-toggle-slim-border_pressed: var(--color-bg-brand-solid); + --color-tooltip-supporting-text: var(--color-stone-300); + + /* BACKGROUND PROPERTY COLORS */ + --background-color-primary: var(--color-bg-primary); + --background-color-tertiary: var(--color-bg-tertiary); + --background-color-brand-primary: var(--color-bg-brand-primary); + --background-color-error-secondary: var(--color-bg-error-secondary); + --background-color-warning-primary: var(--color-bg-warning-primary); + --background-color-warning-secondary: var(--color-bg-warning-secondary); + --background-color-success-primary: var(--color-bg-success-primary); + --background-color-success-secondary: var(--color-bg-success-secondary); + --background-color-brand-solid: var(--color-bg-brand-solid); + --background-color-secondary-solid: var(--color-bg-secondary-solid); + --background-color-error-solid: var(--color-bg-error-solid); + --background-color-warning-solid: var(--color-bg-warning-solid); + --background-color-success-solid: var(--color-bg-success-solid); + --background-color-secondary_hover: var(--color-bg-secondary_hover); + --background-color-primary_hover: var(--color-bg-primary_hover); + --background-color-brand-solid_hover: var(--color-bg-brand-solid_hover); + --background-color-error-primary: var(--color-bg-error-primary); + --background-color-brand-secondary: var(--color-bg-brand-secondary); + --background-color-secondary: var(--color-bg-secondary); + --background-color-quaternary: var(--color-bg-quaternary); + --background-color-primary_alt: var(--color-bg-primary_alt); + --background-color-brand-primary_alt: var(--color-bg-brand-primary_alt); + --background-color-secondary_alt: var(--color-bg-secondary_alt); + --background-color-overlay: var(--color-bg-overlay); + --background-color-brand-section: var(--color-bg-brand-section); + --background-color-brand-section_subtle: var(--color-bg-brand-section_subtle); + --background-color-primary-solid: var(--color-bg-primary-solid); + --background-color-error-solid_hover: var(--color-bg-error-solid_hover); + --background-color-border-brand: var(--color-border-brand); + --background-color-border-brand_alt: var(--color-border-brand_alt); + + /* TEXT PROPERTY COLORS */ + --color-text-white: var(--color-white); + --text-color-primary: var(--color-text-primary); + --text-color-secondary: var(--color-text-secondary); + --text-color-secondary_hover: var(--color-text-secondary_hover); + --text-color-tertiary: var(--color-text-tertiary); + --text-color-tertiary_hover: var(--color-text-tertiary_hover); + --text-color-error-primary: var(--color-text-error-primary); + --text-color-warning-primary: var(--color-text-warning-primary); + --text-color-success-primary: var(--color-text-success-primary); + --text-color-brand-secondary: var(--color-text-brand-secondary); + --text-color-placeholder: var(--color-text-placeholder); + --text-color-brand-tertiary: var(--color-text-brand-tertiary); + --text-color-editor-icon-fg: var(--color-text-editor-icon-fg); + --text-color-editor-icon-fg_active: var(--color-text-editor-icon-fg_active); + --text-color-quaternary: var(--color-text-quaternary); + --text-color-brand-primary: var(--color-text-brand-primary); + --text-color-primary_on-brand: var(--color-text-primary_on-brand); + --text-color-secondary_on-brand: var(--color-text-secondary_on-brand); + --text-color-tertiary_on-brand: var(--color-text-tertiary_on-brand); + --text-color-quaternary_on-brand: var(--color-text-quaternary_on-brand); + --text-color-brand-tertiary_alt: var(--color-text-brand-tertiary_alt); + --text-color-error-primary_hover: var(--color-text-error-primary_hover); + --text-color-brand-secondary_hover: var(--color-text-brand-secondary_hover); + --text-color-tooltip-supporting-text: var(--color-tooltip-supporting-text); + + /* BORDER PROPERTY COLORS */ + --border-color-primary: var(--color-border-primary); + --border-color-secondary: var(--color-border-secondary); + --border-color-secondary_alt: var(--color-border-secondary_alt); + --border-color-tertiary: var(--color-border-tertiary); + --border-color-error: var(--color-border-error); + --border-color-error_subtle: var(--color-border-error_subtle); + --border-color-brand: var(--color-border-brand); + --border-color-brand_alt: var(--color-border-brand_alt); + --border-color-brand-solid: var(--color-bg-brand-solid); + --border-color-brand-solid_hover: var(--color-bg-brand-solid_hover); + + /* RING PROPERTY COLORS */ + --ring-color-primary: var(--color-border-primary); + --ring-color-secondary: var(--color-border-secondary); + --ring-color-secondary_alt: var(--color-border-secondary_alt); + --ring-color-tertiary: var(--color-border-tertiary); + --ring-color-error: var(--color-border-error); + --ring-color-error_subtle: var(--color-border-error_subtle); + --ring-color-brand: var(--color-border-brand); + --ring-color-brand_alt: var(--color-border-brand_alt); + --ring-color-brand-solid: var(--color-bg-brand-solid); + --ring-color-brand-solid_hover: var(--color-bg-brand-solid_hover); + + /* OUTLINE PROPERTY COLORS */ + --outline-color-primary: var(--color-border-primary); + --outline-color-secondary: var(--color-border-secondary); + --outline-color-secondary_alt: var(--color-border-secondary_alt); + --outline-color-tertiary: var(--color-border-tertiary); + --outline-color-error: var(--color-border-error); + --outline-color-error_subtle: var(--color-border-error_subtle); + --outline-color-brand: var(--color-border-brand); + --outline-color-brand_alt: var(--color-border-brand_alt); + --outline-color-brand-solid: var(--color-bg-brand-solid); + --outline-color-brand-solid_hover: var(--color-bg-brand-solid_hover); +} + +@layer base { + /* DARK MODE VARIABLES */ + + .dark-mode { + --color-alpha-white: rgb(12 14 18); + --color-alpha-black: rgb(255 255 255); + + /* UTILITY COLORS */ + + --color-utility-blue-50: var(--color-blue-950); + --color-utility-blue-100: var(--color-blue-900); + --color-utility-blue-200: var(--color-blue-800); + --color-utility-blue-300: var(--color-blue-700); + --color-utility-blue-400: var(--color-blue-600); + --color-utility-blue-500: var(--color-blue-500); + --color-utility-blue-600: var(--color-blue-400); + --color-utility-blue-700: var(--color-blue-300); + + --color-utility-brand-50: var(--color-brand-950); + --color-utility-brand-50_alt: var(--color-utility-neutral-50); + --color-utility-brand-100: var(--color-brand-900); + --color-utility-brand-100_alt: var(--color-utility-neutral-100); + --color-utility-brand-200: var(--color-brand-800); + --color-utility-brand-200_alt: var(--color-utility-neutral-200); + --color-utility-brand-300: var(--color-brand-700); + --color-utility-brand-300_alt: var(--color-utility-neutral-300); + --color-utility-brand-400: var(--color-brand-600); + --color-utility-brand-400_alt: var(--color-utility-neutral-400); + --color-utility-brand-500: var(--color-brand-500); + --color-utility-brand-500_alt: var(--color-utility-neutral-500); + --color-utility-brand-600: var(--color-brand-400); + --color-utility-brand-600_alt: var(--color-utility-neutral-600); + --color-utility-brand-700: var(--color-brand-300); + --color-utility-brand-700_alt: var(--color-utility-neutral-700); + --color-utility-brand-800: var(--color-brand-200); + --color-utility-brand-800_alt: var(--color-utility-neutral-800); + --color-utility-brand-900: var(--color-brand-100); + --color-utility-brand-900_alt: var(--color-utility-neutral-900); + + --color-utility-neutral-50: var(--color-stone-900); + --color-utility-neutral-100: var(--color-stone-800); + --color-utility-neutral-200: var(--color-stone-700); + --color-utility-neutral-300: var(--color-stone-700); + --color-utility-neutral-400: var(--color-stone-600); + --color-utility-neutral-500: var(--color-stone-500); + --color-utility-neutral-600: var(--color-stone-400); + --color-utility-neutral-700: var(--color-stone-300); + --color-utility-neutral-800: var(--color-stone-200); + --color-utility-neutral-900: var(--color-stone-100); + + --color-utility-red-50: var(--color-red-950); + --color-utility-red-100: var(--color-red-900); + --color-utility-red-200: var(--color-red-800); + --color-utility-red-300: var(--color-red-700); + --color-utility-red-400: var(--color-red-600); + --color-utility-red-500: var(--color-red-500); + --color-utility-red-600: var(--color-red-400); + --color-utility-red-700: var(--color-red-300); + + --color-utility-yellow-50: var(--color-yellow-950); + --color-utility-yellow-100: var(--color-yellow-900); + --color-utility-yellow-200: var(--color-yellow-800); + --color-utility-yellow-300: var(--color-yellow-700); + --color-utility-yellow-400: var(--color-yellow-600); + --color-utility-yellow-500: var(--color-yellow-500); + --color-utility-yellow-600: var(--color-yellow-400); + --color-utility-yellow-700: var(--color-yellow-300); + + --color-utility-green-50: var(--color-green-950); + --color-utility-green-100: var(--color-green-900); + --color-utility-green-200: var(--color-green-800); + --color-utility-green-300: var(--color-green-700); + --color-utility-green-400: var(--color-green-600); + --color-utility-green-500: var(--color-green-500); + --color-utility-green-600: var(--color-green-400); + --color-utility-green-700: var(--color-green-300); + + --color-utility-orange-50: var(--color-orange-950); + --color-utility-orange-100: var(--color-orange-900); + --color-utility-orange-200: var(--color-orange-800); + --color-utility-orange-300: var(--color-orange-700); + --color-utility-orange-400: var(--color-orange-600); + --color-utility-orange-500: var(--color-orange-500); + --color-utility-orange-600: var(--color-orange-400); + --color-utility-orange-700: var(--color-orange-300); + + --color-utility-indigo-50: var(--color-indigo-950); + --color-utility-indigo-100: var(--color-indigo-900); + --color-utility-indigo-200: var(--color-indigo-800); + --color-utility-indigo-300: var(--color-indigo-700); + --color-utility-indigo-400: var(--color-indigo-600); + --color-utility-indigo-500: var(--color-indigo-500); + --color-utility-indigo-600: var(--color-indigo-400); + --color-utility-indigo-700: var(--color-indigo-300); + + --color-utility-fuchsia-50: var(--color-fuchsia-950); + --color-utility-fuchsia-100: var(--color-fuchsia-900); + --color-utility-fuchsia-200: var(--color-fuchsia-800); + --color-utility-fuchsia-300: var(--color-fuchsia-700); + --color-utility-fuchsia-400: var(--color-fuchsia-600); + --color-utility-fuchsia-500: var(--color-fuchsia-500); + --color-utility-fuchsia-600: var(--color-fuchsia-400); + --color-utility-fuchsia-700: var(--color-fuchsia-300); + + --color-utility-pink-50: var(--color-pink-950); + --color-utility-pink-100: var(--color-pink-900); + --color-utility-pink-200: var(--color-pink-800); + --color-utility-pink-300: var(--color-pink-700); + --color-utility-pink-400: var(--color-pink-600); + --color-utility-pink-500: var(--color-pink-500); + --color-utility-pink-600: var(--color-pink-400); + --color-utility-pink-700: var(--color-pink-300); + + --color-utility-purple-50: var(--color-purple-950); + --color-utility-purple-100: var(--color-purple-900); + --color-utility-purple-200: var(--color-purple-800); + --color-utility-purple-300: var(--color-purple-700); + --color-utility-purple-400: var(--color-purple-600); + --color-utility-purple-500: var(--color-purple-500); + --color-utility-purple-600: var(--color-purple-400); + --color-utility-purple-700: var(--color-purple-300); + + --color-utility-sky-50: var(--color-sky-950); + --color-utility-sky-100: var(--color-sky-900); + --color-utility-sky-200: var(--color-sky-800); + --color-utility-sky-300: var(--color-sky-700); + --color-utility-sky-400: var(--color-sky-600); + --color-utility-sky-500: var(--color-sky-500); + --color-utility-sky-600: var(--color-sky-400); + --color-utility-sky-700: var(--color-sky-300); + + --color-utility-slate-50: var(--color-slate-950); + --color-utility-slate-100: var(--color-slate-900); + --color-utility-slate-200: var(--color-slate-800); + --color-utility-slate-300: var(--color-slate-700); + --color-utility-slate-400: var(--color-slate-600); + --color-utility-slate-500: var(--color-slate-500); + --color-utility-slate-600: var(--color-slate-400); + --color-utility-slate-700: var(--color-slate-300); + + --color-utility-emerald-50: var(--color-emerald-950); + --color-utility-emerald-100: var(--color-emerald-900); + --color-utility-emerald-200: var(--color-emerald-800); + --color-utility-emerald-300: var(--color-emerald-700); + --color-utility-emerald-400: var(--color-emerald-600); + --color-utility-emerald-500: var(--color-emerald-500); + --color-utility-emerald-600: var(--color-emerald-400); + --color-utility-emerald-700: var(--color-emerald-300); + + --color-utility-amber-50: var(--color-amber-950); + --color-utility-amber-100: var(--color-amber-900); + --color-utility-amber-200: var(--color-amber-800); + --color-utility-amber-300: var(--color-amber-700); + --color-utility-amber-400: var(--color-amber-600); + --color-utility-amber-500: var(--color-amber-500); + --color-utility-amber-600: var(--color-amber-400); + --color-utility-amber-700: var(--color-amber-300); + + --color-text-primary: var(--color-stone-50); + --color-text-tertiary: var(--color-stone-400); + --color-text-error-primary: var(--color-red-400); + --color-text-warning-primary: var(--color-yellow-400); + --color-text-success-primary: var(--color-green-400); + --color-text-secondary: var(--color-stone-300); + --color-text-secondary_hover: var(--color-stone-200); + --color-text-tertiary_hover: var(--color-stone-300); + --color-text-brand-secondary: var(--color-stone-300); + --color-text-placeholder: var(--color-stone-500); + --color-text-brand-tertiary: var(--color-stone-400); + --color-text-editor-icon-fg: var(--color-stone-400); + --color-text-editor-icon-fg_active: var(--color-white); + --color-text-quaternary: var(--color-stone-400); + --color-text-brand-primary: var(--color-stone-50); + --color-text-primary_on-brand: var(--color-stone-50); + --color-text-secondary_on-brand: var(--color-stone-300); + --color-text-tertiary_on-brand: var(--color-stone-400); + --color-text-quaternary_on-brand: var(--color-stone-400); + --color-text-brand-tertiary_alt: var(--color-stone-50); + --color-text-error-primary_hover: var(--color-red-300); + --color-text-brand-secondary_hover: var(--color-stone-200); + + --color-border-secondary: var(--color-stone-800); + --color-border-error_subtle: var(--color-red-500); + --color-border-primary: var(--color-stone-700); + --color-border-brand: var(--color-brand-400); + --color-border-error: var(--color-red-400); + --color-border-tertiary: var(--color-stone-800); + --color-border-brand_alt: var(--color-stone-700); + --color-border-secondary_alt: var(--color-stone-800); + + --color-fg-secondary: var(--color-stone-300); + --color-fg-warning-primary: var(--color-yellow-500); + --color-fg-success-primary: var(--color-green-500); + --color-fg-white: var(--color-white); + --color-fg-success-secondary: var(--color-green-400); + --color-fg-secondary_hover: var(--color-stone-200); + --color-fg-primary: var(--color-white); + --color-fg-brand-secondary: var(--color-brand-500); + --color-fg-brand-primary: var(--color-brand-500); + --color-fg-quaternary: var(--color-stone-600); + --color-fg-quaternary_hover: var(--color-stone-500); + --color-fg-error-primary: var(--color-red-500); + --color-fg-warning-secondary: var(--color-yellow-400); + --color-fg-error-secondary: var(--color-red-400); + --color-fg-tertiary: var(--color-stone-400); + --color-fg-tertiary_hover: var(--color-stone-300); + --color-fg-brand-primary_alt: var(--color-stone-300); + --color-fg-brand-secondary_alt: var(--color-stone-600); + --color-fg-brand-secondary_hover: var(--color-stone-500); + + --color-bg-primary: var(--color-stone-950); + --color-bg-tertiary: var(--color-stone-800); + --color-bg-brand-primary: var(--color-brand-500); + --color-bg-error-secondary: var(--color-red-600); + --color-bg-warning-primary: var(--color-yellow-950); + --color-bg-warning-secondary: var(--color-yellow-600); + --color-bg-success-primary: var(--color-green-950); + --color-bg-success-secondary: var(--color-green-600); + --color-bg-brand-solid: var(--color-brand-600); + --color-bg-secondary-solid: var(--color-stone-600); + --color-bg-error-solid: var(--color-red-600); + --color-bg-warning-solid: var(--color-yellow-600); + --color-bg-success-solid: var(--color-green-600); + --color-bg-secondary_hover: var(--color-stone-800); + --color-bg-primary_hover: var(--color-stone-900); + --color-bg-brand-solid_hover: var(--color-brand-500); + --color-bg-error-primary: var(--color-red-950); + --color-bg-brand-secondary: var(--color-brand-600); + --color-bg-secondary: var(--color-stone-900); + --color-bg-quaternary: var(--color-stone-700); + --color-bg-primary_alt: var(--color-bg-secondary); + --color-bg-brand-primary_alt: var(--color-bg-secondary); + --color-bg-secondary_alt: var(--color-bg-primary); + --color-bg-overlay: var(--color-stone-800); + --color-bg-brand-section: var(--color-bg-secondary); + --color-bg-brand-section_subtle: var(--color-bg-primary); + --color-bg-primary-solid: var(--color-bg-secondary); + --color-bg-error-solid_hover: var(--color-red-500); + + --color-app-store-badge-border: var(--color-white); + --color-avatar-styles-bg-neutral: 224 224 224 1; + --color-featured-icon-light-fg-brand: var(--color-brand-200); + --color-featured-icon-light-fg-error: var(--color-red-200); + --color-featured-icon-light-fg-gray: var(--color-stone-200); + --color-featured-icon-light-fg-success: var(--color-green-200); + --color-featured-icon-light-fg-warning: var(--color-yellow-200); + --color-focus-ring-error: var(--color-red-500); + --color-focus-ring: var(--color-brand-500); + --color-footer-button-fg: var(--color-stone-300); + --color-footer-button-fg_hover: var(--color-stone-100); + --color-icon-fg-brand: var(--color-stone-400); + --color-icon-fg-brand_on-brand: var(--color-stone-400); + --color-screen-mockup-border: var(--color-stone-700); + --color-slider-handle-bg: var(--color-fg-brand-primary); + --color-slider-handle-border: var(--color-bg-primary); + --color-toggle-border: var(--color-transparent); + --color-toggle-slim-border_pressed-hover: var(--color-transparent); + --color-toggle-slim-border_pressed: var(--color-transparent); + --color-tooltip-supporting-text: var(--color-stone-300); + + /* BACKGROUND PROPERTY COLORS */ + --background-color-primary: var(--color-bg-primary); + --background-color-tertiary: var(--color-bg-tertiary); + --background-color-brand-primary: var(--color-bg-brand-primary); + --background-color-error-secondary: var(--color-bg-error-secondary); + --background-color-warning-primary: var(--color-bg-warning-primary); + --background-color-warning-secondary: var(--color-bg-warning-secondary); + --background-color-success-primary: var(--color-bg-success-primary); + --background-color-success-secondary: var(--color-bg-success-secondary); + --background-color-brand-solid: var(--color-bg-brand-solid); + --background-color-secondary-solid: var(--color-bg-secondary-solid); + --background-color-error-solid: var(--color-bg-error-solid); + --background-color-warning-solid: var(--color-bg-warning-solid); + --background-color-success-solid: var(--color-bg-success-solid); + --background-color-secondary_hover: var(--color-bg-secondary_hover); + --background-color-primary_hover: var(--color-bg-primary_hover); + --background-color-brand-solid_hover: var(--color-bg-brand-solid_hover); + --background-color-error-primary: var(--color-bg-error-primary); + --background-color-brand-secondary: var(--color-bg-brand-secondary); + --background-color-secondary: var(--color-bg-secondary); + --background-color-quaternary: var(--color-bg-quaternary); + --background-color-primary_alt: var(--color-bg-primary_alt); + --background-color-brand-primary_alt: var(--color-bg-brand-primary_alt); + --background-color-secondary_alt: var(--color-bg-secondary_alt); + --background-color-overlay: var(--color-bg-overlay); + --background-color-brand-section: var(--color-bg-brand-section); + --background-color-brand-section_subtle: var(--color-bg-brand-section_subtle); + --background-color-primary-solid: var(--color-bg-primary-solid); + --background-color-error-solid_hover: var(--color-bg-error-solid_hover); + + --background-color-border-brand: var(--color-border-brand); + --background-color-border-tertiary: var(--color-border-tertiary); + --background-color-border-brand_alt: var(--color-border-brand_alt); + + /* TEXT PROPERTY COLORS */ + --text-color-primary: var(--color-text-primary); + --text-color-tertiary: var(--color-text-tertiary); + --text-color-error-primary: var(--color-text-error-primary); + --text-color-warning-primary: var(--color-text-warning-primary); + --text-color-success-primary: var(--color-text-success-primary); + --text-color-secondary: var(--color-text-secondary); + --text-color-secondary_hover: var(--color-text-secondary_hover); + --text-color-tertiary_hover: var(--color-text-tertiary_hover); + --text-color-brand-secondary: var(--color-text-brand-secondary); + --text-color-placeholder: var(--color-text-placeholder); + --text-color-brand-tertiary: var(--color-text-brand-tertiary); + --text-color-editor-icon-fg: var(--color-text-editor-icon-fg); + --text-color-editor-icon-fg_active: var(--color-text-editor-icon-fg_active); + --text-color-quaternary: var(--color-text-quaternary); + --text-color-brand-primary: var(--color-text-brand-primary); + --text-color-primary_on-brand: var(--color-text-primary_on-brand); + --text-color-secondary_on-brand: var(--color-text-secondary_on-brand); + --text-color-tertiary_on-brand: var(--color-text-tertiary_on-brand); + --text-color-quaternary_on-brand: var(--color-text-quaternary_on-brand); + --text-color-brand-tertiary_alt: var(--color-text-brand-tertiary_alt); + --text-color-error-primary_hover: var(--color-text-error-primary_hover); + --text-color-brand-secondary_hover: var(--color-text-brand-secondary_hover); + --text-color-tooltip-supporting-text: var(--color-tooltip-supporting-text); + + /* BORDER PROPERTY COLORS */ + --border-color-primary: var(--color-border-primary); + --border-color-secondary: var(--color-border-secondary); + --border-color-secondary_alt: var(--color-border-secondary_alt); + --border-color-tertiary: var(--color-border-tertiary); + --border-color-error: var(--color-border-error); + --border-color-error_subtle: var(--color-border-error_subtle); + --border-color-brand: var(--color-border-brand); + --border-color-brand_alt: var(--color-border-brand_alt); + --border-color-brand-solid: var(--color-bg-brand-solid); + --border-color-brand-solid_hover: var(--color-bg-brand-solid_hover); + + /* RING PROPERTY COLORS */ + --ring-color-primary: var(--color-border-primary); + --ring-color-secondary: var(--color-border-secondary); + --ring-color-secondary_alt: var(--color-border-secondary_alt); + --ring-color-tertiary: var(--color-border-tertiary); + --ring-color-error: var(--color-border-error); + --ring-color-error_subtle: var(--color-border-error_subtle); + --ring-color-brand: var(--color-border-brand); + --ring-color-brand-solid: var(--color-bg-brand-solid); + --ring-color-brand-solid_hover: var(--color-bg-brand-solid_hover); + --ring-color-brand_alt: var(--color-border-brand_alt); + + /* OUTLINE PROPERTY COLORS */ + --outline-color-primary: var(--color-border-primary); + --outline-color-secondary: var(--color-border-secondary); + --outline-color-secondary_alt: var(--color-border-secondary_alt); + --outline-color-tertiary: var(--color-border-tertiary); + --outline-color-error: var(--color-border-error); + --outline-color-error_subtle: var(--color-border-error_subtle); + --outline-color-brand: var(--color-border-brand); + --outline-color-brand-solid: var(--color-bg-brand-solid); + --outline-color-brand-solid_hover: var(--color-bg-brand-solid_hover); + --outline-color-brand_alt: var(--color-border-brand_alt); + } +} diff --git a/src/utils/cx.ts b/src/utils/cx.ts new file mode 100644 index 0000000..9c039b5 --- /dev/null +++ b/src/utils/cx.ts @@ -0,0 +1,24 @@ +import { extendTailwindMerge } from "tailwind-merge"; + +const twMerge = extendTailwindMerge({ + extend: { + theme: { + text: ["display-xs", "display-sm", "display-md", "display-lg", "display-xl", "display-2xl"], + }, + }, +}); + +/** + * This function is a wrapper around the twMerge function. + * It is used to merge the classes inside style objects. + */ +export const cx = twMerge; + +/** + * This function does nothing besides helping us to be able to + * sort the classes inside style objects which is not supported + * by the Tailwind IntelliSense by default. + */ +export function sortCx>>>(classes: T): T { + return classes; +} diff --git a/src/utils/is-react-component.ts b/src/utils/is-react-component.ts new file mode 100644 index 0000000..dcb37d0 --- /dev/null +++ b/src/utils/is-react-component.ts @@ -0,0 +1,36 @@ +import type React from "react"; + +// Below the import on purpose: the shadcn CLI drops comments that come before a file's first +// statement, which would drop this directive and fail lint in projects installed that way. +// `any` is required here: with `unknown`, the type guards below stop narrowing to component types. +/* eslint-disable @typescript-eslint/no-explicit-any */ + +type ReactComponent = React.FC | React.ComponentClass; + +/** + * Checks if a given value is a function component. + */ +export const isFunctionComponent = (component: any): component is React.FC => { + return typeof component === "function"; +}; + +/** + * Checks if a given value is a class component. + */ +export const isClassComponent = (component: any): component is React.ComponentClass => { + return typeof component === "function" && component.prototype && (!!component.prototype.isReactComponent || !!component.prototype.render); +}; + +/** + * Checks if a given value is a forward ref component. + */ +export const isForwardRefComponent = (component: any): component is React.ForwardRefExoticComponent => { + return typeof component === "object" && component !== null && component.$$typeof.toString() === "Symbol(react.forward_ref)"; +}; + +/** + * Checks if a given value is a valid React component. + */ +export const isReactComponent = (component: any): component is ReactComponent => { + return isFunctionComponent(component) || isForwardRefComponent(component) || isClassComponent(component); +}; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..14859a7 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,42 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ] + } + }, + "include": [ + "**/*.ts", + "**/*.tsx", + "next-env.d.ts", + "next.config.mjs", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} From 40defcdaba9c06df35f373ab3630adbf45f45971 Mon Sep 17 00:00:00 2001 From: David Heyman Date: Wed, 4 Nov 2026 23:14:37 +0000 Subject: [PATCH 3/7] Build as a static export and add a CI build workflow Set output: "export" (with images.unoptimized) in next.config.mjs so `next build` writes a self-contained out/ folder that the existing static host for contours.axismaps.com can serve; the same config still runs unchanged on Vercel. Verified the served export end to end with a plain file server: #zoom/lat/lng hash routing, icon.svg, the dynamically imported Leaflet chunk, contour drawing and the Mapbox place search. Point `npm start` at `npx serve out`, since `next start` refuses a static export. Add .github/workflows/build.yml, which runs npm ci and npm run build on pushes and pull requests with NEXT_PUBLIC_MAPBOX_TOKEN from a repository secret and uploads out/ as an artifact. No deploy step: the hosting target isn't decided. Document the export, the host requirements (serve from the domain root, basePath for sub-paths, caching, 404 page) and Vercel in the README. Co-Authored-By: Claude Fable 5.1 --- .github/workflows/build.yml | 32 ++++++++++++++++++++++++++++++++ README.md | 31 ++++++++++++++++++++++++++++++- next.config.mjs | 9 +++++++++ package.json | 2 +- 4 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..aeba228 --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,32 @@ +name: Build + +on: + push: + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - run: npm ci + + - run: npm run build + env: + # Public Mapbox token for the place search. Without it the build still + # passes; the exported site just has search disabled. + NEXT_PUBLIC_MAPBOX_TOKEN: ${{ secrets.NEXT_PUBLIC_MAPBOX_TOKEN }} + + # The static export, ready to copy to any file host. No deploy step yet: + # the hosting target isn't decided. + - uses: actions/upload-artifact@v4 + with: + name: site + path: out/ + if-no-files-found: error diff --git a/README.md b/README.md index 76f9784..38734c5 100644 --- a/README.md +++ b/README.md @@ -37,10 +37,39 @@ Then open http://localhost:3000/. Without `NEXT_PUBLIC_MAPBOX_TOKEN` the app sti ```bash npm run build +``` + +The app is configured as a static export (`output: "export"` in `next.config.mjs`), so `next build` writes the whole site to `out/` as plain HTML, JS and CSS. There is no server to run: preview the build with any file server, for example + +```bash npm start ``` -`npm run typecheck` runs the TypeScript compiler on its own. +which runs `npx serve out`, or `python3 -m http.server 8787 --directory out`. `npm run typecheck` runs the TypeScript compiler on its own. + +## Deploying + +### Static hosting (contours.axismaps.com) + +The site is entirely client-side (Leaflet, d3 and the Mapbox geocoder all run in the browser), so the exported `out/` folder is the deployable: copy its contents to the web root of any static host, exactly as the old `index.html` site was deployed. + +1. Make sure `NEXT_PUBLIC_MAPBOX_TOKEN` is set when you build (in `.env.local` locally, or as an environment variable in CI). It is baked into the JavaScript at build time; without it the site works but search is disabled. +2. Run `npm run build`. +3. Upload the contents of `out/` to the host's document root. + +Things the host needs to get right: + +- **Serve from the domain root.** Asset URLs are root-absolute (`/_next/static/...`, `/icon.svg`). To host under a sub-path such as `example.com/contours/`, set `basePath: "/contours"` in `next.config.mjs` and rebuild; there is no `assetPrefix` or CDN setting otherwise. +- **`/` must serve `out/index.html`.** Every file server does this by default. `trailingSlash` is left at its default; the app has a single route, so it makes no difference here. +- **The map view lives in the URL hash** (`#zoom/lat/lng`), which never reaches the server, so no rewrite rules are needed for deep links. +- **Caching.** Everything under `out/_next/static/` has a content hash in its name and can be cached forever. `index.html` and `icon.svg` should be served with a short or no cache so a new deploy is picked up. +- **404s.** `out/404.html` is a styled not-found page; point the host's error document at it if it supports one. Nothing in the app depends on it. + +The GitHub Actions workflow in `.github/workflows/build.yml` runs `npm ci` and `npm run build` on every push and pull request, reading the Mapbox token from the `NEXT_PUBLIC_MAPBOX_TOKEN` repository secret, and uploads `out/` as a workflow artifact named `site`. It does not deploy anywhere; download the artifact or add a deploy step for whichever host is chosen. + +### Vercel + +The same code runs unchanged on Vercel: import the repository, set `NEXT_PUBLIC_MAPBOX_TOKEN` in the project's environment variables, and Vercel detects Next.js and serves the static export. ## Project layout diff --git a/next.config.mjs b/next.config.mjs index 7bcbd41..9e92497 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,5 +1,14 @@ /** @type {import('next').NextConfig} */ const nextConfig = { + // Static export: `next build` writes a self-contained `out/` folder that any + // file server (including the existing contours.axismaps.com host) can serve. + // The same config also runs unchanged on Vercel, which serves the export. + output: "export", + // The only is the external Axis Maps logo; the image optimizer needs a + // server, so it is turned off for the export. + images: { + unoptimized: true, + }, experimental: { optimizePackageImports: ["@untitledui/icons"], }, diff --git a/package.json b/package.json index e3bc5b2..755688e 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "scripts": { "dev": "next dev --turbopack", "build": "next build", - "start": "next start", + "start": "npx serve out", "typecheck": "tsc --noEmit", "format": "prettier --write ." }, From 28e20765efd1f3c9f24d1d50e454a54b57739e9a Mon Sep 17 00:00:00 2001 From: David Heyman Date: Wed, 4 Nov 2026 23:18:08 +0000 Subject: [PATCH 4/7] Add Vitest tests and ESLint 9 flat config Tests (npm test, Vitest, node environment) cover the pure modules in src/lib/contours: Terrarium decoding and unit rounding, threshold selection and the junk-band filter, hex normalisation and alpha, hash parsing and formatting, style resolution and fills (hypsometric domain shift, bathymetry only below sea level), SVG serialisation and the GeoJSON builder, all on small synthetic grids. Lint (npm run lint) uses eslint-config-next core-web-vitals + typescript with eslint-config-prettier on top. Fixes for what it reported: - color-field, number-field: adopt a changed prop during render instead of setState in an effect (react-hooks/set-state-in-effect). - place-search: clear results in the input handler, not the effect. - use-contour-map: mirror settings into the ref from an effect instead of during render (react-hooks/refs). - input.tsx: empty interface -> type alias. - avatar img tags: explicit no-img-element disables with a reason. - postcss.config.mjs: named export object. Co-Authored-By: Claude Fable 5.1 --- eslint.config.mjs | 12 + package-lock.json | 7454 ++++++++++++++--- package.json | 8 +- postcss.config.mjs | 4 +- src/components/base/avatar/avatar.tsx | 2 + .../base-components/avatar-company-icon.tsx | 2 + src/components/base/input/input.tsx | 2 +- src/components/contours/color-field.tsx | 11 +- src/components/contours/number-field.tsx | 11 +- src/components/contours/place-search.tsx | 14 +- src/hooks/use-contour-map.ts | 4 +- src/lib/contours/__tests__/color.test.ts | 51 + src/lib/contours/__tests__/draw.test.ts | 159 + src/lib/contours/__tests__/elevation.test.ts | 68 + src/lib/contours/__tests__/export.test.ts | 58 + src/lib/contours/__tests__/fixtures.ts | 36 + src/lib/contours/__tests__/hash.test.ts | 60 + src/lib/contours/__tests__/thresholds.test.ts | 57 + vitest.config.ts | 12 + 19 files changed, 7066 insertions(+), 959 deletions(-) create mode 100644 eslint.config.mjs create mode 100644 src/lib/contours/__tests__/color.test.ts create mode 100644 src/lib/contours/__tests__/draw.test.ts create mode 100644 src/lib/contours/__tests__/elevation.test.ts create mode 100644 src/lib/contours/__tests__/export.test.ts create mode 100644 src/lib/contours/__tests__/fixtures.ts create mode 100644 src/lib/contours/__tests__/hash.test.ts create mode 100644 src/lib/contours/__tests__/thresholds.test.ts create mode 100644 vitest.config.ts diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..b5252ed --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,12 @@ +import nextCoreWebVitals from "eslint-config-next/core-web-vitals"; +import nextTypescript from "eslint-config-next/typescript"; +import prettier from "eslint-config-prettier/flat"; +import { defineConfig, globalIgnores } from "eslint/config"; + +export default defineConfig([ + ...nextCoreWebVitals, + ...nextTypescript, + // Turn off the stylistic rules Prettier already enforces. + prettier, + globalIgnores([".next/**", "out/**", "build/**", "coverage/**", "next-env.d.ts"]), +]); diff --git a/package-lock.json b/package-lock.json index 5364aa0..347859e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "untitledui-nextjs-starter-kit", - "version": "0.0.0", + "name": "contours", + "version": "2.0.0", "lockfileVersion": 3, "requires": true, "packages": { "": { - "name": "untitledui-nextjs-starter-kit", - "version": "0.0.0", + "name": "contours", + "version": "2.0.0", "dependencies": { "@tailwindcss/postcss": "^4.3.3", "@untitledui/icons": "^0.0.23", @@ -37,11 +37,15 @@ "@types/node": "^26.6.3", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", + "eslint": "^9.39.5", + "eslint-config-next": "^16.4.3", + "eslint-config-prettier": "^10.1.8", "postcss": "^8.5.28", "postcss-loader": "^8.2.1", "prettier": "^3.9.9", "prettier-plugin-tailwindcss": "^0.8.1", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^5.1.0" } }, "node_modules/@alloc/quick-lru": { @@ -71,6 +75,57 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.13.tgz", + "integrity": "sha512-j1nvOQCZYSgp7HLmisnCXFTpVvFODiijQ9QHkORfGKWH6YDtskTEeCJS7eV545GZ9UBzrq0e2xsOd5QQ5PjIqw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.13", + "@babel/helper-compilation-targets": "^7.29.13", + "@babel/helper-module-transforms": "^7.29.13", + "@babel/helpers": "^7.29.13", + "@babel/parser": "^7.29.13", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.13", + "@babel/types": "^7.29.11", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/generator": { "version": "7.29.13", "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.13.tgz", @@ -88,6 +143,33 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.13.tgz", + "integrity": "sha512-8WxJ4/Vmes3ghacj8VOJy3NW8eyHiFY/4thU/LSBuBE470+igjR/j1ZTNn8fV5VdNrLx6iXUwig526UdOvfP0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@babel/helper-globals": { "version": "7.29.7", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", @@ -98,6 +180,38 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.13.tgz", + "integrity": "sha512-/U96wOz+SCoZ7Da/8kjDGltcNY280JPQcRNPK0Hk3PTbdpaiBDQImtC6JsbVwb1HGMr3usZQU2WsWVjhYNmRzw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.13" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, "node_modules/@babel/helper-string-parser": { "version": "7.29.13", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.13.tgz", @@ -118,6 +232,30 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.13", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.13.tgz", + "integrity": "sha512-laoRpD/kO/iA4CDlgJbixHekg7oPvWALK0PQyK3Ob2I2njZRvVAD77RfWCIImtqWCq6KijoBoZK7bp/HlTWASQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.11" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/parser": { "version": "7.29.13", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.13.tgz", @@ -192,6 +330,277 @@ "tslib": "^2.4.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-array/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/config-array/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.7.tgz", + "integrity": "sha512-F42g89Qd5oAWtp0k0nnSrjziAKza7w8SVT4mStc18LZMaRb4J1HQAHLCalEtDCxrTuksx7NU9qsmeLwpOfPqWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.14.0", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.3.2", + "minimatch": "^3.1.5", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.5.tgz", + "integrity": "sha512-QywQuszQh77pIXCsq998c8hbhSTI/azTty1Z6N53dmAudKHhy573j3yvRLsX2BSp8YpLtoCEG8E9DJe+8zUh4A==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/types": "^0.15.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, "node_modules/@img/colour": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz", @@ -771,6 +1180,49 @@ "integrity": "sha512-a7tz1QCcWWkwC/rPJcPMJk/UNRu9CFhDd3OHYE5hddbJrs8BqRTalxI6GlKCWEGzRLHN+S/B7a4PAHCtm25aBQ==", "license": "MIT" }, + "node_modules/@next/eslint-plugin-next": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.4.3.tgz", + "integrity": "sha512-oxuC06wt/71h6ACYCV9GbKOfnf5a7VlRe/p80YHTut15TNgDEmynDnaq8t/xhy5rlvk7dhItIPs6H3rvIM4kyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "4.9.1", + "fast-glob": "3.3.1" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@next/eslint-plugin-next/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@next/swc-darwin-arm64": { "version": "16.4.3", "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.4.3.tgz", @@ -899,15 +1351,359 @@ "node": ">= 10" } }, - "node_modules/@react-types/shared": { - "version": "3.37.0", - "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.37.0.tgz", - "integrity": "sha512-VNlZuifu4tHdxWPfHvoMfKsHA9kVTCGkduv5vKVTdinm8wHFJNhyVB5RzIMiRFj0q3RKoCbP68u2zaoBSiFueg==", + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nolyfill/is-core-module": { + "version": "1.0.39", + "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz", + "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.4.0" + } + }, + "node_modules/@oxc-project/types": { + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "url": "https://github.com/sponsors/oxc-project" + } + }, + "node_modules/@react-types/shared": { + "version": "3.37.0", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.37.0.tgz", + "integrity": "sha512-VNlZuifu4tHdxWPfHvoMfKsHA9kVTCGkduv5vKVTdinm8wHFJNhyVB5RzIMiRFj0q3RKoCbP68u2zaoBSiFueg==", "license": "Apache-2.0", "peerDependencies": { "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" } }, + "node_modules/@rolldown/binding-android-arm-eabi": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz", + "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz", + "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz", + "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz", + "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz", + "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz", + "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz", + "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz", + "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz", + "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz", + "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz", + "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz", + "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz", + "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz", + "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz", + "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true, + "license": "MIT" + }, "node_modules/@swc/helpers": { "version": "0.5.24", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.24.tgz", @@ -1271,6 +2067,17 @@ } } }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -1333,6 +2140,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/geojson": { "version": "7946.0.16", "resolved": "https://registry.npmjs.org/@types/geojson/-/geojson-7946.0.16.tgz", @@ -1340,6 +2161,20 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/json5": { + "version": "0.0.29", + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/leaflet": { "version": "1.9.22", "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.22.tgz", @@ -1380,1303 +2215,6038 @@ "@types/react": "^19.3.0" } }, - "node_modules/@untitledui/icons": { - "version": "0.0.23", - "resolved": "https://registry.npmjs.org/@untitledui/icons/-/icons-0.0.23.tgz", - "integrity": "sha512-WoBtPCXaxweF0RXS9IuqgEeJkBmT7A9SszPHB+vQJOxeAl0hDTFXHlWXsooYniXQ0JmP4+7RADdhWb39GWFT8g==", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.72.1.tgz", + "integrity": "sha512-5CokTWXpmbylfFabsbvHC0CAw3XLaGnoa+nFJX5FR592h/LoZsENUE6p/rg5xvkKrFEXz6pYmgI29AgXc2N3BQ==", + "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.72.1", + "@typescript-eslint/type-utils": "8.72.1", + "@typescript-eslint/utils": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, "peerDependencies": { - "react": ">= 16" + "@typescript-eslint/parser": "^8.72.1", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.14", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.14.tgz", + "integrity": "sha512-wCBiZ77u1tuWde6G/nfOtLaCTYcFDU5yCzARzK10y+rLhzYoL7a7yEemcopcc2Mzow7gZOIjnV2ym1NYmdiJOQ==", "dev": true, - "license": "Python-2.0" - }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, "engines": { - "node": ">=10" + "node": ">= 4" } }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/@typescript-eslint/parser": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.72.1.tgz", + "integrity": "sha512-ZGwZP4OCfyA0AQTpEAMWz+rtX01DRhQWW5hB0ucKDEIX5U7jdmPSaMx94rQfaPAQzIYz2BimOgKOJtjfpRFQ9g==", "dev": true, - "license": "MIT" - }, - "node_modules/baseline-browser-mapping": { - "version": "2.11.35", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz", - "integrity": "sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==", - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", + "debug": "^4.4.3" }, "engines": { - "node": ">=6.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/brace-expansion": { - "version": "2.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", - "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.72.1.tgz", + "integrity": "sha512-fAuVMkwdO/huI8nyo/WKnNKf6Gag93S56Rh3Ts5mGcavwSdaN8tRg1gCeIGX8PF+nzp1BjGfO4gHhGzW1J6jTg==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "@typescript-eslint/tsconfig-utils": "^8.72.1", + "@typescript-eslint/types": "^8.72.1", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.72.1.tgz", + "integrity": "sha512-lC/t6eMCZZQAAKZEYGL6RDVSuFehpdmMT6h+nJIjIa6oylN/PwDgDVtEIbB7O9xFfZYtZAYLoLlDNWKJhtF+uQ==", "dev": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1" + }, "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001820", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz", - "integrity": "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/client-only": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", - "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", - "license": "MIT" - }, - "node_modules/clsx": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", - "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.72.1.tgz", + "integrity": "sha512-e4D5R5vpeAEPBxT2BAzsFQL3sbwyeeeo5+aczgNSRK1V734jE7pTqaGFU+fXapWEQwnZbpFaTn32+Z8DwS6OTg==", + "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/cosmiconfig": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", - "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.72.1.tgz", + "integrity": "sha512-iY2wlmyrXJmx4yxUGP3aYCFF3cA/6ShddOKmLU2dx7LnEykbAM8WThlxZEfd1NddqceF6eJmZUayxx9oKIYNlg==", "dev": true, "license": "MIT", "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1", + "@typescript-eslint/utils": "8.72.1", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=14" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/d-fischer" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/csstype": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "node_modules/@typescript-eslint/types": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.72.1.tgz", + "integrity": "sha512-LmE5IhQet1BWOWEXqBrVtBPsMAOQ6Nzk35d+WLh3mk4EUF33sqlfxjio1MdYeSvKSG5skactKKuixBIKXhL4Rw==", "dev": true, - "license": "MIT" - }, - "node_modules/d3-array": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.5.tgz", - "integrity": "sha512-Th8qGXoBalmWsstpkuhmBuTsBQIFqYOJtjlBvNVvBfJ8E3uYusBUwUR8HU20U0qKKQkgCvKyR1YjiozWadk5QA==", - "license": "ISC", - "dependencies": { - "internmap": "1 - 2" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/d3-color": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.1.tgz", - "integrity": "sha512-lfab8x0Bj0UO7SNsY8dZSbNIA6ipbkE3X0NG9nqjCu6CfCHZseuDnOSJY7hidKrjpP++i1M2qT+FlAdPKYRilA==", - "license": "ISC", + "license": "MIT", "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/d3-contour": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.3.tgz", - "integrity": "sha512-5DYURc8KwXPOhSUBblU0P9rVcArsSPxPOxsfO0Eud0s28oU0sB2e80aRHRL9AZW3usT8ikFKyIHpHHLNWLdRVA==", - "license": "ISC", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.72.1.tgz", + "integrity": "sha512-DAlUoUV93hIvrtIQLwCbmZQYVZwOMhiHJrOVUUIWicFZaIYjTRqlY3I/NZevfkLmSqkNHRu2qYDcq9lrn16oOQ==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-array": "^3.2.4" + "@typescript-eslint/project-service": "8.72.1", + "@typescript-eslint/tsconfig-utils": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/visitor-keys": "8.72.1", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">=12" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/d3-format": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.3.tgz", - "integrity": "sha512-+F+/49swqmGuQJmSKnVMIDH/QkEFV5li9QRj0xm/KhyAUBv099O5PrvpQFxBp24PYph17Mnvt9mjKQV0ii0nMA==", - "license": "ISC", + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">=12" + "node": "18 || 20 || >=22" } }, - "node_modules/d3-geo": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.2.tgz", - "integrity": "sha512-5R8ZgDQorIxmauv7R6gpE/ndz+Z0qAZssigq0aXFdvPHdY41RqtW02jrRUQAL724NdPABYXo1+gQEGBBr7uKNg==", - "license": "ISC", + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.12.tgz", + "integrity": "sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-array": "2.5.0 - 3" + "balanced-match": "^4.0.2" }, "engines": { - "node": ">=12" + "node": "20 || >=22" } }, - "node_modules/d3-interpolate": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.2.tgz", - "integrity": "sha512-IRzGU62DtxhvsnSTWMQIdnoZ6gA+BCWwBon1EJI6SjoN595799b6z/QRBzXfFhE8iupUPp3042yBNo0STPqnzQ==", - "license": "ISC", + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", "dependencies": { - "d3-color": "1 - 3" + "brace-expansion": "^5.0.8" }, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-scale": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.3.tgz", - "integrity": "sha512-66cWobCNzcMvlfoaDHCxuIv9TBUWG1BLOWdv9kHHFuCr1JdqAWPvTmsaEYDD5NRMyWZ5+7u0GqiCDfMXMqh3hg==", - "license": "ISC", - "dependencies": { - "d3-array": "2.10.0 - 3", - "d3-format": "1 - 3", - "d3-interpolate": "1.2.0 - 3", - "d3-time": "2.1.1 - 3", - "d3-time-format": "2 - 4" + "node": "18 || 20 || >=22" }, - "engines": { - "node": ">=12" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/d3-time": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.1.tgz", - "integrity": "sha512-Ze7rjrhMzdeulh0/hjcfFj7XLAHOfowtEMfsoG6DU866HPynQNW7Wfen6hzXcqKTv8XzxvF6Hzr5XD6bMHr2ug==", - "license": "ISC", + "node_modules/@typescript-eslint/utils": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.72.1.tgz", + "integrity": "sha512-5v5liypurJBFHCBNqh/RjsymAD+VWcE0djuGY/BUqp5fC3Qn+luWDEksjuTtWjPn6i5zQlUtkZCRG/CYG0yCkA==", + "dev": true, + "license": "MIT", "dependencies": { - "d3-array": "2 - 3" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.72.1", + "@typescript-eslint/types": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1" }, "engines": { - "node": ">=12" - } - }, - "node_modules/d3-time-format": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.1.tgz", - "integrity": "sha512-22XaqjyCjpqgda20erhsOjGmC3vvYwTNYniKAd45W5UWLDF43moeOuV9qYpRqobIlsnK0UhnKZjUUJm3VgORUA==", - "license": "ISC", - "dependencies": { - "d3-time": "1 - 3" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "engines": { - "node": ">=12" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.72.1.tgz", + "integrity": "sha512-SB8xqipSzZHUePCCLNeYxqnxq83X61/qzNhjQGbTfjxGZIeOEP7tBgWYX05Tcd6CgE02WdTJnh5b8kMZJ/+bnQ==", "dev": true, "license": "MIT", "dependencies": { - "ms": "^2.1.3" + "@typescript-eslint/types": "8.72.1", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": ">=6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=8" - } - }, - "node_modules/enhanced-resolve": { - "version": "5.26.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.1.tgz", - "integrity": "sha512-PPnnSJq3azKOIewXR/uppBV/6h98dprBJ/WaQ9Qgj10xX1lJLNfg+XShpbsrSqcDSy2yYYyhfXM1wWhftqgyzw==", - "license": "MIT", - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.3.3" + "node": "^20.19.0 || ^22.13.0 || >=24" }, - "engines": { - "node": ">=10.13.0" + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.13.0.tgz", + "integrity": "sha512-60E5mvmIiW+i1E/HYeSX4iE4A8s9NJSGYPu7UM0DJght1c2tTkWwuyDyAwzsEXhKM/XbbndfnTiXdfrf27d2hw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.13.0.tgz", + "integrity": "sha512-qlh/JR3Tuy1cJ7xtf5pR4ykvLiK6os7X7EL2+zXZbrVc/s+yB8DoFdQrCAW9/a2TOxPVbR7QVNpkPWh7UWIsEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.13.0.tgz", + "integrity": "sha512-yynnnnJXUC99WeD1GfvuXPKsKNfvgzGYC2eRayFnUfsHVROjqrbFOkFQLZiSx1H8+PLy5BzU8vqyPyLqbRfeBQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.13.0.tgz", + "integrity": "sha512-t8PXE0Ip9tmGyhRlfVsnEbzGM5iSvJIeNIXciUACjzpg0mmPs13fGv2x7W8sBa0S8zbIiohxKXS9fL/LXW/ERQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.13.0.tgz", + "integrity": "sha512-qdtl9GDWDoDx6rhzF4RU5SJutV3lcLOiTiyqD6nBxYfEO6lAgLNukfrA+HJ2u2OiyWvV8JW5iEWD3LoONw7lYA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.13.0.tgz", + "integrity": "sha512-VrMUVwdrScTarsh4B6aUhTtziUavSlglo4Hi8XZ6CpAe4+j3nquY+XoP+tYkiSBDsXZ8386ne4pv4YYYBNi02w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.13.0.tgz", + "integrity": "sha512-zSbTVwaxiTC+zYJ9xVNTC6c+BYO/kOV/odcTI3bHuFFXa+XmjLoUkb/8UxsZeU5YF4K000m17wrXoTblNWuzOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.13.0.tgz", + "integrity": "sha512-Z/Rax87moZHsuvJ8awdfP+zrMlsCt3d7mggUPIiPe32KpeagimyzJWnZSMQoV67an9uVQghyh92Pc0VprITnYA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.13.0.tgz", + "integrity": "sha512-oUzXz8hNvFLNEAag8lBKJe1euiV2dmGHAR+eQ5fNpsZO0F1K7IMRb4J6pqYsd7uLMRQNPiu5A1WKUJHbFLWbbw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-gnu/-/resolver-binding-linux-loong64-gnu-1.13.0.tgz", + "integrity": "sha512-BxhGYvZBg0YrvNOeSEGR3eNklwXYy0mUJk7oeGJvZoQMKM68zYvbbf52+fQI3Le4X0fW276FTKIoTwjpMob80A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-loong64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-loong64-musl/-/resolver-binding-linux-loong64-musl-1.13.0.tgz", + "integrity": "sha512-nZcIuup9Ar3gAlqoZfX/KW6x83TMI11tLpCjldZG5M1KeY8qNUPNJJ71+sPvG8HMBpoh+ix1pAm5gkMFvcSAcQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.13.0.tgz", + "integrity": "sha512-AG6AgdDrkRNjAPwD/cftFJGgZUWCiJX+E8vpYCDEnwFHLTHZ7ppBiU6/vyPPwrfjcPUPQCDbbxE/eZUXsx49jQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.13.0.tgz", + "integrity": "sha512-6vOqYGcAmzpRWpUURWutzvpf1qJ6eOu5cfYObdtPJnzZ0+Kw4cw+ZkZJqIYEh8fPuTBwSYDL/FwlUhma1G/JVQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.13.0.tgz", + "integrity": "sha512-4HeIn0S2KVUowK0A+EweXrDvZ8CKHEfTtif0kx9vt3ORNsqcXNFFIFnHE0tizma3/Gt5OGrU2ovYNuj+D/bN4g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.13.0.tgz", + "integrity": "sha512-JfkvqY7NZbxyhexLE+g6Z8Hsx4utHOGaVazAVghPiBn8Upw2iERZPpKYnW1FBVU4WStv/BiIsoK/Muke0E7asA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.13.0.tgz", + "integrity": "sha512-iwjLeVdZo/8v5OKAeRIC+4VQV045t1EPZr5B/AVLgUQ04IdtNthWzg6not2cNjZgH2gHpSf4fOXMmN83aLO1FA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.13.0.tgz", + "integrity": "sha512-TOh1fqOjyq1OPoht3BDLd7Y+HeMJRwd5g2VNmCQclknEbjp4py5GFRmT4L/rOybF5Af8q0LT9zHs2WGkwlqtrw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-openharmony-arm64": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-openharmony-arm64/-/resolver-binding-openharmony-arm64-1.13.0.tgz", + "integrity": "sha512-3T2MNtF2dd1yjaXCVqrsi+/3VZeksEzU1hygPzqqkBLVVjn+2iJzXEkxxCKIVFrWnpmEbazeHJp/ruYD0RUVWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.13.0.tgz", + "integrity": "sha512-hKWfSLiN3L4cIxg0jrpyP/66AXLTcyD37jmSn//rGdOUYGlizYxTEFdJ+GZbp2NnaML3YS/A73nCysze8FaJdg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.13.0.tgz", + "integrity": "sha512-N4gDKkDoiyIQEcn3vTqe2ksRxUAtBvQ8c5TiT+WonSa6FHgCAY5+RggnZ134hXJoRj57JEjv4/m+i/pAKHWrTQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.13.0.tgz", + "integrity": "sha512-DpLMlAqlrk4zBVK3DiDjwKD0oq7pZNy+8BiIBRuByqb6dm5jsdoLMhIiyh/xR7+WejaW/1Ui+XgbgMNa4+zWJg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@untitledui/icons": { + "version": "0.0.23", + "resolved": "https://registry.npmjs.org/@untitledui/icons/-/icons-0.0.23.tgz", + "integrity": "sha512-WoBtPCXaxweF0RXS9IuqgEeJkBmT7A9SszPHB+vQJOxeAl0hDTFXHlWXsooYniXQ0JmP4+7RADdhWb39GWFT8g==", + "license": "MIT", + "peerDependencies": { + "react": ">= 16" + } + }, + "node_modules/@vitest/mocker": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.1.0.tgz", + "integrity": "sha512-5umB9/jlWcqVS6fsrFmv5Gdnpu6wAUio/lEOwAmvB7v+lFo/IW2B9FhAD0p19GCULTgMzLCdUmuCDCnAzWgppQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.31", + "@vitest/spy": "5.1.0", + "estree-walker": "^3.0.3", + "magic-string": "^1.4.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9 || ^3.0.0", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/spy": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.1.0.tgz", + "integrity": "sha512-2DPO5OMHWKrBngC+fCmTQqemNF0gidRhp3lxbBmRhW+M1XJrfgPpqNqWVevXtn2f2ufD5ZaR1B6B641aAlcX5A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/acorn": { + "version": "8.19.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.19.0.tgz", + "integrity": "sha512-oJlA3XiRm7Cyk6qFD2Jn8ak9B7jSy0qy00ADO3+8dpT0LSjFihQYv4C02LFCSYJV3Q37xYwwRy5m+IpIiUzqWw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/aria-hidden": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", + "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/aria-query": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz", + "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-includes": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.2.0.tgz", + "integrity": "sha512-VXY5eFRarnXcYxwBjJzPmEhH55+rmP79/+ueDhi0F+TuqfHCItagIHqxeUZrmgrOPa31QTh9H85DjX3FfJ0FTg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "es-shim-unscopables": "^1.1.0", + "is-string": "^1.1.1", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlast": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz", + "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.findlastindex": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz", + "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-shim-unscopables": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flat": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.flatmap": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array.prototype.tosorted": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz", + "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3", + "es-errors": "^1.3.0", + "es-shim-unscopables": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.5", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/ast-types-flow": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz", + "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/async-function": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axe-core": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.14.0.tgz", + "integrity": "sha512-9WTZxEjsZ7b13TH8JPmbV2z8CHbl80/2hm3XPEG4JgNdQLK81IBRXmSxHfMAOkSqQeRxT/0dwNDz2GOm3zzpcQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=4" + } + }, + "node_modules/axobject-query": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", + "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.35", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.35.tgz", + "integrity": "sha512-GNk8KX52CHqeNKGk8+7tZnQab/2k1t/BgrUHPKNdijzpEhF1saPQZHVKb6m5loSAxB39gyrTYw34Qf9CQ3u1qQ==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.5.tgz", + "integrity": "sha512-khTmKu5NGYTotqcYo8Bew2k4RAE9PuXRSFRXG2lQBrCEQHyeQ/vX6gKMxfokwywBsLiXZMnHsmWMG1pnO8J7uQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.11.33", + "caniuse-lite": "^1.0.30001819", + "electron-to-chromium": "^1.5.464", + "node-releases": "^2.0.60", + "update-browserslist-db": "^1.3.4" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001820", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001820.tgz", + "integrity": "sha512-gfWD6MxqVCqAOwmODfZf7DchWvlrA+SRdNX7ceLfg3xRqe5ssjjpF7EgslC/0tiLiNTajdaNN/q8c/5u/7Gz8w==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chai": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "license": "MIT" + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, + "node_modules/cosmiconfig": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.2.tgz", + "integrity": "sha512-gtTZxTDau1wL7Y7zifc2dd8jHSK/k6BTx/2Xp/BpdlAdnlYWFVt7qhJqgwi7637yRwRQ3qL4ZidbB4I8tA5VOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "env-paths": "^2.2.1", + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.5.tgz", + "integrity": "sha512-Th8qGXoBalmWsstpkuhmBuTsBQIFqYOJtjlBvNVvBfJ8E3uYusBUwUR8HU20U0qKKQkgCvKyR1YjiozWadk5QA==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.1.tgz", + "integrity": "sha512-lfab8x0Bj0UO7SNsY8dZSbNIA6ipbkE3X0NG9nqjCu6CfCHZseuDnOSJY7hidKrjpP++i1M2qT+FlAdPKYRilA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-contour": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/d3-contour/-/d3-contour-4.0.3.tgz", + "integrity": "sha512-5DYURc8KwXPOhSUBblU0P9rVcArsSPxPOxsfO0Eud0s28oU0sB2e80aRHRL9AZW3usT8ikFKyIHpHHLNWLdRVA==", + "license": "ISC", + "dependencies": { + "d3-array": "^3.2.4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.3.tgz", + "integrity": "sha512-+F+/49swqmGuQJmSKnVMIDH/QkEFV5li9QRj0xm/KhyAUBv099O5PrvpQFxBp24PYph17Mnvt9mjKQV0ii0nMA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-geo": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-geo/-/d3-geo-3.1.2.tgz", + "integrity": "sha512-5R8ZgDQorIxmauv7R6gpE/ndz+Z0qAZssigq0aXFdvPHdY41RqtW02jrRUQAL724NdPABYXo1+gQEGBBr7uKNg==", + "license": "ISC", + "dependencies": { + "d3-array": "2.5.0 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.2.tgz", + "integrity": "sha512-IRzGU62DtxhvsnSTWMQIdnoZ6gA+BCWwBon1EJI6SjoN595799b6z/QRBzXfFhE8iupUPp3042yBNo0STPqnzQ==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.3.tgz", + "integrity": "sha512-66cWobCNzcMvlfoaDHCxuIv9TBUWG1BLOWdv9kHHFuCr1JdqAWPvTmsaEYDD5NRMyWZ5+7u0GqiCDfMXMqh3hg==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.1.tgz", + "integrity": "sha512-Ze7rjrhMzdeulh0/hjcfFj7XLAHOfowtEMfsoG6DU866HPynQNW7Wfen6hzXcqKTv8XzxvF6Hzr5XD6bMHr2ug==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.1.tgz", + "integrity": "sha512-22XaqjyCjpqgda20erhsOjGmC3vvYwTNYniKAd45W5UWLDF43moeOuV9qYpRqobIlsnK0UhnKZjUUJm3VgORUA==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/damerau-levenshtein": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz", + "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/data-view-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/data-view-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/inspect-js" + } + }, + "node_modules/data-view-byte-offset": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.477", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.477.tgz", + "integrity": "sha512-PN++F4zWZoVDGWinxMnSCpVqKhqFOJfLXxdU8bYJKudAEsHL/XKVITOKWVbfMt1r276Mi9RDLU9VHx9uKJ4RMQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.26.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.26.1.tgz", + "integrity": "sha512-PPnnSJq3azKOIewXR/uppBV/6h98dprBJ/WaQ9Qgj10xX1lJLNfg+XShpbsrSqcDSy2yYYyhfXM1wWhftqgyzw==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.3.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/error-ex": { "version": "1.3.4", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", "dev": true, "license": "MIT", "dependencies": { - "is-arrayish": "^0.2.1" + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.24.2", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", + "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.2", + "arraybuffer.prototype.slice": "^1.0.4", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "data-view-buffer": "^1.0.2", + "data-view-byte-length": "^1.0.2", + "data-view-byte-offset": "^1.0.1", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "es-set-tostringtag": "^2.1.0", + "es-to-primitive": "^1.3.0", + "function.prototype.name": "^1.1.8", + "get-intrinsic": "^1.3.0", + "get-proto": "^1.0.1", + "get-symbol-description": "^1.1.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "internal-slot": "^1.1.0", + "is-array-buffer": "^3.0.5", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.2", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.2.1", + "is-set": "^2.0.3", + "is-shared-array-buffer": "^1.0.4", + "is-string": "^1.1.1", + "is-typed-array": "^1.1.15", + "is-weakref": "^1.1.1", + "math-intrinsics": "^1.1.0", + "object-inspect": "^1.13.4", + "object-keys": "^1.1.1", + "object.assign": "^4.1.7", + "own-keys": "^1.0.1", + "regexp.prototype.flags": "^1.5.4", + "safe-array-concat": "^1.1.3", + "safe-push-apply": "^1.0.0", + "safe-regex-test": "^1.1.0", + "set-proto": "^1.0.0", + "stop-iteration-iterator": "^1.1.0", + "string.prototype.trim": "^1.2.10", + "string.prototype.trimend": "^1.0.9", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.3", + "typed-array-byte-length": "^1.0.3", + "typed-array-byte-offset": "^1.0.4", + "typed-array-length": "^1.0.7", + "unbox-primitive": "^1.1.0", + "which-typed-array": "^1.1.19" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract-get": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", + "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "is-callable": "^1.2.7", + "object-inspect": "^1.13.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-iterator-helpers": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.4.0.tgz", + "integrity": "sha512-c/A0P0oxkACDc+cKWw8evLXK83oBKgn0qPOqCYT4x9uolpCIJAcYvJC9QYKNDRPsTeGyCrQ326jrvgZWdCdK5Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-set-tostringtag": "^2.1.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.3.0", + "globalthis": "^1.0.4", + "gopd": "^1.2.0", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "iterator.prototype": "^1.1.5", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.2.tgz", + "integrity": "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-shim-unscopables": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", + "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-abstract-get": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "is-callable": "^1.2.7", + "is-date-object": "^1.1.0", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.5", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.5.tgz", + "integrity": "sha512-DgZS62aPLXKlnxILS/AYCoRvHaZeXceIzlXPkkGGzJWSow1aEk0lbTlxUSlyjC8jcaKxAdOnTDz+o1JFSBsyjw==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.6", + "@eslint/js": "9.39.5", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-config-next": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.4.3.tgz", + "integrity": "sha512-j9IqBma6abp800frdUURaizYMgXOK+jNGyq6CsYcM9AxpSJs5D/TgMNGeAmRUilZvA7g9V1Tr71gNg9/SVTuMQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@next/eslint-plugin-next": "16.4.3", + "eslint-import-resolver-node": "^0.3.6", + "eslint-import-resolver-typescript": "^3.5.2", + "eslint-plugin-import": "^2.32.0", + "eslint-plugin-jsx-a11y": "^6.10.0", + "eslint-plugin-react": "^7.37.0", + "eslint-plugin-react-hooks": "^7.1.0", + "globals": "16.4.0", + "typescript-eslint": "^8.56.0" + }, + "peerDependencies": { + "eslint": ">=9.0.0", + "typescript": ">=3.3.1" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/eslint-config-prettier": { + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", + "dev": true, + "license": "MIT", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-import-resolver-node": { + "version": "0.3.10", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", + "integrity": "sha512-tRrKqFyCaKict5hOd244sL6EQFNycnMQnBe+j8uqGNXYzsImGbGUU4ibtoaBmv5FLwJwcFJNeg1GeVjQfbMrDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.16.1", + "resolve": "^2.0.0-next.6" + } + }, + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-import-resolver-typescript": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz", + "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "@nolyfill/is-core-module": "1.0.39", + "debug": "^4.4.0", + "get-tsconfig": "^4.10.0", + "is-bun-module": "^2.0.0", + "stable-hash": "^0.0.5", + "tinyglobby": "^0.2.13", + "unrs-resolver": "^1.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint-import-resolver-typescript" + }, + "peerDependencies": { + "eslint": "*", + "eslint-plugin-import": "*", + "eslint-plugin-import-x": "*" + }, + "peerDependenciesMeta": { + "eslint-plugin-import": { + "optional": true + }, + "eslint-plugin-import-x": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils": { + "version": "2.14.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.14.0.tgz", + "integrity": "sha512-W2WCRZ9Dqntd+2u8jJcVMV2PKulc6RdLgUUoh/yQr3uB6lo/ZOeGx11sv60/8S4QFFKNslAlWhr9u0Ef7ZW6Ig==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^3.2.7" + }, + "engines": { + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } + } + }, + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz", + "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.9", + "array.prototype.findlastindex": "^1.2.6", + "array.prototype.flat": "^1.3.3", + "array.prototype.flatmap": "^1.3.3", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.1", + "hasown": "^2.0.2", + "is-core-module": "^2.16.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.1", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.9", + "tsconfig-paths": "^3.15.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-import/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eslint-plugin-import/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-import/node_modules/semver": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-plugin-jsx-a11y": { + "version": "6.10.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz", + "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "aria-query": "^5.3.2", + "array-includes": "^3.1.8", + "array.prototype.flatmap": "^1.3.2", + "ast-types-flow": "^0.0.8", + "axe-core": "^4.10.0", + "axobject-query": "^4.1.0", + "damerau-levenshtein": "^1.0.8", + "emoji-regex": "^9.2.2", + "hasown": "^2.0.2", + "jsx-ast-utils": "^3.3.5", + "language-tags": "^1.0.9", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "safe-regex-test": "^1.0.3", + "string.prototype.includes": "^2.0.1" + }, + "engines": { + "node": ">=4.0" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-jsx-a11y/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react": { + "version": "7.37.5", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz", + "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.8", + "array.prototype.findlast": "^1.2.5", + "array.prototype.flatmap": "^1.3.3", + "array.prototype.tosorted": "^1.1.4", + "doctrine": "^2.1.0", + "es-iterator-helpers": "^1.2.1", + "estraverse": "^5.3.0", + "hasown": "^2.0.2", + "jsx-ast-utils": "^2.4.1 || ^3.0.0", + "minimatch": "^3.1.2", + "object.entries": "^1.1.9", + "object.fromentries": "^2.0.8", + "object.values": "^1.2.1", + "prop-types": "^15.8.1", + "resolve": "^2.0.0-next.5", + "semver": "^6.3.1", + "string.prototype.matchall": "^4.0.12", + "string.prototype.repeat": "^1.0.0" + }, + "engines": { + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7" + } + }, + "node_modules/eslint-plugin-react-hooks": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-react/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint-plugin-react/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint-plugin-react/node_modules/semver": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, + "license": "ISC" + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/function.prototype.name": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", + "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2", + "hasown": "^2.0.4", + "is-callable": "^1.2.7", + "is-document.all": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/generator-function": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", + "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/get-symbol-description": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-tsconfig": { + "version": "4.14.3", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.3.tgz", + "integrity": "sha512-++QEw4DIY7WGoukz+/+A/8dGYPT9l9yIadnmSgZ8Rjr3YVSVDipQSO9CdnJo9ePqFqUUqh+wk9uIaoiAwsiPkA==", + "dev": true, + "license": "MIT", + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globalthis": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.2.1", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "dev": true, + "license": "MIT" + }, + "node_modules/is-async-function": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "async-function": "^1.0.0", + "call-bound": "^1.0.3", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bun-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz", + "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.7.1" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.18.0.tgz", + "integrity": "sha512-qyax9fgaKOEC4qK8WZn7Q4IcoZC9RC1O1fFxRcRTq/oMMkWT/uXpqpXa12kzWR409zAt1drYN9N9BO26UmBCmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-data-view": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "get-intrinsic": "^1.2.6", + "is-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-document.all": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", + "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-finalizationregistry": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-generator-function": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", + "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "generator-function": "^2.0.0", + "get-proto": "^1.0.1", + "has-tostringtag": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", + "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true, + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/iterator.prototype": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", + "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.6", + "get-proto": "^1.0.0", + "has-symbols": "^1.1.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/javascript-natural-sort": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", + "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "dev": true, + "license": "MIT" + }, + "node_modules/jiti": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", + "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", + "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsx-ast-utils": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", + "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-includes": "^3.1.6", + "array.prototype.flat": "^1.3.1", + "object.assign": "^4.1.4", + "object.values": "^1.1.6" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/language-subtag-registry": { + "version": "0.3.23", + "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", + "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/language-tags": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz", + "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==", + "dev": true, + "license": "MIT", + "dependencies": { + "language-subtag-registry": "^0.3.20" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "license": "BSD-2-Clause" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lightningcss": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", + "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.6.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.8.tgz", + "integrity": "sha512-dHbDC5V1g/EI8OLEtL4Iuwbu0fgzmyykxenn2KnlsX9XdhTXL3gWnfbbP+ab+oUcgV6CFpb8QGyQt/ZXA0UXig==", + "dev": true, + "license": "MIT", + "bin": { + "napi-patch": "lib/patch.js", + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/next": { + "version": "16.4.3", + "resolved": "https://registry.npmjs.org/next/-/next-16.4.3.tgz", + "integrity": "sha512-OqxKTGi9u80FydydsMNYNjnuje9Au+kvHlt2GZ//ftdpDHm1dxKqAaENoOOTX5wKzn+9vaKUNIfZeyZUuLhCnA==", + "license": "MIT", + "dependencies": { + "@next/env": "16.4.3", + "@swc/helpers": "0.5.23", + "baseline-browser-mapping": "^2.9.19", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.5.23", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": ">=20.9.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "16.4.3", + "@next/swc-darwin-x64": "16.4.3", + "@next/swc-linux-arm64-gnu": "16.4.3", + "@next/swc-linux-arm64-musl": "16.4.3", + "@next/swc-linux-x64-gnu": "16.4.3", + "@next/swc-linux-x64-musl": "16.4.3", + "@next/swc-win32-arm64-msvc": "16.4.3", + "@next/swc-win32-x64-msvc": "16.4.3", + "sharp": "^0.35.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.51.1", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next/node_modules/@swc/helpers": { + "version": "0.5.23", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", + "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.8.0" + } + }, + "node_modules/next/node_modules/postcss": { + "version": "8.5.23", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", + "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.16", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/node-exports-info": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.2.tgz", + "integrity": "sha512-kXs9Go0cah0qHVV2v389IXQLdLCeE1xfFtjOAF+iobu0OIoG1pje8At2vMHyaPMiPMnG/LWP50twML21eMcAag==", + "dev": true, + "license": "MIT", + "dependencies": { + "array.prototype.flatmap": "^1.3.3", + "es-errors": "^1.3.0", + "object.entries": "^1.1.9", + "semver": "^6.3.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/node-exports-info/node_modules/semver": { + "version": "6.3.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.2.tgz", + "integrity": "sha512-GYrCADwpu6yV5OAvchkQ27Z5wwMxWAEMg5NNWjyHh/5DboFFqbdbvYNIvZouQpxXyeMPoTOh0vdPYKlf0ZswdQ==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/node-releases": { + "version": "2.0.63", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.63.tgz", + "integrity": "sha512-6li2QanLsAXpIH2R6j77MzRyvOyrvdTqlKtO4CgBdHGNvN99ZZfyxszHc5o5+IZBWD5u6Hzwg83JoGXmqZNG9Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.entries": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz", + "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.fromentries": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object.groupby": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.values": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/obug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", + "dev": true, + "funding": [ + "https://github.com/sponsors/sxzz", + "https://opencollective.com/debug" + ], + "license": "MIT", + "engines": { + "node": ">=12.20.0" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/own-keys": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.2.tgz", + "integrity": "sha512-19YVAg7T+WTrxggPukVq7DjTv6+PJ867TmhCvBsYwmbFCsZd344rq2Ld1p0wo8f8Qrrhgp82c6FJRqdXWtSEhg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "object-keys": "^1.1.1", + "safe-push-apply": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-imports-exports": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-statements": "1.0.11" + } + }, + "node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parse-statements": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", + "dev": true, + "license": "MIT" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/postcss": { + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-loader": { + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.1.tgz", + "integrity": "sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==", + "dev": true, + "license": "MIT", + "dependencies": { + "cosmiconfig": "^9.0.0", + "jiti": "^2.5.1", + "semver": "^7.6.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", + "postcss": "^7.0.0 || ^8.0.1", + "webpack": "^5.0.0" + }, + "peerDependenciesMeta": { + "@rspack/core": { + "optional": true + }, + "webpack": { + "optional": true + } + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.9.10", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", + "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.8.1.tgz", + "integrity": "sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20.19" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-hermes": "*", + "@prettier/plugin-oxc": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "@zackad/prettier-plugin-twig": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-multiline-arrays": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-sort-imports": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-hermes": { + "optional": true + }, + "@prettier/plugin-oxc": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "@zackad/prettier-plugin-twig": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-multiline-arrays": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + } + } + }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/react": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", + "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-aria": { + "version": "3.53.1", + "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.53.1.tgz", + "integrity": "sha512-dikisyOjJd3D2fFrIDWmVWzqiLz5B86GzZv1zxs7xI4dVc0JIz/1Wa52I20gYvVL2WXeFXlygYsle/QcOMhtcg==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.9", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.37.0", + "@swc/helpers": "^0.5.0", + "aria-hidden": "^1.2.3", + "clsx": "^2.0.0", + "react-stately": "3.51.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-aria-components": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.22.1.tgz", + "integrity": "sha512-UTHaYHOaM71bOCjGPF50DcLywlrwAodE63IP64LB4kO+OizatQEL8TesVEOM8+TkBeSgHoflJ45388IlJfa7aw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.37.0", + "@swc/helpers": "^0.5.0", + "client-only": "^0.0.1", + "react-aria": "3.53.1", + "react-stately": "3.51.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", + "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/react-dom": { + "version": "19.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", + "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", + "license": "MIT", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.1" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" + "node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "dev": true, + "license": "MIT" }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "node_modules/react-stately": { + "version": "3.51.0", + "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.51.0.tgz", + "integrity": "sha512-YJcmkAXu2XOQP97hO/qz/cC3qv9kni3oVhwBlxWr358BHePJUpY15XwL3YSRMKxYt4EkslPqAz11JRirMfANGw==", + "license": "Apache-2.0", + "dependencies": { + "@internationalized/date": "^3.12.4", + "@internationalized/number": "^3.6.9", + "@internationalized/string": "^3.2.10", + "@react-types/shared": "^3.37.0", + "@swc/helpers": "^0.5.0", + "use-sync-external-store": "^1.6.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + } + }, + "node_modules/reflect.getprototypeof": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", "dev": true, "license": "MIT", "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.9", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.7", + "get-proto": "^1.0.1", + "which-builtin-type": "^1.2.1" }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, "engines": { - "node": ">=12" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", + "node_modules/resolve": { + "version": "2.0.0-next.7", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.7.tgz", + "integrity": "sha512-tqt+NBWwyaMgw3zDsnygx4CByWjQEJHOPMdslYhppaQSJUtL/D4JO9CcBBlhPoI8lz9oJIDXkwXfhF4aWqP8xQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.2", + "node-exports-info": "^1.6.0", + "object-keys": "^1.1.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/javascript-natural-sort": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/javascript-natural-sort/-/javascript-natural-sort-0.7.1.tgz", - "integrity": "sha512-nO6jcEfZWQXDhOiBtG2KvKyEptz7RVbpGP4vTD2hLBdmNQSsCiicO2Ioinv6UI4y9ukqnBpy+XZ9H6uLNgJTlw==", + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "node_modules/jiti": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.7.0.tgz", - "integrity": "sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, "license": "MIT", - "bin": { - "jiti": "lib/jiti-cli.mjs" + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } }, - "node_modules/js-yaml": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.2.tgz", - "integrity": "sha512-SFNOvSJ+Dgf/9An904Yx+CgSlIPCkIpao4qo51lpee25TIRejdH3rhR4EZMGoNx3/TP3O+wzWuiTFl4sqbltzA==", + "node_modules/rolldown": { + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz", + "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==", + "dev": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@oxc-project/types": "=0.156.0", + "@rolldown/pluginutils": "^1.0.0" + }, + "bin": { + "rolldown": "bin/cli.mjs" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "optionalDependencies": { + "@rolldown/binding-android-arm-eabi": "1.2.16", + "@rolldown/binding-android-arm64": "1.2.16", + "@rolldown/binding-darwin-arm64": "1.2.16", + "@rolldown/binding-darwin-x64": "1.2.16", + "@rolldown/binding-freebsd-x64": "1.2.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", + "@rolldown/binding-linux-arm64-gnu": "1.2.16", + "@rolldown/binding-linux-arm64-musl": "1.2.16", + "@rolldown/binding-linux-ppc64-gnu": "1.2.16", + "@rolldown/binding-linux-s390x-gnu": "1.2.16", + "@rolldown/binding-linux-x64-gnu": "1.2.16", + "@rolldown/binding-linux-x64-musl": "1.2.16", + "@rolldown/binding-openharmony-arm64": "1.2.16", + "@rolldown/binding-win32-arm64-msvc": "1.2.16", + "@rolldown/binding-win32-x64-msvc": "1.2.16" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", "dev": true, "funding": [ { "type": "github", - "url": "https://github.com/sponsors/puzrin" + "url": "https://github.com/sponsors/feross" }, { - "type": "github", - "url": "https://github.com/sponsors/nodeca" + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } ], "license": "MIT", "dependencies": { - "argparse": "^2.0.1" + "queue-microtask": "^1.2.2" + } + }, + "node_modules/safe-array-concat": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", + "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "get-intrinsic": "^1.3.0", + "has-symbols": "^1.1.0", + "isarray": "^2.0.5" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "engines": { + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/jsesc": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", - "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "node_modules/safe-push-apply": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" + "dependencies": { + "es-errors": "^1.3.0", + "isarray": "^2.0.5" }, "engines": { - "node": ">=6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", "license": "MIT" }, - "node_modules/leaflet": { - "version": "1.9.4", - "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", - "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", - "license": "BSD-2-Clause" + "node_modules/semver": { + "version": "7.8.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.6.tgz", + "integrity": "sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==", + "devOptional": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-proto": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", + "dev": true, + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/sharp": { + "version": "0.35.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", + "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "@img/colour": "^1.1.0", + "detect-libc": "^2.1.2", + "semver": "^7.8.5" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.35.5", + "@img/sharp-darwin-x64": "0.35.5", + "@img/sharp-freebsd-wasm32": "0.35.5", + "@img/sharp-libvips-darwin-arm64": "1.3.4", + "@img/sharp-libvips-darwin-x64": "1.3.4", + "@img/sharp-libvips-linux-arm": "1.3.4", + "@img/sharp-libvips-linux-arm64": "1.3.4", + "@img/sharp-libvips-linux-ppc64": "1.3.4", + "@img/sharp-libvips-linux-riscv64": "1.3.4", + "@img/sharp-libvips-linux-s390x": "1.3.4", + "@img/sharp-libvips-linux-x64": "1.3.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", + "@img/sharp-libvips-linuxmusl-x64": "1.3.4", + "@img/sharp-linux-arm": "0.35.5", + "@img/sharp-linux-arm64": "0.35.5", + "@img/sharp-linux-ppc64": "0.35.5", + "@img/sharp-linux-riscv64": "0.35.5", + "@img/sharp-linux-s390x": "0.35.5", + "@img/sharp-linux-x64": "0.35.5", + "@img/sharp-linuxmusl-arm64": "0.35.5", + "@img/sharp-linuxmusl-x64": "0.35.5", + "@img/sharp-webcontainers-wasm32": "0.35.5", + "@img/sharp-win32-arm64": "0.35.5", + "@img/sharp-win32-ia32": "0.35.5", + "@img/sharp-win32-x64": "0.35.5" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/lightningcss": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", - "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", - "license": "MPL-2.0", + "node_modules/side-channel": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", + "integrity": "sha512-6x6dK6zJdpTzF4sQeNYxwtvBzf6Eg4GtlesS94HOvTudUeyK2WXAaIfmDgsyslYrRBeFIlsi54AYsFGUuhmvrQ==", + "dev": true, + "license": "MIT", "dependencies": { - "detect-libc": "^2.0.3" + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4", + "side-channel-list": "^1.0.1", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "lightningcss-android-arm64": "1.33.0", - "lightningcss-darwin-arm64": "1.33.0", - "lightningcss-darwin-x64": "1.33.0", - "lightningcss-freebsd-x64": "1.33.0", - "lightningcss-linux-arm-gnueabihf": "1.33.0", - "lightningcss-linux-arm64-gnu": "1.33.0", - "lightningcss-linux-arm64-musl": "1.33.0", - "lightningcss-linux-x64-gnu": "1.33.0", - "lightningcss-linux-x64-musl": "1.33.0", - "lightningcss-win32-arm64-msvc": "1.33.0", - "lightningcss-win32-x64-msvc": "1.33.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-android-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", - "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "android" - ], + "node_modules/side-channel-list": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.4" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-darwin-arm64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", - "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-darwin-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", - "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "darwin" - ], + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-freebsd-x64": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", - "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "freebsd" - ], + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", "engines": { - "node": ">= 12.0.0" + "node": ">=0.10.0" + } + }, + "node_modules/stable-hash": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz", + "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", + "dev": true, + "license": "MIT" + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "engines": { + "node": ">= 0.4" } }, - "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", - "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", - "cpu": [ - "arm" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/string.prototype.includes": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", + "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.3" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" + } + }, + "node_modules/string.prototype.matchall": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.1.0.tgz", + "integrity": "sha512-tHNHTxInrYLCga9O9YGxWA3G9/nnzQw8UGAyqGx3Ar1pSTTzIuM4woFSq4SowkXCjJIwq5sIiQvEfRI9tCH1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.2", + "get-intrinsic": "^1.3.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "internal-slot": "^1.1.0", + "regexp.prototype.flags": "^1.5.4", + "set-function-name": "^2.0.2", + "side-channel": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", - "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/string.prototype.repeat": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz", + "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==", + "dev": true, + "license": "MIT", + "dependencies": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "node_modules/string.prototype.trim": { + "version": "1.2.11", + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", + "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-data-property": "^1.1.4", + "define-properties": "^1.2.1", + "es-abstract": "^1.24.2", + "es-object-atoms": "^1.1.2", + "has-property-descriptors": "^1.0.2", + "safe-regex-test": "^1.1.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", - "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/string.prototype.trimend": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", + "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.1.2" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", - "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/string.prototype.trimstart": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lightningcss-linux-x64-musl": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", - "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "linux" - ], + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 12.0.0" + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", - "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", - "cpu": [ - "arm64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "license": "MIT", + "dependencies": { + "client-only": "0.0.1" + }, "engines": { "node": ">= 12.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } } }, - "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", - "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", - "cpu": [ - "x64" - ], - "license": "MPL-2.0", - "optional": true, - "os": [ - "win32" - ], + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, "engines": { - "node": ">= 12.0.0" + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/parcel" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "dev": true, - "license": "MIT" + "node_modules/tailwind-merge": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz", + "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } }, - "node_modules/lodash-es": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", - "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", - "dev": true, + "node_modules/tailwindcss": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.4.tgz", + "integrity": "sha512-PCJZTFFdG+sL3gvnP1Do9o6GbkvH2q0p04xSs3f7cSvmAeK+sttG+/7ncK1fdRwKFfC7n+wDDJaOMBSefkI9ow==", "license": "MIT" }, - "node_modules/magic-string": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", - "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", + "node_modules/tailwindcss-animate": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", + "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", "license": "MIT", - "dependencies": { - "@jridgewell/sourcemap-codec": "^1.6.0" + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" } }, - "node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^2.0.2" - }, + "node_modules/tailwindcss-react-aria-components": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tailwindcss-react-aria-components/-/tailwindcss-react-aria-components-2.2.0.tgz", + "integrity": "sha512-iNVDSQd6haXWiu4RakjaYnCUnLw/nOEFSFHEelFeIkT/0f8HSrJ23fwt59F93yNw4FrjjHDXIGHgEtqProNcmQ==", + "license": "Apache-2.0", + "peerDependencies": { + "tailwindcss": "^4.0.0" + } + }, + "node_modules/tapable": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", + "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "license": "MIT", "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=6" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "type": "opencollective", + "url": "https://opencollective.com/webpack" } }, - "node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "node_modules/tinybench": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.3.tgz", + "integrity": "sha512-Al01C2JIBnO2CXCAPbpAl6sUiOZHeThGtHYNTa51JRw9PyWOCdr+9Ong05R4qg/Sk3+H645E8ov1h2c58ityNw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } }, - "node_modules/nanoid": { - "version": "3.3.20", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", - "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/tinyexec": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", + "dev": true, "license": "MIT", - "bin": { - "nanoid": "bin/nanoid.cjs" - }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=18" } }, - "node_modules/next": { - "version": "16.4.3", - "resolved": "https://registry.npmjs.org/next/-/next-16.4.3.tgz", - "integrity": "sha512-OqxKTGi9u80FydydsMNYNjnuje9Au+kvHlt2GZ//ftdpDHm1dxKqAaENoOOTX5wKzn+9vaKUNIfZeyZUuLhCnA==", + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, "license": "MIT", "dependencies": { - "@next/env": "16.4.3", - "@swc/helpers": "0.5.23", - "baseline-browser-mapping": "^2.9.19", - "caniuse-lite": "^1.0.30001579", - "postcss": "8.5.23", - "styled-jsx": "5.1.6" - }, - "bin": { - "next": "dist/bin/next" + "fdir": "^6.5.0", + "picomatch": "^4.0.4" }, "engines": { - "node": ">=20.9.0" + "node": ">=12.0.0" }, - "optionalDependencies": { - "@next/swc-darwin-arm64": "16.4.3", - "@next/swc-darwin-x64": "16.4.3", - "@next/swc-linux-arm64-gnu": "16.4.3", - "@next/swc-linux-arm64-musl": "16.4.3", - "@next/swc-linux-x64-gnu": "16.4.3", - "@next/swc-linux-x64-musl": "16.4.3", - "@next/swc-win32-arm64-msvc": "16.4.3", - "@next/swc-win32-x64-msvc": "16.4.3", - "sharp": "^0.35.5" + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" }, "peerDependencies": { - "@opentelemetry/api": "^1.1.0", - "@playwright/test": "^1.51.1", - "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", - "sass": "^1.3.0" + "picomatch": "^3 || ^4" }, "peerDependenciesMeta": { - "@opentelemetry/api": { - "optional": true - }, - "@playwright/test": { - "optional": true - }, - "babel-plugin-react-compiler": { - "optional": true - }, - "sass": { + "picomatch": { "optional": true } } }, - "node_modules/next/node_modules/@swc/helpers": { - "version": "0.5.23", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.23.tgz", - "integrity": "sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==", - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/next/node_modules/postcss": { - "version": "8.5.23", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.23.tgz", - "integrity": "sha512-g50586zr4bZmwFiTlflMu8E0bDTb5I5gertgwAKmsdUlTQIhZtunzUlD1WSzwcVWPoAVpsrA6vlfCD7oXvRwgg==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "license": "MIT", "dependencies": { - "nanoid": "^3.3.16", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "is-number": "^7.0.0" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">=8.0" } }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "node_modules/ts-api-utils": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, "engines": { - "node": ">=6" + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" } }, - "node_modules/parse-imports-exports": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", - "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", + "node_modules/tsconfig-paths": { + "version": "3.15.0", + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", "dev": true, "license": "MIT", "dependencies": { - "parse-statements": "1.0.11" + "@types/json5": "^0.0.29", + "json5": "^1.0.2", + "minimist": "^1.2.6", + "strip-bom": "^3.0.0" } }, - "node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/tsconfig-paths/node_modules/json5": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" + "minimist": "^1.2.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "bin": { + "json5": "lib/cli.js" } }, - "node_modules/parse-statements": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", - "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", - "dev": true, - "license": "MIT" - }, - "node_modules/picocolors": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", - "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "license": "ISC" + "node_modules/tslib": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz", + "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==", + "license": "0BSD" }, - "node_modules/postcss": { - "version": "8.5.32", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", - "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/postcss/" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/postcss" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, "license": "MIT", "dependencies": { - "nanoid": "^3.3.20", - "picocolors": "^1.1.1", - "source-map-js": "^1.2.2" + "prelude-ls": "^1.2.1" }, "engines": { - "node": "^10 || ^12 || >=14" + "node": ">= 0.8.0" } }, - "node_modules/postcss-loader": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/postcss-loader/-/postcss-loader-8.2.1.tgz", - "integrity": "sha512-k98jtRzthjj3f76MYTs9JTpRqV1RaaMhEU0Lpw9OTmQZQdppg4B30VZ74BojuBHt3F4KyubHJoXCMUeM8Bqeow==", + "node_modules/typed-array-buffer": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, "license": "MIT", "dependencies": { - "cosmiconfig": "^9.0.0", - "jiti": "^2.5.1", - "semver": "^7.6.2" + "call-bound": "^1.0.3", + "es-errors": "^1.3.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">= 18.12.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependencies": { - "@rspack/core": "0.x || ^1.0.0 || ^2.0.0-0", - "postcss": "^7.0.0 || ^8.0.1", - "webpack": "^5.0.0" - }, - "peerDependenciesMeta": { - "@rspack/core": { - "optional": true - }, - "webpack": { - "optional": true - } + "node": ">= 0.4" } }, - "node_modules/prettier": { - "version": "3.9.10", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.9.10.tgz", - "integrity": "sha512-skGHTH1Yb9QvaTC4hWnRTzEYtCpApF0UMBPz+t/0ZPzP8ihefo/btKd+QA7pUE0fEwA1SbS/NmhyEcstdXOIdA==", + "node_modules/typed-array-byte-length": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", "dev": true, "license": "MIT", - "bin": { - "prettier": "bin/prettier.cjs" + "dependencies": { + "call-bind": "^1.0.8", + "for-each": "^0.3.3", + "gopd": "^1.2.0", + "has-proto": "^1.2.0", + "is-typed-array": "^1.1.14" }, "engines": { - "node": ">=14" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/prettier/prettier?sponsor=1" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/prettier-plugin-tailwindcss": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.8.1.tgz", - "integrity": "sha512-iaFMYqDsE4ffdDkn5qup0j5f2aCEBFZrdrZnvu9QKTlWx/iGPeQ4HHu7b7fCPMxeo9nwQBiOAh2nSypdFYWJkw==", + "node_modules/typed-array-byte-offset": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.5.tgz", + "integrity": "sha512-0FHJvLPqZ7KJzp17O13jfsAjsqazgrxBu2zEK95PmUz8lv2+GjRuxUInCr2Rk9Dms3ihN21zJ929ZO43yJ95QQ==", "dev": true, "license": "MIT", - "engines": { - "node": ">=20.19" + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "reflect.getprototypeof": "^1.0.10" }, - "peerDependencies": { - "@ianvs/prettier-plugin-sort-imports": "*", - "@prettier/plugin-hermes": "*", - "@prettier/plugin-oxc": "*", - "@prettier/plugin-pug": "*", - "@shopify/prettier-plugin-liquid": "*", - "@trivago/prettier-plugin-sort-imports": "*", - "@zackad/prettier-plugin-twig": "*", - "prettier": "^3.0", - "prettier-plugin-astro": "*", - "prettier-plugin-css-order": "*", - "prettier-plugin-jsdoc": "*", - "prettier-plugin-marko": "*", - "prettier-plugin-multiline-arrays": "*", - "prettier-plugin-organize-attributes": "*", - "prettier-plugin-organize-imports": "*", - "prettier-plugin-sort-imports": "*", - "prettier-plugin-svelte": "*" + "engines": { + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "@ianvs/prettier-plugin-sort-imports": { - "optional": true - }, - "@prettier/plugin-hermes": { - "optional": true - }, - "@prettier/plugin-oxc": { - "optional": true - }, - "@prettier/plugin-pug": { - "optional": true - }, - "@shopify/prettier-plugin-liquid": { - "optional": true - }, - "@trivago/prettier-plugin-sort-imports": { - "optional": true - }, - "@zackad/prettier-plugin-twig": { - "optional": true - }, - "prettier-plugin-astro": { - "optional": true - }, - "prettier-plugin-css-order": { - "optional": true - }, - "prettier-plugin-jsdoc": { - "optional": true - }, - "prettier-plugin-marko": { - "optional": true - }, - "prettier-plugin-multiline-arrays": { - "optional": true - }, - "prettier-plugin-organize-attributes": { - "optional": true - }, - "prettier-plugin-organize-imports": { - "optional": true - }, - "prettier-plugin-sort-imports": { - "optional": true - }, - "prettier-plugin-svelte": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react": { - "version": "19.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-19.3.1.tgz", - "integrity": "sha512-QNhWdt7nrgVWWQG58QuGsAR3jv3Hbe4mb4vz7w2csnHavciZJgda1CPNaldF6eVxv90ud3jUXpKlHL69gO38yw==", + "node_modules/typed-array-length": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", + "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", + "dev": true, "license": "MIT", + "dependencies": { + "call-bind": "^1.0.9", + "for-each": "^0.3.5", + "gopd": "^1.2.0", + "is-typed-array": "^1.1.15", + "possible-typed-array-names": "^1.1.0", + "reflect.getprototypeof": "^1.0.10" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react-aria": { - "version": "3.53.1", - "resolved": "https://registry.npmjs.org/react-aria/-/react-aria-3.53.1.tgz", - "integrity": "sha512-dikisyOjJd3D2fFrIDWmVWzqiLz5B86GzZv1zxs7xI4dVc0JIz/1Wa52I20gYvVL2WXeFXlygYsle/QcOMhtcg==", + "node_modules/typescript": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", + "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "dev": true, "license": "Apache-2.0", - "dependencies": { - "@internationalized/date": "^3.12.4", - "@internationalized/number": "^3.6.9", - "@internationalized/string": "^3.2.10", - "@react-types/shared": "^3.37.0", - "@swc/helpers": "^0.5.0", - "aria-hidden": "^1.2.3", - "clsx": "^2.0.0", - "react-stately": "3.51.0", - "use-sync-external-store": "^1.6.0" + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + "engines": { + "node": ">=14.17" } }, - "node_modules/react-aria-components": { - "version": "1.22.1", - "resolved": "https://registry.npmjs.org/react-aria-components/-/react-aria-components-1.22.1.tgz", - "integrity": "sha512-UTHaYHOaM71bOCjGPF50DcLywlrwAodE63IP64LB4kO+OizatQEL8TesVEOM8+TkBeSgHoflJ45388IlJfa7aw==", - "license": "Apache-2.0", + "node_modules/typescript-eslint": { + "version": "8.72.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.72.1.tgz", + "integrity": "sha512-3Li9FfPmQp45nxEtFD1ZCrYFI3cYK5kpyNtmm1H7lMagm8u9sy6PTjy3ytwQZs7KNukOyKFHAZ6z6GjYFeaeXw==", + "dev": true, + "license": "MIT", "dependencies": { - "@internationalized/date": "^3.12.4", - "@internationalized/string": "^3.2.10", - "@react-types/shared": "^3.37.0", - "@swc/helpers": "^0.5.0", - "client-only": "^0.0.1", - "react-aria": "3.53.1", - "react-stately": "3.51.0", - "use-sync-external-store": "^1.6.0" + "@typescript-eslint/eslint-plugin": "8.72.1", + "@typescript-eslint/parser": "8.72.1", + "@typescript-eslint/typescript-estree": "8.72.1", + "@typescript-eslint/utils": "8.72.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1", - "react-dom": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/react-dom": { - "version": "19.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.1.tgz", - "integrity": "sha512-Y0bARz53lhEC1NMCASs0/j+faB/zqt3R8hpvg1e8+61ZVJ4UL0UdC4cBbXfZMcIuhrfgeILTLe+ZeBCavQPtKQ==", + "node_modules/unbox-primitive": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", + "dev": true, "license": "MIT", "dependencies": { - "scheduler": "^0.28.0" + "call-bound": "^1.0.3", + "has-bigints": "^1.0.2", + "has-symbols": "^1.1.0", + "which-boxed-primitive": "^1.1.1" }, - "peerDependencies": { - "react": "^19.3.1" + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/react-stately": { - "version": "3.51.0", - "resolved": "https://registry.npmjs.org/react-stately/-/react-stately-3.51.0.tgz", - "integrity": "sha512-YJcmkAXu2XOQP97hO/qz/cC3qv9kni3oVhwBlxWr358BHePJUpY15XwL3YSRMKxYt4EkslPqAz11JRirMfANGw==", - "license": "Apache-2.0", + "node_modules/undici-types": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", + "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "dev": true, + "license": "MIT" + }, + "node_modules/unrs-resolver": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.13.0.tgz", + "integrity": "sha512-mcPQo9Vm6kXd84d/WqMiYBi2CcA57QHBX/ev5PoUEb8b2oQzUvJ6xYsfseQblnCncgnVmCZGWiFwK7KkS5JZJA==", + "dev": true, + "license": "MIT", "dependencies": { - "@internationalized/date": "^3.12.4", - "@internationalized/number": "^3.6.9", - "@internationalized/string": "^3.2.10", - "@react-types/shared": "^3.37.0", - "@swc/helpers": "^0.5.0", - "use-sync-external-store": "^1.6.0" + "napi-postinstall": "^0.3.7" }, - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0 || ^19.0.0-rc.1" - } - }, - "node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.13.0", + "@unrs/resolver-binding-android-arm64": "1.13.0", + "@unrs/resolver-binding-darwin-arm64": "1.13.0", + "@unrs/resolver-binding-darwin-x64": "1.13.0", + "@unrs/resolver-binding-freebsd-x64": "1.13.0", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.13.0", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.13.0", + "@unrs/resolver-binding-linux-arm64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-arm64-musl": "1.13.0", + "@unrs/resolver-binding-linux-loong64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-loong64-musl": "1.13.0", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-riscv64-musl": "1.13.0", + "@unrs/resolver-binding-linux-s390x-gnu": "1.13.0", + "@unrs/resolver-binding-linux-x64-gnu": "1.13.0", + "@unrs/resolver-binding-linux-x64-musl": "1.13.0", + "@unrs/resolver-binding-openharmony-arm64": "1.13.0", + "@unrs/resolver-binding-win32-arm64-msvc": "1.13.0", + "@unrs/resolver-binding-win32-ia32-msvc": "1.13.0", + "@unrs/resolver-binding-win32-x64-msvc": "1.13.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.4.tgz", + "integrity": "sha512-t8wonIOdQyOjtPd2VktlBu6QjTVTks6v8bSi7kd0r2Vll8TK1JYDZtRRdbmCHOAMphkZgrzK+L+vXK4pyZ8t4A==", "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "license": "MIT", - "engines": { - "node": ">=4" + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/scheduler": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", - "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==", - "license": "MIT" + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } }, - "node_modules/semver": { - "version": "7.8.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.6.tgz", - "integrity": "sha512-9lpSSU4Mly3mrtruwLs1eLK6+gzbzqeBFwRNjIjE+9LuDpWdYctrQlQ3ej1Ilgi7gknpsTRnPoLP8DpCHqHIJw==", - "devOptional": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" + "node_modules/use-sync-external-store": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", + "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/sharp": { - "version": "0.35.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.5.tgz", - "integrity": "sha512-Ywn4OnzGukp7CDMrp08RQ50YKmuwG47brZgIVPTvBaaAfQlRlygrRqSrxdCiL9M+LlzLBiJ68IR1QqvzHyjC7g==", - "license": "Apache-2.0", - "optional": true, + "node_modules/vite": { + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", + "dev": true, + "license": "MIT", + "peer": true, "dependencies": { - "@img/colour": "^1.1.0", - "detect-libc": "^2.1.2", - "semver": "^7.8.5" + "lightningcss": "^1.33.0", + "picomatch": "^4.0.7", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", + "tinyglobby": "^0.2.17" + }, + "bin": { + "vite": "bin/vite.js" }, "engines": { - "node": ">=20.9.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://opencollective.com/libvips" + "url": "https://github.com/vitejs/vite?sponsor=1" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.35.5", - "@img/sharp-darwin-x64": "0.35.5", - "@img/sharp-freebsd-wasm32": "0.35.5", - "@img/sharp-libvips-darwin-arm64": "1.3.4", - "@img/sharp-libvips-darwin-x64": "1.3.4", - "@img/sharp-libvips-linux-arm": "1.3.4", - "@img/sharp-libvips-linux-arm64": "1.3.4", - "@img/sharp-libvips-linux-ppc64": "1.3.4", - "@img/sharp-libvips-linux-riscv64": "1.3.4", - "@img/sharp-libvips-linux-s390x": "1.3.4", - "@img/sharp-libvips-linux-x64": "1.3.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.3.4", - "@img/sharp-libvips-linuxmusl-x64": "1.3.4", - "@img/sharp-linux-arm": "0.35.5", - "@img/sharp-linux-arm64": "0.35.5", - "@img/sharp-linux-ppc64": "0.35.5", - "@img/sharp-linux-riscv64": "0.35.5", - "@img/sharp-linux-s390x": "0.35.5", - "@img/sharp-linux-x64": "0.35.5", - "@img/sharp-linuxmusl-arm64": "0.35.5", - "@img/sharp-linuxmusl-x64": "0.35.5", - "@img/sharp-webcontainers-wasm32": "0.35.5", - "@img/sharp-win32-arm64": "0.35.5", - "@img/sharp-win32-ia32": "0.35.5", - "@img/sharp-win32-x64": "0.35.5" + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", + "esbuild": "^0.27.0 || ^0.28.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true + }, + "@vitejs/devtools": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, - "node_modules/source-map-js": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", - "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", - "license": "BSD-3-Clause", + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "peer": true, "engines": { - "node": ">=0.10.0" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/styled-jsx": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", - "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "node_modules/vitest": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.1.0.tgz", + "integrity": "sha512-MzmNFnrwa8v8lF1bLxw3UJ4EREwVOVb75VqJZmUDRjJ6EluK/x9th7qTbaeDk41WxE1GxWqmTXcdsfobfH5sOA==", + "dev": true, "license": "MIT", "dependencies": { - "client-only": "0.0.1" + "@types/chai": "^5.2.2", + "@vitest/mocker": "5.1.0", + "chai": "^6.3.0", + "es-module-lexer": "^2.3.2", + "expect-type": "^1.4.0", + "magic-string": "^1.4.3", + "obug": "^3.0.0", + "picomatch": "^4.0.7", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", + "tinyglobby": "^0.2.17", + "why-is-node-running": "3.2.1" + }, + "bin": { + "vitest": "vitest.mjs" }, "engines": { - "node": ">= 12.0.0" + "node": "^22.12.0 || ^24.0.0 || >=26.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + "@edge-runtime/vm": "*", + "@opentelemetry/api": "^1.9.0", + "@types/node": "^22.0.0 || >=24.0.0", + "@vitest/browser-playwright": "5.1.0", + "@vitest/browser-preview": "5.1.0", + "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", + "@vitest/coverage-istanbul": "5.1.0", + "@vitest/coverage-v8": "5.1.0", + "@vitest/ui": "5.1.0", + "happy-dom": "*", + "jsdom": "*", + "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { - "@babel/core": { + "@edge-runtime/vm": { "optional": true }, - "babel-plugin-macros": { + "@opentelemetry/api": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser-playwright": { + "optional": true + }, + "@vitest/browser-preview": { + "optional": true + }, + "@vitest/browser-webdriverio": { + "optional": true + }, + "@vitest/coverage-istanbul": { + "optional": true + }, + "@vitest/coverage-v8": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { "optional": true + }, + "vite": { + "optional": false } } }, - "node_modules/tailwind-merge": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.7.0.tgz", - "integrity": "sha512-XPPUyAc+cvspz3lHTcR/QgPfW2A0lv/xQNIjX3HGhLR+Nq2lHaLq5MtTesHn8GUr3W3DguT2KT5x3NVgRtYwmA==", + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, "license": "MIT", + "engines": { + "node": ">=12" + }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/dcastil" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tailwindcss": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.3.4.tgz", - "integrity": "sha512-PCJZTFFdG+sL3gvnP1Do9o6GbkvH2q0p04xSs3f7cSvmAeK+sttG+/7ncK1fdRwKFfC7n+wDDJaOMBSefkI9ow==", - "license": "MIT" + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/tailwindcss-animate": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/tailwindcss-animate/-/tailwindcss-animate-1.0.7.tgz", - "integrity": "sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==", + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "dev": true, "license": "MIT", - "peerDependencies": { - "tailwindcss": ">=3.0.0 || insiders" + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tailwindcss-react-aria-components": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tailwindcss-react-aria-components/-/tailwindcss-react-aria-components-2.2.0.tgz", - "integrity": "sha512-iNVDSQd6haXWiu4RakjaYnCUnLw/nOEFSFHEelFeIkT/0f8HSrJ23fwt59F93yNw4FrjjHDXIGHgEtqProNcmQ==", - "license": "Apache-2.0", - "peerDependencies": { - "tailwindcss": "^4.0.0" + "node_modules/which-builtin-type": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "function.prototype.name": "^1.1.6", + "has-tostringtag": "^1.0.2", + "is-async-function": "^2.0.0", + "is-date-object": "^1.1.0", + "is-finalizationregistry": "^1.1.0", + "is-generator-function": "^1.0.10", + "is-regex": "^1.2.1", + "is-weakref": "^1.0.2", + "isarray": "^2.0.5", + "which-boxed-primitive": "^1.1.0", + "which-collection": "^1.0.2", + "which-typed-array": "^1.1.16" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tapable": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", - "integrity": "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==", + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "dev": true, "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, "engines": { - "node": ">=6" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/tslib": { - "version": "2.9.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.9.1.tgz", - "integrity": "sha512-a4Ux9VKuIMHsOS7ENbEq6hK2ZGYIT27Rtl0E4eD9KqwxhKiQeMZE/9xb3IdYwWaSOyIBQLExhQgXDTYoiCxnWA==", - "license": "0BSD" + "node_modules/which-typed-array": { + "version": "1.1.24", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.24.tgz", + "integrity": "sha512-wk4Mf4pR5mRP7eYuuTBCIQ9d0ud2Fv2jRLQpfgnRjbOxAFHmjKFValgTpitVKzJJS8ajnYQV2Du1SZ8j6b/EUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.9", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/typescript": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", - "integrity": "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==", + "node_modules/why-is-node-running": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" + "why-is-node-running": "cli.js" }, "engines": { - "node": ">=14.17" + "node": ">=20.11" } }, - "node_modules/undici-types": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.9.0.tgz", - "integrity": "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==", + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/use-sync-external-store": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.7.0.tgz", - "integrity": "sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==", + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zod": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", + "dev": true, "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + "zod": "^3.25.0 || ^4.0.0" } } } diff --git a/package.json b/package.json index e3bc5b2..52eb21a 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,8 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", + "test": "vitest run", + "lint": "eslint .", "typecheck": "tsc --noEmit", "format": "prettier --write ." }, @@ -39,11 +41,15 @@ "@types/node": "^26.6.3", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", + "eslint": "^9.39.5", + "eslint-config-next": "^16.4.3", + "eslint-config-prettier": "^10.1.8", "postcss": "^8.5.28", "postcss-loader": "^8.2.1", "prettier": "^3.9.9", "prettier-plugin-tailwindcss": "^0.8.1", - "typescript": "^6.0.3" + "typescript": "^6.0.3", + "vitest": "^5.1.0" }, "description": "Draw and export simple contour maps in the browser." } diff --git a/postcss.config.mjs b/postcss.config.mjs index 78253b6..148a80b 100644 --- a/postcss.config.mjs +++ b/postcss.config.mjs @@ -1,5 +1,7 @@ -export default { +const config = { plugins: { "@tailwindcss/postcss": {}, }, }; + +export default config; diff --git a/src/components/base/avatar/avatar.tsx b/src/components/base/avatar/avatar.tsx index f4d6033..1927022 100644 --- a/src/components/base/avatar/avatar.tsx +++ b/src/components/base/avatar/avatar.tsx @@ -112,6 +112,8 @@ export const Avatar = ({ const renderMainContent = () => { if (canShowImage) { + // Avatars come from arbitrary URLs, which next/image can't optimize without a remote pattern per host. + // eslint-disable-next-line @next/next/no-img-element return {alt} setIsFailed(true)} />; } diff --git a/src/components/base/avatar/base-components/avatar-company-icon.tsx b/src/components/base/avatar/base-components/avatar-company-icon.tsx index be7cade..8f294fe 100644 --- a/src/components/base/avatar/base-components/avatar-company-icon.tsx +++ b/src/components/base/avatar/base-components/avatar-company-icon.tsx @@ -18,6 +18,8 @@ interface AvatarCompanyIconProps { } export const AvatarCompanyIcon = ({ size, src, alt }: AvatarCompanyIconProps) => ( + // Company icons come from arbitrary URLs, which next/image can't optimize without a remote pattern per host. + // eslint-disable-next-line @next/next/no-img-element {alt}> {} +type TextFieldContextProps = Partial>; const TextFieldContext = createContext({}); diff --git a/src/components/contours/color-field.tsx b/src/components/contours/color-field.tsx index e5e68a4..8484718 100644 --- a/src/components/contours/color-field.tsx +++ b/src/components/contours/color-field.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Button as AriaButton, ColorSwatch as AriaColorSwatch, @@ -28,10 +28,13 @@ interface ColorFieldProps { */ export const ColorField = ({ label, value, onChange, className }: ColorFieldProps) => { const [draft, setDraft] = useState(value); + const [lastValue, setLastValue] = useState(value); - useEffect(() => { - setDraft((current) => (normalizeHex(current) === value ? current : value)); - }, [value]); + // Adopt a new value from outside (the picker, or a reset) unless the draft already spells it. + if (value !== lastValue) { + setLastValue(value); + if (normalizeHex(draft) !== value) setDraft(value); + } const swatch = ( diff --git a/src/components/contours/number-field.tsx b/src/components/contours/number-field.tsx index b5ed71c..f8eaf60 100644 --- a/src/components/contours/number-field.tsx +++ b/src/components/contours/number-field.tsx @@ -1,6 +1,6 @@ "use client"; -import { useEffect, useState } from "react"; +import { useState } from "react"; import { Input, InputBase } from "@/components/base/input/input"; import { InputGroup } from "@/components/base/input/input-group"; @@ -22,10 +22,13 @@ interface NumberFieldProps { */ export const NumberField = ({ label, value, onChange, min = 0, suffix, isDisabled, className }: NumberFieldProps) => { const [draft, setDraft] = useState(String(value)); + const [lastValue, setLastValue] = useState(value); - useEffect(() => { - setDraft((current) => (Number(current) === value ? current : String(value))); - }, [value]); + // Adopt a new value from outside unless the draft already parses to it (so "1." isn't clobbered mid-keystroke). + if (value !== lastValue) { + setLastValue(value); + if (Number(draft) !== value) setDraft(String(value)); + } const parse = (text: string) => { const n = Number(text.trim()); diff --git a/src/components/contours/place-search.tsx b/src/components/contours/place-search.tsx index e3f1a37..27b812b 100644 --- a/src/components/contours/place-search.tsx +++ b/src/components/contours/place-search.tsx @@ -20,13 +20,17 @@ export const PlaceSearch = ({ onSelect }: PlaceSearchProps) => { const [isOpen, setIsOpen] = useState(false); const triggerRef = useRef(null); - useEffect(() => { - const trimmed = query.trim(); - if (trimmed.length < 2 || !MAPBOX_TOKEN) { + const updateQuery = (text: string) => { + setQuery(text); + if (text.trim().length < 2) { setResults([]); setIsOpen(false); - return; } + }; + + useEffect(() => { + const trimmed = query.trim(); + if (trimmed.length < 2 || !MAPBOX_TOKEN) return; const controller = new AbortController(); const timer = window.setTimeout(async () => { try { @@ -62,7 +66,7 @@ export const PlaceSearch = ({ onSelect }: PlaceSearchProps) => { aria-label="Search for a place" items={results} inputValue={query} - onInputChange={setQuery} + onInputChange={updateQuery} selectedKey={null} onSelectionChange={(key) => choose(results.find((r) => r.id === key))} allowsEmptyCollection diff --git a/src/hooks/use-contour-map.ts b/src/hooks/use-contour-map.ts index 34d0e22..3440bd4 100644 --- a/src/hooks/use-contour-map.ts +++ b/src/hooks/use-contour-map.ts @@ -148,7 +148,9 @@ export function useContourMap(settings: ContourSettings): ContourMap { }; // Keep the latest settings reachable from the scheduled callbacks. - settingsRef.current = settings; + useEffect(() => { + settingsRef.current = settings; + }, [settings]); // Create the map once. useEffect(() => { diff --git a/src/lib/contours/__tests__/color.test.ts b/src/lib/contours/__tests__/color.test.ts new file mode 100644 index 0000000..cb62e91 --- /dev/null +++ b/src/lib/contours/__tests__/color.test.ts @@ -0,0 +1,51 @@ +import { describe, expect, it } from "vitest"; +import { hexWithAlpha, makeBathyScale, makeHypsoScale, normalizeHex } from "../color"; + +describe("normalizeHex", () => { + it("expands short hex and lowercases", () => { + expect(normalizeHex("#abc")).toBe("#aabbcc"); + expect(normalizeHex("ABC")).toBe("#aabbcc"); + expect(normalizeHex("#AABBCC")).toBe("#aabbcc"); + expect(normalizeHex("aabbcc")).toBe("#aabbcc"); + }); + + it("tolerates surrounding whitespace", () => { + expect(normalizeHex(" #8c7556 ")).toBe("#8c7556"); + }); + + it("rejects anything that is not a 3 or 6 digit hex color", () => { + for (const bad of ["", "#", "#ab", "#abcd", "#abcde", "#aabbccdd", "#ggg", "red", "rgb(1,2,3)", "# abc"]) { + expect(normalizeHex(bad), bad).toBeNull(); + } + }); +}); + +describe("hexWithAlpha", () => { + it("converts to rgba with the given alpha", () => { + expect(hexWithAlpha("#ff8000", 0.5)).toBe("rgba(255,128,0,0.5)"); + expect(hexWithAlpha("#000000", 1)).toBe("rgba(0,0,0,1)"); + }); + + it("accepts the same loose input as normalizeHex", () => { + expect(hexWithAlpha("abc", 0.25)).toBe("rgba(170,187,204,0.25)"); + }); + + it("falls back to black for invalid input", () => { + expect(hexWithAlpha("nope", 0.3)).toBe("rgba(0,0,0,0.3)"); + }); +}); + +describe("color scales", () => { + it("hypso scale returns the end colors at the domain ends", () => { + const scale = makeHypsoScale("#79a483", "#ffdecb", [0, 400]); + expect(scale.domain()).toEqual([0, 400]); + expect(scale(0)).toBe("rgb(121, 164, 131)"); + expect(scale(400)).toBe("rgb(255, 222, 203)"); + }); + + it("bathy scale returns the end colors at the domain ends", () => { + const scale = makeBathyScale("#315d9b", "#d5f2ff", [-200, -1]); + expect(scale(-200)).toBe("rgb(49, 93, 155)"); + expect(scale(-1)).toBe("rgb(213, 242, 255)"); + }); +}); diff --git a/src/lib/contours/__tests__/draw.test.ts b/src/lib/contours/__tests__/draw.test.ts new file mode 100644 index 0000000..fe13350 --- /dev/null +++ b/src/lib/contours/__tests__/draw.test.ts @@ -0,0 +1,159 @@ +import { describe, expect, it } from "vitest"; +import { hexWithAlpha } from "../color"; +import { contoursToSvg, fillFor, hasBathymetry, resolveStyle } from "../draw"; +import { makeData, makeSettings } from "./fixtures"; + +const land = makeData([0, 100, 200, 300, 400]); +const coast = makeData([-200, -100, 0, 100, 200, 300, 400]); + +describe("hasBathymetry", () => { + it("is true only when the lowest contour is below sea level", () => { + expect(hasBathymetry(land)).toBe(false); + expect(hasBathymetry(coast)).toBe(true); + expect(hasBathymetry(makeData([0, 100]))).toBe(false); + }); +}); + +describe("resolveStyle", () => { + it("spans the whole elevation range with the hypsometric scale by default", () => { + const style = resolveStyle(coast, makeSettings({ background: "hypso", bathymetry: "none" })); + expect(style.bathymetry).toBe("none"); + expect(style.hypso.domain()).toEqual([-200, 400]); + }); + + it("starts the hypsometric scale at sea level when bathymetry is on and the view dips below it", () => { + for (const bathymetry of ["solid", "bathy"] as const) { + const style = resolveStyle(coast, makeSettings({ background: "hypso", bathymetry })); + expect(style.bathymetry).toBe(bathymetry); + expect(style.hypso.domain()).toEqual([0, 400]); + expect(style.bathy.domain()).toEqual([-200, -1]); + } + }); + + it("ignores the bathymetry setting when nothing is below sea level", () => { + const style = resolveStyle(land, makeSettings({ background: "hypso", bathymetry: "bathy" })); + expect(style.bathymetry).toBe("none"); + expect(style.hypso.domain()).toEqual([0, 400]); + }); + + it("derives the index interval from the settings in lines mode only", () => { + expect(resolveStyle(land, makeSettings({ mapType: "lines", indexEvery: 5, interval: 100 })).indexInterval).toBe(500); + expect(resolveStyle(land, makeSettings({ mapType: "lines", indexEvery: 0, interval: 100 })).indexInterval).toBe(0); + expect(resolveStyle(land, makeSettings({ mapType: "illuminated", indexEvery: 5, interval: 100 })).indexInterval).toBe(0); + }); + + it("draws the illuminated highlight at half alpha", () => { + const style = resolveStyle(land, makeSettings({ highlightColor: "#fffbec" })); + expect(style.highlight).toBe(hexWithAlpha("#fffbec", 0.5)); + expect(style.highlight).toBe("rgba(255,251,236,0.5)"); + }); +}); + +describe("fillFor", () => { + it("returns no fill without a background", () => { + const style = resolveStyle(land, makeSettings({ background: "none", bathymetry: "none" })); + expect(fillFor(0, style)).toBeNull(); + expect(fillFor(400, style)).toBeNull(); + }); + + it("uses the solid color for a solid background", () => { + const style = resolveStyle(land, makeSettings({ background: "solid", solidColor: "#fffcfa" })); + expect(fillFor(0, style)).toBe("#fffcfa"); + expect(fillFor(400, style)).toBe("#fffcfa"); + }); + + it("uses the hypsometric scale for a hypso background", () => { + const style = resolveStyle(land, makeSettings({ background: "hypso", hypsoLowColor: "#79a483", hypsoHighColor: "#ffdecb" })); + expect(fillFor(0, style)).toBe("rgb(121, 164, 131)"); + expect(fillFor(400, style)).toBe("rgb(255, 222, 203)"); + expect(fillFor(200, style)).toBe(style.hypso(200)); + }); + + it("fills below sea level with the ocean color or the bathymetric scale", () => { + const solid = resolveStyle(coast, makeSettings({ background: "none", bathymetry: "solid", oceanColor: "#d5f2ff" })); + expect(fillFor(-100, solid)).toBe("#d5f2ff"); + + const bathy = resolveStyle(coast, makeSettings({ background: "none", bathymetry: "bathy", bathyDeepColor: "#315d9b", bathyShallowColor: "#d5f2ff" })); + expect(fillFor(-200, bathy)).toBe("rgb(49, 93, 155)"); + expect(fillFor(-1, bathy)).toBe("rgb(213, 242, 255)"); + expect(fillFor(-100, bathy)).toBe(bathy.bathy(-100)); + }); + + it("masks land with white when the sea floor is colored but the land is not", () => { + const style = resolveStyle(coast, makeSettings({ background: "none", bathymetry: "solid" })); + expect(fillFor(0, style)).toBe("#fff"); + expect(fillFor(300, style)).toBe("#fff"); + + const withLand = resolveStyle(coast, makeSettings({ background: "solid", solidColor: "#eeeeee", bathymetry: "solid" })); + expect(fillFor(0, withLand)).toBe("#eeeeee"); + }); + + it("only applies underwater fills when the data has negative values", () => { + const style = resolveStyle(land, makeSettings({ background: "none", bathymetry: "solid", oceanColor: "#d5f2ff" })); + expect(fillFor(-50, style)).toBeNull(); + expect(fillFor(0, style)).toBeNull(); + + const solidLand = resolveStyle(land, makeSettings({ background: "solid", solidColor: "#fffcfa", bathymetry: "bathy" })); + expect(fillFor(-50, solidLand)).toBe("#fffcfa"); + }); +}); + +describe("contoursToSvg", () => { + const paths = (svg: string) => svg.match(/]*\/>/g) ?? []; + const attr = (path: string | undefined, name: string) => (path === undefined ? undefined : new RegExp(` ${name}="([^"]*)"`).exec(path)?.[1]); + + it("crops the viewport by the buffer and translates the paths back", () => { + const svg = contoursToSvg(land, makeSettings(), 5); + expect(svg.startsWith('')).toBe(true); + expect(svg).toContain(''); + expect(svg.trimEnd().endsWith("\n")).toBe(true); + }); + + it("writes one path per contour with an elev- id", () => { + const svg = contoursToSvg(coast, makeSettings(), 5); + const found = paths(svg); + expect(found).toHaveLength(coast.contours.length); + expect(found.map((p) => attr(p, "id"))).toEqual(["elev--200", "elev--100", "elev-0", "elev-100", "elev-200", "elev-300", "elev-400"]); + expect(attr(found[0], "d")).toBe("M0,0L10,0L10,10L0,10Z"); + }); + + it("uses the index line width on index contours in lines mode", () => { + const svg = contoursToSvg( + land, + makeSettings({ mapType: "lines", indexEvery: 2, interval: 100, lineWidth: 0.75, indexLineWidth: 1.5, lineColor: "#8c7556" }), + 5, + ); + const widths = Object.fromEntries(paths(svg).map((p) => [attr(p, "id"), attr(p, "stroke-width")])); + expect(widths).toEqual({ "elev-0": "1.5", "elev-100": "0.75", "elev-200": "1.5", "elev-300": "0.75", "elev-400": "1.5" }); + for (const p of paths(svg)) { + expect(attr(p, "stroke")).toBe("#8c7556"); + expect(attr(p, "fill")).toBe("none"); + } + }); + + it("uses the shadow width and translucent highlight in illuminated mode", () => { + const svg = contoursToSvg(land, makeSettings({ mapType: "illuminated", indexEvery: 2, shadowWidth: 3, highlightColor: "#fffbec" }), 5); + for (const p of paths(svg)) { + expect(attr(p, "stroke-width")).toBe("3"); + expect(attr(p, "stroke")).toBe("rgba(255,251,236,0.5)"); + } + }); + + it("writes the resolved fill and escapes attribute values", () => { + const svg = contoursToSvg( + coast, + makeSettings({ background: "solid", solidColor: "#fffcfa", bathymetry: "solid", oceanColor: "#d5f2ff", lineColor: 'a" [attr(p, "id"), attr(p, "fill")])); + expect(fills["elev--200"]).toBe("#d5f2ff"); + expect(fills["elev-0"]).toBe("#fffcfa"); + expect(attr(paths(svg)[0], "stroke")).toBe("a"<b&c"); + }); + + it("drops the buffer from the width and height", () => { + const svg = contoursToSvg(makeData([100], { width: 20, height: 20 }), makeSettings(), 0); + expect(svg).toContain('width="20" height="20" viewBox="0 0 20 20"'); + expect(svg).toContain("translate(0 0)"); + }); +}); diff --git a/src/lib/contours/__tests__/elevation.test.ts b/src/lib/contours/__tests__/elevation.test.ts new file mode 100644 index 0000000..27c8c1b --- /dev/null +++ b/src/lib/contours/__tests__/elevation.test.ts @@ -0,0 +1,68 @@ +import { describe, expect, it } from "vitest"; +import { decodeElevationGrid, terrariumToMeters } from "../elevation"; + +describe("terrariumToMeters", () => { + it("decodes sea level and the extremes of the encoding", () => { + expect(terrariumToMeters(128, 0, 0)).toBe(0); + expect(terrariumToMeters(0, 0, 0)).toBe(-32768); + expect(terrariumToMeters(255, 255, 255)).toBeCloseTo(32767.996, 3); + }); + + it("weights the channels as red * 256 + green + blue / 256", () => { + expect(terrariumToMeters(128, 1, 0)).toBe(1); + expect(terrariumToMeters(128, 0, 128)).toBe(0.5); + expect(terrariumToMeters(129, 0, 0)).toBe(256); + // Everest: 8848 m = 41616 above the -32768 offset = 162 * 256 + 144. + expect(terrariumToMeters(162, 144, 0)).toBe(8848); + }); +}); + +/** Build RGBA pixel data for a row-major list of (r, g, b) triples. */ +const rgba = (triples: Array<[number, number, number]>): Uint8ClampedArray => { + const pixels = new Uint8ClampedArray(triples.length * 4); + triples.forEach(([r, g, b], i) => { + pixels.set([r, g, b, 255 - i], i * 4); + }); + return pixels; +}; + +describe("decodeElevationGrid", () => { + // A 2 × 2 grid: 0.5 m, 10 m on the first row, -10 m and 8848 m on the second. + const pixels = rgba([ + [128, 0, 128], + [128, 10, 0], + [127, 246, 0], + [162, 144, 0], + ]); + + it("returns a row-major Int32Array of rounded metres", () => { + const grid = decodeElevationGrid(pixels, 2, 2, "m"); + expect(grid).toBeInstanceOf(Int32Array); + expect(grid).toHaveLength(4); + expect(Array.from(grid)).toEqual([1, 10, -10, 8848]); + }); + + it("converts to feet before rounding", () => { + const grid = decodeElevationGrid(pixels, 2, 2, "ft"); + expect(Array.from(grid)).toEqual([2, 33, -33, 29029]); + }); + + it("ignores the alpha channel and lays rows out as x + y * width", () => { + const wide = rgba([ + [128, 1, 0], + [128, 2, 0], + [128, 3, 0], + [128, 4, 0], + [128, 5, 0], + [128, 6, 0], + ]); + const grid = decodeElevationGrid(wide, 3, 2, "m"); + expect(grid[0 + 1 * 3]).toBe(4); + expect(grid[2 + 1 * 3]).toBe(6); + expect(Array.from(grid)).toEqual([1, 2, 3, 4, 5, 6]); + }); + + it("returns an empty grid for an empty canvas", () => { + expect(decodeElevationGrid(new Uint8ClampedArray(0), 0, 0, "m")).toHaveLength(0); + }); +}); diff --git a/src/lib/contours/__tests__/export.test.ts b/src/lib/contours/__tests__/export.test.ts new file mode 100644 index 0000000..bfed969 --- /dev/null +++ b/src/lib/contours/__tests__/export.test.ts @@ -0,0 +1,58 @@ +import { describe, expect, it, vi } from "vitest"; +import { contoursToGeoJson } from "../export"; +import { makeData, square } from "./fixtures"; + +describe("contoursToGeoJson", () => { + const toLngLat = (x: number, y: number): [number, number] => [x / 10, 5 - y / 10]; + + it("builds a FeatureCollection with one MultiPolygon feature per contour", () => { + const data = makeData([100, 200]); + const geojson = contoursToGeoJson(data, toLngLat); + expect(geojson.type).toBe("FeatureCollection"); + expect(geojson.features).toHaveLength(2); + for (const feature of geojson.features) { + expect(feature.type).toBe("Feature"); + expect(feature.geometry.type).toBe("MultiPolygon"); + } + expect(geojson.features.map((f) => f.properties.elevation)).toEqual([100, 200]); + }); + + it("runs every coordinate through the converter", () => { + const convert = vi.fn(toLngLat); + const data = makeData([100], { contours: [square(100, 10, true)] }); + const geojson = contoursToGeoJson(data, convert); + + expect(convert).toHaveBeenCalledTimes(10); + expect(convert).toHaveBeenCalledWith(10, 10); + + const [polygon] = geojson.features[0].geometry.coordinates; + expect(polygon).toHaveLength(2); + expect(polygon[0]).toEqual([ + [0, 5], + [1, 5], + [1, 4], + [0, 4], + [0, 5], + ]); + expect(polygon[1][0]).toEqual([0.2, 4.8]); + }); + + it("preserves a multi-part geometry", () => { + const contour = square(50); + contour.coordinates.push([ + [ + [20, 20], + [30, 20], + [30, 30], + [20, 20], + ], + ]); + const geojson = contoursToGeoJson(makeData([50], { contours: [contour] }), toLngLat); + expect(geojson.features[0].geometry.coordinates).toHaveLength(2); + expect(geojson.features[0].geometry.coordinates[1][0][1]).toEqual([3, 3]); + }); + + it("returns an empty collection for no contours", () => { + expect(contoursToGeoJson(makeData([100], { contours: [] }), toLngLat)).toEqual({ type: "FeatureCollection", features: [] }); + }); +}); diff --git a/src/lib/contours/__tests__/fixtures.ts b/src/lib/contours/__tests__/fixtures.ts new file mode 100644 index 0000000..58c2435 --- /dev/null +++ b/src/lib/contours/__tests__/fixtures.ts @@ -0,0 +1,36 @@ +import type { ContourMultiPolygon } from "d3-contour"; +import { DEFAULT_SETTINGS } from "../constants"; +import type { ContourData, ContourSettings } from "../types"; + +/** A square contour polygon at `value`, optionally with a square hole inside it. */ +export const square = (value: number, size = 10, hole = false): ContourMultiPolygon => { + const outer: [number, number][] = [ + [0, 0], + [size, 0], + [size, size], + [0, size], + [0, 0], + ]; + const inner: [number, number][] = [ + [2, 2], + [2, size - 2], + [size - 2, size - 2], + [size - 2, 2], + [2, 2], + ]; + return { type: "MultiPolygon", value, coordinates: [hole ? [outer, inner] : [outer]] }; +}; + +/** Contour data for the given threshold values on a small synthetic grid. */ +export const makeData = (values: number[], overrides: Partial = {}): ContourData => ({ + contours: values.map((v) => square(v)), + min: Math.min(...values), + max: Math.max(...values), + interval: 100, + unit: "m", + width: 110, + height: 60, + ...overrides, +}); + +export const makeSettings = (overrides: Partial = {}): ContourSettings => ({ ...DEFAULT_SETTINGS, interval: 100, ...overrides }); diff --git a/src/lib/contours/__tests__/hash.test.ts b/src/lib/contours/__tests__/hash.test.ts new file mode 100644 index 0000000..8df36fa --- /dev/null +++ b/src/lib/contours/__tests__/hash.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from "vitest"; +import { formatHash, parseHash } from "../hash"; + +describe("parseHash", () => { + it("parses #zoom/lat/lng", () => { + expect(parseHash("#13/35.3577/138.7331")).toEqual({ zoom: 13, lat: 35.3577, lng: 138.7331 }); + }); + + it("accepts a hash without the leading #", () => { + expect(parseHash("9/19.6801/-155.5132")).toEqual({ zoom: 9, lat: 19.6801, lng: -155.5132 }); + }); + + it("reads the zoom as an integer", () => { + expect(parseHash("#12.7/0/0")?.zoom).toBe(12); + }); + + it("rejects the wrong number of parts", () => { + expect(parseHash("")).toBeNull(); + expect(parseHash("#")).toBeNull(); + expect(parseHash("#13")).toBeNull(); + expect(parseHash("#13/35.3")).toBeNull(); + expect(parseHash("#13/35.3/138.7/1")).toBeNull(); + }); + + it("rejects non-numeric parts", () => { + expect(parseHash("#zoom/lat/lng")).toBeNull(); + expect(parseHash("#/35.3/138.7")).toBeNull(); + expect(parseHash("#13//138.7")).toBeNull(); + expect(parseHash("#13/35.3/Infinity")).toBeNull(); + }); + + it("rejects latitudes outside ±90", () => { + expect(parseHash("#13/90.5/0")).toBeNull(); + expect(parseHash("#13/-91/0")).toBeNull(); + expect(parseHash("#13/90/0")).toEqual({ zoom: 13, lat: 90, lng: 0 }); + }); +}); + +describe("formatHash", () => { + it("uses more decimal places at higher zooms", () => { + const position = { lat: 35.35774321, lng: 138.73312345 }; + expect(formatHash({ ...position, zoom: 1 })).toBe("#1/35/139"); + expect(formatHash({ ...position, zoom: 8 })).toBe("#8/35.358/138.733"); + expect(formatHash({ ...position, zoom: 13 })).toBe("#13/35.3577/138.7331"); + expect(formatHash({ ...position, zoom: 16 })).toBe("#16/35.3577/138.7331"); + }); + + it("drops floating point noise from the zoom", () => { + expect(formatHash({ zoom: 13.000001, lat: 0, lng: 0 })).toBe("#13/0.0000/0.0000"); + expect(formatHash({ zoom: 12.5, lat: 0, lng: 0 })).toBe("#12.5/0.0000/0.0000"); + }); + + it("round-trips through parseHash", () => { + const hash = "#13/35.3577/138.7331"; + expect(formatHash(parseHash(hash)!)).toBe(hash); + + const position = { zoom: 11, lat: 46.8358, lng: -121.7663 }; + expect(parseHash(formatHash(position))).toEqual(position); + }); +}); diff --git a/src/lib/contours/__tests__/thresholds.test.ts b/src/lib/contours/__tests__/thresholds.test.ts new file mode 100644 index 0000000..3f8590a --- /dev/null +++ b/src/lib/contours/__tests__/thresholds.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from "vitest"; +import { MIN_PIXELS_PER_BAND } from "../constants"; +import { computeThresholds } from "../thresholds"; + +/** Repeat each value `count` times, so a "band" can be given an exact pixel count. */ +const fill = (...bands: Array<[value: number, count: number]>): number[] => bands.flatMap(([value, count]) => Array(count).fill(value)); + +describe("computeThresholds", () => { + it("returns null for empty input", () => { + expect(computeThresholds([], 100)).toBeNull(); + expect(computeThresholds(new Int32Array(0), 100)).toBeNull(); + }); + + it("returns null for an invalid interval", () => { + const values = fill([100, 60]); + expect(computeThresholds(values, 0)).toBeNull(); + expect(computeThresholds(values, -50)).toBeNull(); + expect(computeThresholds(values, Number.NaN)).toBeNull(); + expect(computeThresholds(values, Number.POSITIVE_INFINITY)).toBeNull(); + }); + + it("rounds the range outwards to multiples of the interval", () => { + const result = computeThresholds([-150, 120, 480], 100, 0); + expect(result).toEqual({ min: -200, max: 400, thresholds: [-200, -100, 0, 100, 200, 300, 400] }); + }); + + it("keeps empty bands in the middle of the range", () => { + const result = computeThresholds([0, 1000], 250, 0); + expect(result?.thresholds).toEqual([0, 250, 500, 750, 1000]); + }); + + it("drops junk bands at both ends by default", () => { + const values = fill([5, 4], [120, 60], [220, 60], [320, 60], [950, 3]); + expect(computeThresholds(values, 100)).toEqual({ min: 100, max: 300, thresholds: [100, 200, 300] }); + }); + + it("needs strictly more than the minimum pixel count to keep a band", () => { + const body: Array<[number, number]> = [[500, 200]]; + expect(computeThresholds(fill([0, MIN_PIXELS_PER_BAND], ...body), 100)?.thresholds).toEqual([500]); + expect(computeThresholds(fill([0, MIN_PIXELS_PER_BAND + 1], ...body), 100)?.thresholds).toEqual([0, 100, 200, 300, 400, 500]); + }); + + it("honours a custom pixel minimum", () => { + const values = fill([0, 2], [100, 3], [200, 10]); + expect(computeThresholds(values, 100, 2)?.thresholds).toEqual([100, 200]); + expect(computeThresholds(values, 100, 1)?.thresholds).toEqual([0, 100, 200]); + }); + + it("returns null when every band is junk", () => { + expect(computeThresholds(fill([100, 10]), 100)).toBeNull(); + }); + + it("accepts typed arrays", () => { + const values = Int32Array.from(fill([10, 60], [90, 60])); + expect(computeThresholds(values, 50)).toEqual({ min: 0, max: 50, thresholds: [0, 50] }); + }); +}); diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..7cb93dd --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,12 @@ +import { fileURLToPath } from "node:url"; +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + resolve: { + alias: { "@": fileURLToPath(new URL("./src", import.meta.url)) }, + }, + test: { + environment: "node", + include: ["src/**/*.test.ts"], + }, +}); From 32fb10b26deb91163a5368a897e531d61339eb50 Mon Sep 17 00:00:00 2001 From: David Heyman Date: Wed, 4 Nov 2026 23:21:14 +0000 Subject: [PATCH 5/7] Contour in a Web Worker and treat missing tiles as no-data Decoding, threshold selection and d3-contour now run in src/lib/contours/contour.worker.ts, a thin wrapper around the unchanged pure computeContours. The hook rasterizes the tiles on the main thread, transfers the pixel buffer to the worker and draws the result when it arrives; the worker keeps the last grid so an interval or unit change only sends the new parameters. A generation counter drops results that belong to a view the map has already left, and the "Drawing contours" card stays up from the moment work is scheduled until the result is drawn. The double requestAnimationFrame is gone since nothing blocks. Pixels with alpha 0 (a tile that failed to load or is outside the data) decoded to -32768 m, which stretched the threshold range over tens of thousands of steps and froze the tab. They are now NaN, which d3-contour v4 treats as missing, and computeThresholds skips them when picking the range and counting bands. Threshold counts above MAX_THRESHOLDS (2000) compute nothing and come back as a ContourResult failure that the hook exposes as `problem`, shown as a notice in the map's status card area. Co-Authored-By: Claude Fable 5.1 --- src/components/contours/contours-app.tsx | 2 +- src/components/contours/map-view.tsx | 25 +++++- src/hooks/use-contour-map.ts | 102 ++++++++++++++++------- src/lib/contours/compute.ts | 14 ++-- src/lib/contours/constants.ts | 3 + src/lib/contours/contour-client.ts | 49 +++++++++++ src/lib/contours/contour.worker.ts | 22 +++++ src/lib/contours/elevation.ts | 20 +++-- src/lib/contours/thresholds.ts | 38 +++++++-- src/lib/contours/types.ts | 9 ++ src/lib/contours/worker-protocol.ts | 19 +++++ 11 files changed, 247 insertions(+), 56 deletions(-) create mode 100644 src/lib/contours/contour-client.ts create mode 100644 src/lib/contours/contour.worker.ts create mode 100644 src/lib/contours/worker-protocol.ts diff --git a/src/components/contours/contours-app.tsx b/src/components/contours/contours-app.tsx index 730b532..f775bcc 100644 --- a/src/components/contours/contours-app.tsx +++ b/src/components/contours/contours-app.tsx @@ -40,7 +40,7 @@ export const ContoursApp = () => {
- + diff --git a/src/components/contours/map-view.tsx b/src/components/contours/map-view.tsx index de6b257..21eefac 100644 --- a/src/components/contours/map-view.tsx +++ b/src/components/contours/map-view.tsx @@ -2,23 +2,40 @@ import type { RefObject } from "react"; import { LoadingIndicator } from "@/components/application/loading-indicator/loading-indicator"; +import type { ContourProblem } from "@/lib/contours/types"; interface MapViewProps { containerRef: RefObject; isDrawing: boolean; + problem: ContourProblem | null; } -/** The Leaflet container plus a floating "Drawing contours" status card. */ -export const MapView = ({ containerRef, isDrawing }: MapViewProps) => ( +const describeProblem = (problem: ContourProblem): string => { + switch (problem.kind) { + case "too-many-thresholds": + return `Too many contour lines for this view (${problem.count.toLocaleString()}; the limit is ${problem.limit.toLocaleString()}). Try a larger interval.`; + } +}; + +const cardClass = + "flex items-center gap-3 rounded-xl bg-primary py-2.5 pr-4 pl-3 shadow-lg ring-1 ring-secondary_alt duration-150 animate-in fade-in slide-in-from-top-1"; + +/** The Leaflet container plus floating status cards: "Drawing contours" and why a view has none. */ +export const MapView = ({ containerRef, isDrawing, problem }: MapViewProps) => ( <>
-
+
{isDrawing && ( -
+
Drawing contours…
)} + {problem && !isDrawing && ( +
+ {describeProblem(problem)} +
+ )}
); diff --git a/src/hooks/use-contour-map.ts b/src/hooks/use-contour-map.ts index 34d0e22..073d14b 100644 --- a/src/hooks/use-contour-map.ts +++ b/src/hooks/use-contour-map.ts @@ -2,13 +2,14 @@ import { type RefObject, useCallback, useEffect, useMemo, useRef, useState } from "react"; import L from "leaflet"; -import { computeContours } from "@/lib/contours/compute"; import { CANVAS_BUFFER, ESRI_ATTRIBUTION, ESRI_PLACES_URL, ESRI_ROADS_URL, MAX_ZOOM, REDRAW_DELAY_MS, randomExampleLocation } from "@/lib/contours/constants"; +import { ContourWorkerClient } from "@/lib/contours/contour-client"; import { contoursToSvg, drawContours, hasBathymetry } from "@/lib/contours/draw"; import { canvasToPngBlob, contoursToGeoJson, downloadBlob, downloadText } from "@/lib/contours/export"; import { formatHash, parseHash } from "@/lib/contours/hash"; import { TerrariumLayer } from "@/lib/contours/terrarium-layer"; -import type { ContourData, ContourSettings, MapPosition } from "@/lib/contours/types"; +import type { ContourData, ContourProblem, ContourResult, ContourSettings, MapPosition } from "@/lib/contours/types"; +import type { PixelGrid } from "@/lib/contours/worker-protocol"; /** * How much work a pending redraw needs. A heavier request always wins over a @@ -28,8 +29,10 @@ export interface ContourMapActions { export interface ContourMap { containerRef: RefObject; - /** True while elevation data is being rasterized or contours computed. */ + /** True from the moment a redraw is scheduled until its contours are on screen. */ isDrawing: boolean; + /** Why the current view has no contours, when that is worth telling the user. */ + problem: ContourProblem | null; /** True when the current view contains elevations below sea level. */ hasBathymetry: boolean; actions: ContourMapActions; @@ -41,7 +44,8 @@ const needsRecompute = (a: ContourSettings, b: ContourSettings) => a.interval != /** * Owns the Leaflet map, the hidden elevation layer, the reference overlay and * the contour canvas. Re-runs the elevation → contours → canvas pipeline, - * debounced, whenever the view or the settings change. + * debounced, whenever the view or the settings change. Rasterizing the tiles + * and drawing happen here; decoding and contouring run in a Web Worker. */ export function useContourMap(settings: ContourSettings): ContourMap { const containerRef = useRef(null); @@ -50,16 +54,21 @@ export function useContourMap(settings: ContourSettings): ContourMap { const referenceLayerRef = useRef(null); const contourCanvasRef = useRef(null); const demCanvasRef = useRef(null); - const demPixelsRef = useRef(null); const dataRef = useRef(null); + const workerRef = useRef(null); const settingsRef = useRef(settings); const previousSettingsRef = useRef(null); const timerRef = useRef(null); const pendingWorkRef = useRef(Work.Draw); + /** Bumped when the view changes, so contours computed for the old view are dropped when they arrive. */ + const generationRef = useRef(0); + /** Worker requests posted but not yet answered. */ + const inFlightRef = useRef(0); const [isDrawing, setIsDrawing] = useState(false); const [bathymetry, setBathymetry] = useState(false); + const [problem, setProblem] = useState(null); const gridSize = () => { const size = mapRef.current?.getSize() ?? L.point(0, 0); @@ -86,36 +95,71 @@ export function useContourMap(settings: ContourSettings): ContourMap { drawContours(ctx, data, settingsRef.current); }; - const recomputeContours = () => { - const pixels = demPixelsRef.current; - if (!pixels) return; - const { width, height } = gridSize(); - const { interval, unit } = settingsRef.current; - dataRef.current = computeContours(pixels, width, height, interval, unit); + /** Hide the status card once nothing is waiting or in flight. */ + const settle = () => { + if (timerRef.current === null && inFlightRef.current === 0) setIsDrawing(false); + }; + + const applyResult = (result: ContourResult) => { + dataRef.current = result.status === "ok" ? result.data : null; + setProblem(result.status === "failed" ? result.problem : null); setBathymetry(dataRef.current ? hasBathymetry(dataRef.current) : false); draw(); }; - const rasterizeRelief = () => { + /** Draw the loaded tiles onto the offscreen canvas and read the pixels back. */ + const rasterizeRelief = (): PixelGrid | null => { const map = mapRef.current; const demLayer = demLayerRef.current; const demCanvas = demCanvasRef.current; const container = containerRef.current; const ctx = demCanvas?.getContext("2d", { willReadFrequently: true }); - if (!map || !demLayer || !demCanvas || !ctx || !container) return; + if (!map || !demLayer || !demCanvas || !ctx || !container) return null; const { width, height } = gridSize(); ctx.clearRect(0, 0, width, height); resetCanvasPosition(); demLayer.rasterize(ctx, container, map.getZoom(), CANVAS_BUFFER); - demPixelsRef.current = ctx.getImageData(0, 0, width, height).data; - recomputeContours(); + return { pixels: ctx.getImageData(0, 0, width, height).data, width, height }; + }; + + /** Post a contour computation to the worker; a new grid is sent only after re-rasterizing. */ + const requestContours = (grid: PixelGrid | null) => { + const worker = workerRef.current; + if (!worker) return; + const generation = generationRef.current; + const { interval, unit } = settingsRef.current; + inFlightRef.current++; + worker + .compute({ grid: grid ?? undefined, interval, unit }) + .then( + (result) => { + // The map moved (or unmounted) while this was computing; its result belongs to the old view. + if (generation !== generationRef.current || !mapRef.current) return; + applyResult(result); + }, + (error: unknown) => { + if (generation !== generationRef.current || !mapRef.current) return; + console.error("Contour computation failed", error); + applyResult({ status: "empty" }); + }, + ) + .finally(() => { + inFlightRef.current--; + settle(); + }); }; const runWork = (work: Work) => { - if (work === Work.Relief) rasterizeRelief(); - else if (work === Work.Contours) recomputeContours(); - else draw(); + if (work === Work.Relief) { + const grid = rasterizeRelief(); + if (grid) requestContours(grid); + } else if (work === Work.Contours) { + requestContours(null); + } else { + draw(); + } + settle(); }; const cancelPending = () => { @@ -128,22 +172,15 @@ export function useContourMap(settings: ContourSettings): ContourMap { /** Debounce a redraw; the heaviest requested level of work is what runs. */ const schedule = (work: Work) => { pendingWorkRef.current = Math.max(pendingWorkRef.current, work) as Work; + // A re-rasterization means the view changed: anything still computing is for the old one. + if (work === Work.Relief) generationRef.current++; cancelPending(); + setIsDrawing(true); timerRef.current = window.setTimeout(() => { timerRef.current = null; const pending = pendingWorkRef.current; pendingWorkRef.current = Work.Draw; - setIsDrawing(true); - // Two frames so the "Drawing contours" state paints before the heavy work blocks the thread. - requestAnimationFrame(() => - requestAnimationFrame(() => { - try { - runWork(pending); - } finally { - setIsDrawing(false); - } - }), - ); + runWork(pending); }, REDRAW_DELAY_MS); }; @@ -157,6 +194,7 @@ export function useContourMap(settings: ContourSettings): ContourMap { const map = L.map(container, { scrollWheelZoom: false, zoomControl: false, maxZoom: MAX_ZOOM }); mapRef.current = map; + workerRef.current = new ContourWorkerClient(); L.control.zoom({ position: "topright" }).addTo(map); const demLayer = new TerrariumLayer(() => schedule(Work.Relief)); @@ -225,14 +263,16 @@ export function useContourMap(settings: ContourSettings): ContourMap { return () => { cancelPending(); + generationRef.current++; window.removeEventListener("hashchange", onHashChange); + workerRef.current?.terminate(); + workerRef.current = null; map.remove(); mapRef.current = null; demLayerRef.current = null; referenceLayerRef.current = null; contourCanvasRef.current = null; demCanvasRef.current = null; - demPixelsRef.current = null; dataRef.current = null; }; // The map is created once; later changes flow through refs and the settings effect. @@ -296,5 +336,5 @@ export function useContourMap(settings: ContourSettings): ContourMap { [setView, fitBounds, downloadGeoJson, downloadPng, downloadSvg], ); - return { containerRef, isDrawing, hasBathymetry: bathymetry, actions }; + return { containerRef, isDrawing, problem, hasBathymetry: bathymetry, actions }; } diff --git a/src/lib/contours/compute.ts b/src/lib/contours/compute.ts index b60bd28..36d3f69 100644 --- a/src/lib/contours/compute.ts +++ b/src/lib/contours/compute.ts @@ -1,20 +1,22 @@ import { type ContourMultiPolygon, contours as d3Contours } from "d3-contour"; import { decodeElevationGrid } from "./elevation"; import { computeThresholds } from "./thresholds"; -import type { ContourData, Unit } from "./types"; +import type { ContourResult, Unit } from "./types"; /** * Compute contour polygons for a viewport-sized grid of Terrarium pixels. - * Returns null when no usable elevation data is present (for example before - * any tile has loaded). + * Pure and synchronous; the app runs it inside `contour.worker.ts` so the + * main thread stays responsive. Returns `empty` when no usable elevation data + * is present (for example before any tile has loaded) and `failed` when the + * view would need an unreasonable number of contour lines. */ -export function computeContours(pixels: Uint8ClampedArray, width: number, height: number, interval: number, unit: Unit): ContourData | null { +export function computeContours(pixels: Uint8ClampedArray, width: number, height: number, interval: number, unit: Unit): ContourResult { const values = decodeElevationGrid(pixels, width, height, unit); const range = computeThresholds(values, interval); - if (!range) return null; + if (range.status !== "ok") return range; // d3-contour's typings ask for number[], but it reads any array-like at runtime. const contours: ContourMultiPolygon[] = d3Contours().size([width, height]).thresholds(range.thresholds)(values as unknown as number[]); - return { contours, min: range.min, max: range.max, interval, unit, width, height }; + return { status: "ok", data: { contours, min: range.min, max: range.max, interval, unit, width, height } }; } diff --git a/src/lib/contours/constants.ts b/src/lib/contours/constants.ts index bd5e1cc..b422611 100644 --- a/src/lib/contours/constants.ts +++ b/src/lib/contours/constants.ts @@ -17,6 +17,9 @@ export const CANVAS_BUFFER = 5; /** Elevation bands with fewer pixels than this are treated as junk when choosing thresholds. */ export const MIN_PIXELS_PER_BAND = 50; +/** Refuse to contour a view that would need more thresholds than this; it would only hang the tab. */ +export const MAX_THRESHOLDS = 2000; + /** Wait this long after the last move, tile load or setting change before recomputing. */ export const REDRAW_DELAY_MS = 500; export const SEARCH_DELAY_MS = 250; diff --git a/src/lib/contours/contour-client.ts b/src/lib/contours/contour-client.ts new file mode 100644 index 0000000..3371055 --- /dev/null +++ b/src/lib/contours/contour-client.ts @@ -0,0 +1,49 @@ +import type { ContourResult } from "./types"; +import type { ContourWorkerRequest, ContourWorkerResponse } from "./worker-protocol"; + +interface PendingRequest { + resolve: (result: ContourResult) => void; + reject: (error: Error) => void; +} + +/** + * Promise-based handle on the contour Web Worker. Requests are answered in + * order; callers decide whether a result is still wanted when it arrives. + */ +export class ContourWorkerClient { + private readonly worker: Worker; + private readonly pending = new Map(); + private nextId = 1; + + constructor() { + this.worker = new Worker(new URL("./contour.worker.ts", import.meta.url), { type: "module" }); + this.worker.onmessage = (event: MessageEvent) => { + const request = this.pending.get(event.data.id); + if (!request) return; + this.pending.delete(event.data.id); + if ("error" in event.data) request.reject(new Error(event.data.error)); + else request.resolve(event.data.result); + }; + this.worker.onerror = (event) => this.rejectAll(new Error(event.message || "Contour worker failed")); + } + + /** Compute contours; the pixel buffer in `request.grid`, if any, is transferred rather than copied. */ + compute(request: Omit): Promise { + const id = this.nextId++; + return new Promise((resolve, reject) => { + this.pending.set(id, { resolve, reject }); + const message: ContourWorkerRequest = { id, ...request }; + this.worker.postMessage(message, request.grid ? [request.grid.pixels.buffer] : []); + }); + } + + terminate(): void { + this.rejectAll(new Error("Contour worker terminated")); + this.worker.terminate(); + } + + private rejectAll(error: Error): void { + for (const request of this.pending.values()) request.reject(error); + this.pending.clear(); + } +} diff --git a/src/lib/contours/contour.worker.ts b/src/lib/contours/contour.worker.ts new file mode 100644 index 0000000..abcd1d5 --- /dev/null +++ b/src/lib/contours/contour.worker.ts @@ -0,0 +1,22 @@ +import { computeContours } from "./compute"; +import type { ContourWorkerRequest, ContourWorkerResponse, PixelGrid } from "./worker-protocol"; + +/** + * Runs the elevation → thresholds → d3-contour pipeline off the main thread. + * A thin wrapper around `computeContours`: it only keeps the last pixel grid + * so that an interval or unit change doesn't need to re-send the pixels. + */ +let grid: PixelGrid | null = null; + +addEventListener("message", (event: MessageEvent) => { + const { id, interval, unit } = event.data; + if (event.data.grid) grid = event.data.grid; + + let response: ContourWorkerResponse; + try { + response = { id, result: grid ? computeContours(grid.pixels, grid.width, grid.height, interval, unit) : { status: "empty" } }; + } catch (error) { + response = { id, error: error instanceof Error ? error.message : String(error) }; + } + postMessage(response); +}); diff --git a/src/lib/contours/elevation.ts b/src/lib/contours/elevation.ts index 104586c..2f71642 100644 --- a/src/lib/contours/elevation.ts +++ b/src/lib/contours/elevation.ts @@ -4,18 +4,28 @@ import type { Unit } from "./types"; /** Decode one Terrarium pixel (red, green, blue) to meters above sea level. */ export const terrariumToMeters = (r: number, g: number, b: number): number => r * 256 + g + b / 256 - 32768; +/** + * No-data marker in a decoded grid. d3-contour treats NaN as missing (below + * every threshold), so a hole in the data closes the surrounding contours + * instead of pulling the elevation range down to -32768 m. + */ +export const NO_DATA = NaN; +export const isNoData = (value: number): boolean => Number.isNaN(value); + /** * Turn the RGBA pixels of a canvas that has Terrarium tiles drawn on it into a * row-major grid of rounded elevations in the requested unit, laid out the way - * d3-contour expects (`x + y * width`). + * d3-contour expects (`x + y * width`). Fully transparent pixels, where no tile + * was drawn (not loaded yet, failed, or outside the data), become `NO_DATA`. */ -export function decodeElevationGrid(pixels: Uint8ClampedArray, width: number, height: number, unit: Unit): Int32Array { +export function decodeElevationGrid(pixels: Uint8ClampedArray, width: number, height: number, unit: Unit): Float32Array { const factor = unit === "ft" ? METERS_TO_FEET : 1; - const values = new Int32Array(width * height); + const values = new Float32Array(width * height); for (let y = 0; y < height; y++) { for (let x = 0; x < width; x++) { - const i = (y * width + x) * 4; - values[y * width + x] = Math.round(terrariumToMeters(pixels[i], pixels[i + 1], pixels[i + 2]) * factor); + const k = y * width + x; + const i = k * 4; + values[k] = pixels[i + 3] === 0 ? NO_DATA : Math.round(terrariumToMeters(pixels[i], pixels[i + 1], pixels[i + 2]) * factor); } } return values; diff --git a/src/lib/contours/thresholds.ts b/src/lib/contours/thresholds.ts index 192440b..2f80d1d 100644 --- a/src/lib/contours/thresholds.ts +++ b/src/lib/contours/thresholds.ts @@ -1,4 +1,6 @@ -import { MIN_PIXELS_PER_BAND } from "./constants"; +import { MAX_THRESHOLDS, MIN_PIXELS_PER_BAND } from "./constants"; +import { isNoData } from "./elevation"; +import type { ContourProblem } from "./types"; export interface ThresholdRange { min: number; @@ -6,31 +8,46 @@ export interface ThresholdRange { thresholds: number[]; } +export type ThresholdResult = ({ status: "ok" } & ThresholdRange) | { status: "empty" } | { status: "failed"; problem: ContourProblem }; + /** * Pick the contour thresholds for a grid of elevations: multiples of `interval` * from the lowest to the highest band that holds a meaningful number of pixels. - * Bands with fewer than `minPixels` pixels at either end are treated as junk - * (tile seams, no-data pixels) and dropped. + * No-data values are ignored. Bands with fewer than `minPixels` pixels at + * either end are treated as junk (tile seams, stray pixels) and dropped. + * A range that would need more than `maxThresholds` lines is refused rather + * than computed, since contouring it would freeze the page. */ -export function computeThresholds(values: ArrayLike, interval: number, minPixels = MIN_PIXELS_PER_BAND): ThresholdRange | null { - if (!Number.isFinite(interval) || interval <= 0 || values.length === 0) return null; +export function computeThresholds( + values: ArrayLike, + interval: number, + minPixels = MIN_PIXELS_PER_BAND, + maxThresholds = MAX_THRESHOLDS, +): ThresholdResult { + if (!Number.isFinite(interval) || interval <= 0) return { status: "empty" }; let rawMin = Infinity; let rawMax = -Infinity; for (let i = 0; i < values.length; i++) { const v = values[i]; + if (isNoData(v)) continue; if (v < rawMin) rawMin = v; if (v > rawMax) rawMax = v; } + if (rawMin === Infinity) return { status: "empty" }; const min = Math.floor(rawMin / interval) * interval; const max = Math.ceil(rawMax / interval) * interval; const bandCount = Math.round((max - min) / interval) + 1; - if (!Number.isFinite(bandCount) || bandCount < 1) return null; + if (!Number.isFinite(bandCount) || bandCount < 1) return { status: "empty" }; + // Far beyond the cap even before junk bands are trimmed: don't bother counting. + if (bandCount > maxThresholds * 100) return { status: "failed", problem: { kind: "too-many-thresholds", count: bandCount, limit: maxThresholds } }; const counts = new Uint32Array(bandCount); for (let i = 0; i < values.length; i++) { - counts[Math.floor((values[i] - min) / interval)]++; + const v = values[i]; + if (isNoData(v)) continue; + counts[Math.floor((v - min) / interval)]++; } let first = -1; @@ -41,10 +58,13 @@ export function computeThresholds(values: ArrayLike, interval: number, m last = b; } } - if (first === -1) return null; + if (first === -1) return { status: "empty" }; + + const count = last - first + 1; + if (count > maxThresholds) return { status: "failed", problem: { kind: "too-many-thresholds", count, limit: maxThresholds } }; const thresholds: number[] = []; for (let b = first; b <= last; b++) thresholds.push(min + b * interval); - return { min: thresholds[0], max: thresholds[thresholds.length - 1], thresholds }; + return { status: "ok", min: thresholds[0], max: thresholds[thresholds.length - 1], thresholds }; } diff --git a/src/lib/contours/types.ts b/src/lib/contours/types.ts index 006b3c8..5bc1a03 100644 --- a/src/lib/contours/types.ts +++ b/src/lib/contours/types.ts @@ -49,6 +49,15 @@ export interface ContourData { height: number; } +/** Why contours could not be computed for the current view. */ +export type ContourProblem = { kind: "too-many-thresholds"; count: number; limit: number }; + +/** + * The outcome of one contour computation: contours, nothing to draw (no + * usable elevation data yet), or a problem the UI should explain. + */ +export type ContourResult = { status: "ok"; data: ContourData } | { status: "empty" } | { status: "failed"; problem: ContourProblem }; + export interface MapPosition { lat: number; lng: number; diff --git a/src/lib/contours/worker-protocol.ts b/src/lib/contours/worker-protocol.ts new file mode 100644 index 0000000..1315037 --- /dev/null +++ b/src/lib/contours/worker-protocol.ts @@ -0,0 +1,19 @@ +import type { ContourResult, Unit } from "./types"; + +/** A viewport's worth of Terrarium pixels, as read back from the offscreen canvas. */ +export interface PixelGrid { + /** RGBA pixels; its buffer is transferred to the worker, so don't read it afterwards. */ + pixels: Uint8ClampedArray; + width: number; + height: number; +} + +export interface ContourWorkerRequest { + id: number; + /** A freshly rasterized grid. Omitted when only `interval` or `unit` changed; the worker then reuses the last grid it was sent. */ + grid?: PixelGrid; + interval: number; + unit: Unit; +} + +export type ContourWorkerResponse = { id: number; result: ContourResult } | { id: number; error: string }; From ce47c248f2e680b1f5ddd77e662d61bf14c00188 Mon Sep 17 00:00:00 2001 From: David Heyman Date: Thu, 5 Nov 2026 07:36:03 +0000 Subject: [PATCH 6/7] Update tests for the worker-based contour API The threshold and elevation tests were written before contouring moved to a Web Worker: computeThresholds now returns a status result instead of null, and decodeElevationGrid returns a Float32Array with NaN as no-data. Rewrite the assertions for those shapes and add cases for no-data values and the threshold cap. Silence the one lint warning on the generation counter bumped in the map effect cleanup, which is not a DOM ref. Co-Authored-By: Claude Fable 5.1 --- src/hooks/use-contour-map.ts | 2 + src/lib/contours/__tests__/elevation.test.ts | 17 +++-- src/lib/contours/__tests__/thresholds.test.ts | 66 +++++++++++++------ 3 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/hooks/use-contour-map.ts b/src/hooks/use-contour-map.ts index 2b85db8..31ee528 100644 --- a/src/hooks/use-contour-map.ts +++ b/src/hooks/use-contour-map.ts @@ -265,6 +265,8 @@ export function useContourMap(settings: ContourSettings): ContourMap { return () => { cancelPending(); + // A counter, not a DOM node: bumping it here is what drops results that arrive after unmount. + // eslint-disable-next-line react-hooks/exhaustive-deps generationRef.current++; window.removeEventListener("hashchange", onHashChange); workerRef.current?.terminate(); diff --git a/src/lib/contours/__tests__/elevation.test.ts b/src/lib/contours/__tests__/elevation.test.ts index 27c8c1b..5b396c5 100644 --- a/src/lib/contours/__tests__/elevation.test.ts +++ b/src/lib/contours/__tests__/elevation.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from "vitest"; -import { decodeElevationGrid, terrariumToMeters } from "../elevation"; +import { NO_DATA, decodeElevationGrid, isNoData, terrariumToMeters } from "../elevation"; describe("terrariumToMeters", () => { it("decodes sea level and the extremes of the encoding", () => { @@ -35,9 +35,9 @@ describe("decodeElevationGrid", () => { [162, 144, 0], ]); - it("returns a row-major Int32Array of rounded metres", () => { + it("returns a row-major Float32Array of rounded metres", () => { const grid = decodeElevationGrid(pixels, 2, 2, "m"); - expect(grid).toBeInstanceOf(Int32Array); + expect(grid).toBeInstanceOf(Float32Array); expect(grid).toHaveLength(4); expect(Array.from(grid)).toEqual([1, 10, -10, 8848]); }); @@ -47,7 +47,16 @@ describe("decodeElevationGrid", () => { expect(Array.from(grid)).toEqual([2, 33, -33, 29029]); }); - it("ignores the alpha channel and lays rows out as x + y * width", () => { + it("treats fully transparent pixels as no-data", () => { + const withHole = new Uint8ClampedArray(pixels); + withHole[1 * 4 + 3] = 0; + const grid = decodeElevationGrid(withHole, 2, 2, "m"); + expect(isNoData(grid[1])).toBe(true); + expect(Number.isNaN(NO_DATA)).toBe(true); + expect(Array.from(grid).filter((v) => !isNoData(v))).toEqual([1, -10, 8848]); + }); + + it("ignores partial alpha and lays rows out as x + y * width", () => { const wide = rgba([ [128, 1, 0], [128, 2, 0], diff --git a/src/lib/contours/__tests__/thresholds.test.ts b/src/lib/contours/__tests__/thresholds.test.ts index 3f8590a..eb775c4 100644 --- a/src/lib/contours/__tests__/thresholds.test.ts +++ b/src/lib/contours/__tests__/thresholds.test.ts @@ -1,57 +1,83 @@ import { describe, expect, it } from "vitest"; -import { MIN_PIXELS_PER_BAND } from "../constants"; +import { MAX_THRESHOLDS, MIN_PIXELS_PER_BAND } from "../constants"; +import { NO_DATA } from "../elevation"; import { computeThresholds } from "../thresholds"; /** Repeat each value `count` times, so a "band" can be given an exact pixel count. */ const fill = (...bands: Array<[value: number, count: number]>): number[] => bands.flatMap(([value, count]) => Array(count).fill(value)); +/** Unwrap an "ok" result, failing the test otherwise. */ +const ok = (result: ReturnType) => { + if (result.status !== "ok") throw new Error(`expected an ok result, got ${result.status}`); + return result; +}; + describe("computeThresholds", () => { - it("returns null for empty input", () => { - expect(computeThresholds([], 100)).toBeNull(); - expect(computeThresholds(new Int32Array(0), 100)).toBeNull(); + it("is empty for empty input", () => { + expect(computeThresholds([], 100)).toEqual({ status: "empty" }); + expect(computeThresholds(new Float32Array(0), 100)).toEqual({ status: "empty" }); }); - it("returns null for an invalid interval", () => { + it("is empty for an invalid interval", () => { const values = fill([100, 60]); - expect(computeThresholds(values, 0)).toBeNull(); - expect(computeThresholds(values, -50)).toBeNull(); - expect(computeThresholds(values, Number.NaN)).toBeNull(); - expect(computeThresholds(values, Number.POSITIVE_INFINITY)).toBeNull(); + expect(computeThresholds(values, 0)).toEqual({ status: "empty" }); + expect(computeThresholds(values, -50)).toEqual({ status: "empty" }); + expect(computeThresholds(values, Number.NaN)).toEqual({ status: "empty" }); + expect(computeThresholds(values, Number.POSITIVE_INFINITY)).toEqual({ status: "empty" }); }); it("rounds the range outwards to multiples of the interval", () => { const result = computeThresholds([-150, 120, 480], 100, 0); - expect(result).toEqual({ min: -200, max: 400, thresholds: [-200, -100, 0, 100, 200, 300, 400] }); + expect(result).toEqual({ status: "ok", min: -200, max: 400, thresholds: [-200, -100, 0, 100, 200, 300, 400] }); }); it("keeps empty bands in the middle of the range", () => { const result = computeThresholds([0, 1000], 250, 0); - expect(result?.thresholds).toEqual([0, 250, 500, 750, 1000]); + expect(ok(result).thresholds).toEqual([0, 250, 500, 750, 1000]); }); it("drops junk bands at both ends by default", () => { const values = fill([5, 4], [120, 60], [220, 60], [320, 60], [950, 3]); - expect(computeThresholds(values, 100)).toEqual({ min: 100, max: 300, thresholds: [100, 200, 300] }); + expect(computeThresholds(values, 100)).toEqual({ status: "ok", min: 100, max: 300, thresholds: [100, 200, 300] }); }); it("needs strictly more than the minimum pixel count to keep a band", () => { const body: Array<[number, number]> = [[500, 200]]; - expect(computeThresholds(fill([0, MIN_PIXELS_PER_BAND], ...body), 100)?.thresholds).toEqual([500]); - expect(computeThresholds(fill([0, MIN_PIXELS_PER_BAND + 1], ...body), 100)?.thresholds).toEqual([0, 100, 200, 300, 400, 500]); + expect(ok(computeThresholds(fill([0, MIN_PIXELS_PER_BAND], ...body), 100)).thresholds).toEqual([500]); + expect(ok(computeThresholds(fill([0, MIN_PIXELS_PER_BAND + 1], ...body), 100)).thresholds).toEqual([0, 100, 200, 300, 400, 500]); }); it("honours a custom pixel minimum", () => { const values = fill([0, 2], [100, 3], [200, 10]); - expect(computeThresholds(values, 100, 2)?.thresholds).toEqual([100, 200]); - expect(computeThresholds(values, 100, 1)?.thresholds).toEqual([0, 100, 200]); + expect(ok(computeThresholds(values, 100, 2)).thresholds).toEqual([100, 200]); + expect(ok(computeThresholds(values, 100, 1)).thresholds).toEqual([0, 100, 200]); }); - it("returns null when every band is junk", () => { - expect(computeThresholds(fill([100, 10]), 100)).toBeNull(); + it("is empty when every band is junk", () => { + expect(computeThresholds(fill([100, 10]), 100)).toEqual({ status: "empty" }); }); it("accepts typed arrays", () => { - const values = Int32Array.from(fill([10, 60], [90, 60])); - expect(computeThresholds(values, 50)).toEqual({ min: 0, max: 50, thresholds: [0, 50] }); + const values = Float32Array.from(fill([10, 60], [90, 60])); + expect(computeThresholds(values, 50)).toEqual({ status: "ok", min: 0, max: 50, thresholds: [0, 50] }); + }); + + it("ignores no-data values", () => { + const values = fill([NO_DATA, 500], [120, 60], [220, 60]); + expect(computeThresholds(values, 100)).toEqual({ status: "ok", min: 100, max: 200, thresholds: [100, 200] }); + expect(computeThresholds(fill([NO_DATA, 500]), 100)).toEqual({ status: "empty" }); + }); + + it("refuses a range that needs more than the maximum number of thresholds", () => { + // 0 .. 3000 at 1 unit: 3001 bands, each well above the pixel minimum. + const values = fill(...Array.from({ length: 3001 }, (_, i): [number, number] => [i, 2])); + expect(computeThresholds(values, 1, 1)).toEqual({ status: "failed", problem: { kind: "too-many-thresholds", count: 3001, limit: MAX_THRESHOLDS } }); + expect(computeThresholds(values, 1, 1, 5000).status).toBe("ok"); + }); + + it("refuses an absurd range before counting bands", () => { + const result = computeThresholds([0, 1_000_000], 1, 0, 10); + expect(result.status).toBe("failed"); + if (result.status === "failed") expect(result.problem).toEqual({ kind: "too-many-thresholds", count: 1_000_001, limit: 10 }); }); }); From 948ab27cd85c8fa0284c9cb2d3c38194139f5498 Mon Sep 17 00:00:00 2001 From: David Heyman Date: Thu, 5 Nov 2026 07:51:19 +0000 Subject: [PATCH 7/7] Exclude build output from type-checking Turbopack copies the contour worker's source into out/_next/static/media as an asset, so the second build on any machine, and the first one on Vercel, failed when tsc swept up out/. Exclude out, .next and .vercel. Co-Authored-By: Claude Fable 5.1 --- tsconfig.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/tsconfig.json b/tsconfig.json index 14859a7..9b644f7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -37,6 +37,9 @@ ".next/dev/types/**/*.ts" ], "exclude": [ - "node_modules" + "node_modules", + "out", + ".next", + ".vercel" ] }