@geolibre/core 3.7.0 → 3.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"file":"project-diff-DPGO-zCS.mjs","names":["WEB_MERCATOR_WKIDS","newLayerId","isPlainObject","isPlainObject","uuidv4"],"sources":["../src/types.ts","../src/regional-basemaps.ts","../src/cesium-imagery.ts","../src/arcgis-projection.ts","../src/map-crs.ts","../src/geojson-z.ts","../src/color-ramp.ts","../src/polyline.ts","../src/runtime-env.ts","../src/selection.ts","../src/expressions.ts","../src/vector-color.ts","../src/layer-descriptive-metadata.ts","../src/virtual-fields.ts","../src/joins.ts","../src/capabilities.ts","../src/layer-groups.ts","../src/layer-ref-scrub.ts","../src/print-layout-config.ts","../src/style-library.ts","../src/raster-source-defaults.ts","../src/geoarrow.ts","../src/czml.ts","../src/cesium-kml.ts","../src/cesium-ion.ts","../src/header-references.ts","../src/credentials.ts","../src/storymap-duration.ts","../src/geocoding.ts","../src/project.ts","../src/project-diff.ts"],"sourcesContent":["import type { FeatureCollection } from \"geojson\";\nimport type { GeoArrowBatch } from \"./geoarrow\";\nexport type { GeoArrowBatch };\nimport type { PrintLayoutConfig } from \"./print-layout-config\";\n\nexport const OPENFREEMAP_BASEMAPS = [\n {\n id: \"liberty\",\n name: \"Liberty\",\n styleUrl: \"https://tiles.openfreemap.org/styles/liberty\",\n },\n {\n id: \"liberty-3d\",\n name: \"Liberty 3D\",\n styleUrl: \"https://tiles.openfreemap.org/styles/liberty\",\n },\n {\n id: \"positron\",\n name: \"Positron\",\n styleUrl: \"https://tiles.openfreemap.org/styles/positron\",\n },\n {\n id: \"bright\",\n name: \"Bright\",\n styleUrl: \"https://tiles.openfreemap.org/styles/bright\",\n },\n {\n id: \"dark\",\n name: \"Dark\",\n styleUrl: \"https://tiles.openfreemap.org/styles/dark\",\n },\n {\n id: \"fiord\",\n name: \"Fiord\",\n styleUrl: \"https://tiles.openfreemap.org/styles/fiord\",\n },\n] as const;\n\n/**\n * Protomaps v5 basemap flavors. These are resolved to full style URLs at use\n * time by `getProtomapsStyleUrl`, which injects the `VITE_PROTOMAPS_API_KEY`\n * runtime env var. The key is only present in builds configured with it (e.g.\n * the GitHub Pages web demo), so consumers should hide these options when no\n * key is available.\n */\nexport const PROTOMAPS_BASEMAPS = [\n { id: \"protomaps-light\", name: \"Light\", flavor: \"light\" },\n { id: \"protomaps-dark\", name: \"Dark\", flavor: \"dark\" },\n { id: \"protomaps-white\", name: \"White\", flavor: \"white\" },\n { id: \"protomaps-grayscale\", name: \"Grayscale\", flavor: \"grayscale\" },\n { id: \"protomaps-black\", name: \"Black\", flavor: \"black\" },\n] as const;\n\nexport const DEFAULT_BASEMAP = \"https://tiles.openfreemap.org/styles/liberty\";\n\nexport const BLANK_BASEMAP = \"\";\n\nexport const PROJECT_VERSION = \"0.2.0\";\n\n/**\n * Every layer type, as a runtime list so untrusted input (an imported Layer\n * Library bundle, a hand-edited project) can be validated against it.\n * {@link LayerType} is derived from this array, so the two cannot drift.\n */\nexport const LAYER_TYPES = [\n \"geojson\",\n \"raster\",\n \"wms\",\n \"wmts\",\n \"xyz\",\n \"vector-tiles\",\n \"arcgis\",\n \"pmtiles\",\n \"mbtiles\",\n \"zarr\",\n \"lidar\",\n \"gaussian-splat\",\n \"3d-tiles\",\n \"cog\",\n \"flatgeobuf\",\n \"geoparquet\",\n \"duckdb-query\",\n \"deckgl-viz\",\n \"video\",\n \"image\",\n] as const;\n\nexport type LayerType = (typeof LAYER_TYPES)[number];\n\nexport type VectorStyleMode = \"single\" | \"graduated\" | \"categorized\" | \"rule-based\" | \"expression\";\n\n/**\n * One entry in a {@link LayerStyle.vectorRules} rule-based renderer. Each rule\n * pairs a MapLibre filter expression (a boolean expression array, serialized as\n * JSON) with a symbol color. Rules are evaluated top to bottom; the first whose\n * filter matches wins. The catch-all rule (`isElse`) has no filter and supplies\n * the color for features no other rule matched. Mirrors the QGIS/ArcGIS\n * rule-based renderer.\n */\nexport interface VectorRule {\n id: string;\n /** Human-readable label shown in the editor and the legend. */\n label: string;\n /**\n * A MapLibre boolean filter expression serialized as JSON, e.g.\n * `[\"==\", [\"get\", \"TYPE\"], \"park\"]`. Ignored when {@link isElse} is set or\n * the JSON is invalid.\n */\n filter: string;\n /** The symbol fill/circle color for features this rule matches (6-digit hex). */\n color: string;\n /**\n * When true this is the catch-all \"else\" rule: {@link filter} is unused and\n * the rule is an unconditional fallback for features no other rule matched,\n * so the {@link minZoom}/{@link maxZoom} and {@link parentId} fields are\n * ignored on it. Its symbol overrides and {@link enabled} toggle do apply.\n */\n isElse: boolean;\n /**\n * Whether the rule participates in rendering. `false` temporarily disables\n * the rule (and, for a group, its whole subtree) without deleting it,\n * mirroring the QGIS rule checkbox. Absent means enabled.\n */\n enabled?: boolean;\n /**\n * Lowest zoom (inclusive) the rule applies at, mirroring MapLibre's layer\n * `minzoom` convention. Absent means no lower bound.\n */\n minZoom?: number;\n /**\n * Zoom the rule stops applying at (exclusive, like MapLibre's layer\n * `maxzoom`). Absent means no upper bound. Together with {@link minZoom}\n * this is the QGIS per-rule \"scale range\".\n */\n maxZoom?: number;\n /**\n * The id of the parent rule when this rule is nested inside a group (QGIS\n * rule tree). A child rule matches only features that also match every\n * ancestor's filter, and inherits the intersection of the ancestors' zoom\n * ranges and enabled state. A rule that has children acts as a group: its\n * own symbol is not rendered; only leaf rules draw. Absent or dangling\n * means a top-level rule.\n */\n parentId?: string;\n /**\n * Per-rule stroke/outline color override (6-digit hex): the polygon outline\n * and circle stroke color for matching features. Absent inherits the layer\n * {@link LayerStyle.strokeColor}.\n */\n strokeColor?: string;\n /**\n * Per-rule stroke width override in pixels: the line width (lines and\n * polygon outlines) and circle stroke width for matching features. Absent\n * inherits the layer {@link LayerStyle.strokeWidth}.\n */\n strokeWidth?: number;\n /**\n * Per-rule fill/circle opacity override (0..1) for matching features.\n * Absent inherits the layer {@link LayerStyle.fillOpacity}.\n */\n fillOpacity?: number;\n /**\n * Per-rule circle radius override in pixels for matching point features.\n * Absent inherits the layer {@link LayerStyle.circleRadius}.\n */\n circleRadius?: number;\n}\n\n/**\n * The fill pattern applied to polygon layers. `\"none\"` keeps a flat fill; the\n * named patterns are generated as recolorable sprite tiles; `\"svg\"` rasterizes\n * the user-supplied markup in {@link LayerStyle.fillPatternSvg}.\n */\nexport type FillPattern =\n | \"none\"\n | \"hatch\"\n | \"cross-hatch\"\n | \"horizontal\"\n | \"vertical\"\n | \"dots\"\n | \"svg\";\n\n/**\n * The built-in marker shape for a point layer, or `\"custom\"` to rasterize the\n * user-supplied SVG in {@link LayerStyle.markerSvg}. Built-in shapes are drawn\n * on a sprite tile and recolored via {@link LayerStyle.markerColor}.\n */\nexport type MarkerShape =\n | \"circle\"\n | \"square\"\n | \"triangle\"\n | \"diamond\"\n | \"star\"\n | \"cross\"\n | \"pin\"\n | \"custom\";\n\n/**\n * How a point layer is rendered: as individual markers, a density heatmap, or\n * clustered bubbles. Only applies to point geometry.\n */\nexport type PointRenderer = \"single\" | \"heatmap\" | \"cluster\";\n\n/**\n * The repeated decoration symbol drawn along line features (and polygon\n * outlines), or `\"none\"` when decorations are off. Mirrors the QGIS\n * marker-line / arrow symbol layers: `\"arrow\"` renders directional arrowheads\n * that follow the line, the other shapes render as repeated markers.\n */\nexport type LineDecoration = \"none\" | \"arrow\" | \"triangle\" | \"circle\" | \"square\";\n\n/**\n * The per-feature derived geometry rendered by the geometry generator, or\n * `\"none\"` when the generator is off. Mirrors QGIS geometry-generator symbol\n * layers: each feature's derived geometry (its centroid, bounding box, convex\n * hull, or a buffer) is drawn as an extra symbol over the layer's normal\n * symbology. `\"centroid\"` derives points; the rest derive polygons.\n */\nexport type GeometryGeneratorType = \"none\" | \"centroid\" | \"bounding-box\" | \"convex-hull\" | \"buffer\";\n\n/**\n * Unit a stroke/line width is measured in. `\"pixels\"` is constant screen space;\n * `\"meters\"` is ground distance, so the rendered width scales with the map\n * scale (zoom). See {@link LayerStyle.strokeWidthUnit}.\n */\nexport type StrokeWidthUnit = \"pixels\" | \"meters\";\n\n/**\n * The named dash patterns the Style panel offers for {@link LayerStyle.strokeDash},\n * in MapLibre `line-dasharray` units (multiples of the line width). `solid` is\n * the empty pattern. QGIS pen styles map onto the same names.\n */\nexport const STROKE_DASH_PRESETS = {\n solid: [],\n dashed: [4, 2],\n dotted: [1, 2],\n \"dash-dot\": [4, 2, 1, 2],\n \"long-dash\": [8, 3],\n} as const satisfies Record<string, readonly number[]>;\n\n/** A named entry of {@link STROKE_DASH_PRESETS}. */\nexport type StrokeDashPreset = keyof typeof STROKE_DASH_PRESETS;\n\n/**\n * The chart drawn on top of each feature by the diagram renderer (QGIS-style\n * diagram symbology), or `\"none\"` when diagrams are off. Diagrams visualize\n * several numeric attributes per feature at once — e.g. election results by\n * party per county — and render through the shared deck.gl overlay on the\n * feature's point location or polygon centroid.\n */\nexport type DiagramType = \"none\" | \"pie\" | \"donut\" | \"bar\" | \"stacked-bar\";\n\n/**\n * How the overall diagram size is determined.\n *\n * - `\"fixed\"`: every diagram renders at {@link LayerStyle.diagramSize} pixels.\n * - `\"sum\"`: scaled by the sum of the mapped attribute values, so the largest\n * total renders at {@link LayerStyle.diagramSize} pixels (area-true square\n * root scaling).\n * - `\"attribute\"`: scaled the same way by the single numeric attribute in\n * {@link LayerStyle.diagramSizeProperty}.\n */\nexport type DiagramSizeMode = \"fixed\" | \"sum\" | \"attribute\";\n\n/** One attribute rendered as a slice/bar of a feature diagram. */\nexport interface DiagramField {\n /** Numeric feature property visualized by this slice/bar. */\n property: string;\n /** Slice/bar color (6-digit hex). */\n color: string;\n}\n\nexport interface VectorStyleStop {\n value: string | number;\n color: string;\n label?: string;\n}\n\n/** Attribute-driven labeling for a vector layer (rendered as a MapLibre symbol layer). */\nexport interface LabelStyle {\n /** Whether labels are shown for the layer. */\n enabled: boolean;\n /** Attribute field whose value becomes the label text. */\n field: string;\n /**\n * Optional MapLibre expression (JSON string) for the label text, which\n * overrides {@link field} when non-empty (e.g. concatenating several fields).\n */\n expression: string;\n /** `\"point\"` labels at the feature/centroid; `\"line\"` places them along lines. */\n placement: \"point\" | \"line\";\n /** Label text size in pixels. */\n size: number;\n /** CSS color string for the label text. */\n color: string;\n /** CSS color string for the text halo drawn behind the label. */\n haloColor: string;\n /** Width of the text halo in pixels. */\n haloWidth: number;\n /** Scale range for labels; `0` / `24` inherit the layer's own zoom range. */\n minZoom: number;\n maxZoom: number;\n /** Let labels overlap instead of hiding colliding ones. */\n allowOverlap: boolean;\n /**\n * Where the label sits relative to its anchor point (MapLibre `text-anchor`),\n * e.g. `\"top\"` places the text above the point. Ignored for line placement.\n */\n anchor: LabelAnchor;\n /** Horizontal label offset in ems (MapLibre `text-offset` x). */\n offsetX: number;\n /** Vertical label offset in ems (MapLibre `text-offset` y; positive is down). */\n offsetY: number;\n /** Label rotation in degrees clockwise (MapLibre `text-rotate`). */\n rotation: number;\n /** Maximum line width in ems before the label wraps (MapLibre `text-max-width`). */\n maxWidth: number;\n /** Letter-case transform applied to the label text (MapLibre `text-transform`). */\n transform: LabelTransform;\n /**\n * Render a numeric {@link field} with the locale's thousands and decimal\n * separators (issue #2336), so `1234567.5` labels as `1,234,567.5` instead\n * of running together. Non-numeric values are unaffected, and it is not\n * applied to {@link expression}, which formats its own output (MapLibre's\n * `number-format`, offered in the Expression Builder).\n */\n numberFormatEnabled: boolean;\n /** Decimal places kept while {@link numberFormatEnabled} is on (0-10). */\n numberDecimals: number;\n /**\n * BCP 47 tag picking the separators for {@link numberFormatEnabled}, from\n * {@link LABEL_NUMBER_LOCALES}. Empty (the default) follows the app's own\n * language, the way popup number fields do.\n */\n numberLocale: string;\n /**\n * How to handle features that share a label.\n *\n * - `\"off\"`: every feature is labeled (the historical behavior).\n * - `\"unique\"`: features stacked at the same point are collapsed to a single\n * label, so co-located points (e.g. several antennas at one cell site) do not\n * stack overlapping text.\n * - `\"concatenate\"`: co-located points are merged into one label that joins\n * their distinct {@link field} values, one per line.\n *\n * Applies to point layers rendered through the inline GeoJSON path and uses\n * {@link field} (not {@link expression}) as the label value.\n */\n dedupe: LabelDedupe;\n /**\n * Data-defined override for {@link size}: a MapLibre expression (JSON\n * string) producing a number, e.g. sizing labels by population. Empty means\n * \"use the literal {@link size}\". Like the other data-defined overrides\n * below, it reads source feature attributes, so it is skipped while\n * {@link dedupe} collapsing is active (the aggregated features carry only\n * the label value).\n */\n sizeExpression: string;\n /**\n * Data-defined override for {@link color}: a MapLibre expression (JSON\n * string) producing a color, e.g. coloring labels by category.\n */\n colorExpression: string;\n /**\n * Data-defined label opacity: a MapLibre expression (JSON string) producing\n * a number in 0..1. When set it replaces the layer-wide opacity for labels\n * (wrapping it would invalidate top-level `[\"zoom\"]` interpolations).\n */\n opacityExpression: string;\n /**\n * Per-feature label visibility: a MapLibre expression (JSON string)\n * producing a boolean. Features evaluating false get no label (e.g. hide\n * labels below an attribute threshold). Combined with the layer's other\n * feature filters.\n */\n visibilityExpression: string;\n /**\n * Per-feature placement priority: a MapLibre expression (JSON string)\n * producing a number, applied as `symbol-sort-key`. Labels with lower\n * values are placed first, so they win when space is tight.\n */\n priorityExpression: string;\n}\n\n/** MapLibre `text-anchor` positions offered for {@link LabelStyle.anchor}. */\nexport type LabelAnchor =\n | \"center\"\n | \"left\"\n | \"right\"\n | \"top\"\n | \"bottom\"\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\";\n\n/** Letter-case transform offered for {@link LabelStyle.transform}. */\nexport type LabelTransform = \"none\" | \"uppercase\" | \"lowercase\";\n\n/** Duplicate-label handling offered for {@link LabelStyle.dedupe}. */\nexport type LabelDedupe = \"off\" | \"unique\" | \"concatenate\";\n\n/**\n * Cartographic blend modes offered for {@link LayerStyle.blendMode}.\n *\n * The list is bounded by what WebGL *fixed-function* blending can express --\n * one `blendFunc` factor pair plus one `blendEquation` -- because that is the\n * only blend state MapLibre's renderer exposes, and it applies to colour and\n * alpha together. Two consequences shaped this list:\n *\n * - Modes needing the destination colour inside a fragment shader (`overlay`,\n * `color-dodge`, `soft-light`, `difference`, and the non-separable HSL modes)\n * are absent. MapLibre draws straight into the map framebuffer, which a\n * shader cannot sample, so computing them would need an extra copy pass.\n * - `darken` and `subtract` are absent even though `MIN` and\n * `FUNC_REVERSE_SUBTRACT` look like they would serve. Both also apply to the\n * alpha channel, where they drive the map canvas transparent: `MIN` takes the\n * whole canvas to `min(0, dst) = 0` everywhere the layer does not cover, and\n * `FUNC_REVERSE_SUBTRACT` leaves `dstA - srcA` inside it. Correcting either\n * needs `blendEquationSeparate` / `blendFuncSeparate`, which MapLibre's state\n * tracker does not offer.\n *\n * Every mode here leaves the canvas opaque and leaves the map untouched\n * wherever the layer contributes nothing. See `docs/user-guide/layers.md`.\n */\nexport const BLEND_MODES = [\"normal\", \"multiply\", \"screen\", \"lighten\", \"add\"] as const;\n\n/**\n * How a layer's pixels combine with the map beneath it. `\"normal\"` is ordinary\n * alpha compositing (the default, and what every layer did before blend modes\n * existed). See {@link BLEND_MODES}.\n */\nexport type BlendMode = (typeof BLEND_MODES)[number];\n\n/**\n * The mode a layer renders with unless it says otherwise. Declared separately\n * from {@link DEFAULT_LAYER_STYLE} so consumers get a non-optional\n * {@link BlendMode} to fall back to: `blendMode` is optional on\n * {@link LayerStyle}, so `DEFAULT_LAYER_STYLE.blendMode` is typed\n * `BlendMode | undefined` however it is initialized.\n */\nexport const DEFAULT_BLEND_MODE: BlendMode = \"normal\";\n\nexport interface LayerStyle {\n minZoom: number;\n maxZoom: number;\n fillColor: string;\n strokeColor: string;\n strokeWidth: number;\n /**\n * Unit the {@link strokeWidth} value is expressed in.\n *\n * - `\"pixels\"` (default): a constant screen-space width that never changes\n * with zoom — the historical behavior.\n * - `\"meters\"`: a ground-distance width, so the rendered line stays\n * proportional to the map scale (thicker zoomed in, thinner zoomed out),\n * matching QGIS \"map units\". Only affects line/polygon-outline rendering;\n * point/circle outlines remain pixel-based.\n */\n strokeWidthUnit: StrokeWidthUnit;\n /**\n * Opacity (0..1) of lines, polygon outlines and point outlines, applied on\n * top of the layer opacity, so a translucent outline can sit over an opaque\n * fill. A per-feature simplestyle `stroke-opacity` replaces it for features\n * that carry one, as `fill-opacity` replaces {@link fillOpacity}.\n */\n strokeOpacity: number;\n /**\n * Dash pattern of lines and polygon outlines: alternating dash and gap\n * lengths in multiples of the stroke width, as MapLibre's `line-dasharray`\n * takes them. Empty draws a solid line. See {@link STROKE_DASH_PRESETS}.\n */\n strokeDash: number[];\n /** Blur in pixels applied to lines and polygon outlines; 0 keeps them sharp. */\n strokeBlur: number;\n fillOpacity: number;\n circleRadius: number;\n textColor: string;\n textHaloColor: string;\n textHaloWidth: number;\n textSize: number;\n labels: LabelStyle;\n extrusionEnabled: boolean;\n extrusionColor: string;\n extrusionOpacity: number;\n extrusionHeightProperty: string;\n extrusionHeightScale: number;\n extrusionBase: number;\n /**\n * Attribute whose value, times {@link extrusionBaseScale}, is added to\n * {@link extrusionBase} per feature, so one layer can stack floors from a\n * `level` field. Empty keeps the flat base. Like the height, the base is an\n * altitude in metres, not a thickness: the roof still comes from the height.\n */\n extrusionBaseProperty: string;\n /** Multiplier for {@link extrusionBaseProperty} (e.g. metres per floor). */\n extrusionBaseScale: number;\n /**\n * When above 0, every feature is this many metres tall on top of its base\n * (roof = base + floor height), and the height property and expression are\n * ignored. 0 keeps the height as the roof's altitude.\n */\n extrusionFloorHeight: number;\n extrusionAdvancedStyleEnabled: boolean;\n extrusionColorExpression: string;\n extrusionHeightExpression: string;\n /**\n * Advanced-mode MapLibre expression for the extrusion base, as JSON. When it\n * parses it wins over {@link extrusionBase} and {@link extrusionBaseProperty}.\n */\n extrusionBaseExpression: string;\n /**\n * When true, a vector layer whose coordinates carry Z values (e.g. a GPX\n * track with elevations) renders in true 3D through the shared deck.gl\n * overlay instead of MapLibre's flat 2D layers, so features sit at their\n * own altitude. Orthogonal to {@link extrusionEnabled}, which extrudes flat\n * polygons by an attribute; only one of the two should be on at a time.\n */\n elevation3dEnabled: boolean;\n /** Multiplier applied to each coordinate's Z value (vertical exaggeration). */\n elevation3dVerticalScale: number;\n /** Constant altitude offset in meters added after the vertical scale. */\n elevation3dOffset: number;\n vectorStyleMode: VectorStyleMode;\n vectorStyleProperty: string;\n vectorStyleClassCount: number;\n vectorStyleColorRamp: string;\n vectorStyleClassificationScheme: string;\n vectorStyleStops: VectorStyleStop[];\n vectorStyleExpression: string;\n /**\n * Ordered rules for the `\"rule-based\"` {@link vectorStyleMode}. Compiled to a\n * MapLibre `case` color expression (first matching filter wins, catch-all\n * last). See {@link VectorRule}.\n */\n vectorRules: VectorRule[];\n /**\n * When true, the point circle radius (or line width) is sized by a numeric\n * field via an `interpolate` between {@link proportionalSizeMinValue} ..\n * {@link proportionalSizeMaxValue} mapped onto {@link proportionalSizeMinRadius}\n * .. {@link proportionalSizeMaxRadius} (QGIS \"graduated → size\" / proportional\n * symbols). Orthogonal to the color {@link vectorStyleMode}.\n */\n proportionalSizeEnabled: boolean;\n proportionalSizeProperty: string;\n proportionalSizeMinValue: number;\n proportionalSizeMaxValue: number;\n proportionalSizeMinRadius: number;\n proportionalSizeMaxRadius: number;\n /**\n * Polygon fill pattern. `\"none\"` keeps the flat fill; other values render a\n * recolorable sprite tile ({@link fillPatternColor}); `\"svg\"` rasterizes\n * {@link fillPatternSvg}. See {@link FillPattern}.\n */\n fillPattern: FillPattern;\n fillPatternColor: string;\n /** Raw SVG markup (or a data URL) used when {@link fillPattern} is `\"svg\"`. */\n fillPatternSvg: string;\n /**\n * When true, point features render as a marker icon ({@link markerShape})\n * instead of a plain circle. Built-in shapes are recolored via\n * {@link markerColor}; `\"custom\"` rasterizes {@link markerSvg}.\n */\n markerEnabled: boolean;\n markerShape: MarkerShape;\n markerColor: string;\n markerSize: number;\n /**\n * The custom marker image used when {@link markerShape} is `\"custom\"`: raw\n * SVG markup, a `data:` URL (SVG or a raster PNG/JPEG/GIF), or an `http(s)`\n * URL. Only SVG sources take {@link markerColor}; raster images draw as-is.\n */\n markerSvg: string;\n /**\n * When true, per-feature [simplestyle-spec](https://github.com/mapbox/simplestyle-spec)\n * properties (`fill`, `fill-opacity`, `stroke`, `stroke-width`,\n * `stroke-opacity`, `marker-color`) override the flat layer style on a\n * per-feature basis. Set automatically when a GeoJSON layer is added whose\n * features carry these properties (e.g. styled KML/KMZ), so embedded\n * symbology renders without manual configuration.\n */\n simpleStyleEnabled: boolean;\n /**\n * Per-feature chart symbology (QGIS-style diagrams). `\"none\"` disables it;\n * any other value renders one {@link DiagramType} chart per feature over the\n * layer's normal symbology, built from the numeric attributes in\n * {@link diagramFields}. See `@geolibre/core`'s `diagram.ts` helpers.\n */\n diagramType: DiagramType;\n /** Attributes (and their colors) charted by the diagram renderer, in order. */\n diagramFields: DiagramField[];\n /** How the per-feature diagram size is determined. */\n diagramSizeMode: DiagramSizeMode;\n /**\n * Diagram size in pixels: the rendered diameter/height for `\"fixed\"` sizing,\n * or the diameter/height of the largest feature for scaled sizing.\n */\n diagramSize: number;\n /** Numeric attribute driving `\"attribute\"` sizing (see {@link DiagramSizeMode}). */\n diagramSizeProperty: string;\n /** Minimum zoom at which diagrams are drawn, to avoid clutter when zoomed out. */\n diagramMinZoom: number;\n /**\n * When true, diagrams that would overlap an already-placed diagram on screen\n * are skipped (largest first), decluttering dense areas. Recomputed as the\n * view changes.\n */\n diagramDeclutter: boolean;\n pointRenderer: PointRenderer;\n heatmapRadius: number;\n heatmapIntensity: number;\n /** Built-in color ramp used by the heatmap density renderer. */\n heatmapColorRamp: string;\n /** Numeric feature property used as heatmap weight; blank gives every point equal weight. */\n heatmapWeightProperty: string;\n clusterRadius: number;\n clusterMaxZoom: number;\n /**\n * When true, the polygon fill renders *inverted*: the area outside the\n * features is filled (with {@link fillColor}/{@link fillOpacity}) and the\n * features themselves become holes, mirroring the QGIS \"Inverted polygons\"\n * renderer. Feature outlines still render normally. Only applies to layers\n * with polygon geometry; ignored while {@link extrusionEnabled} is on.\n */\n invertedFillEnabled: boolean;\n /**\n * Repeated decoration symbol drawn along line features and polygon outlines\n * (QGIS marker-line / arrow lines). `\"none\"` disables it. See\n * {@link LineDecoration}.\n */\n lineDecoration: LineDecoration;\n /**\n * Decoration symbol color (6-digit hex). An empty string inherits\n * {@link strokeColor} so decorations follow the stroke by default.\n */\n lineDecorationColor: string;\n /** Decoration symbol size in pixels. */\n lineDecorationSize: number;\n /** Distance between consecutive decoration symbols in pixels. */\n lineDecorationSpacing: number;\n /**\n * Per-feature derived geometry drawn over the layer's normal symbology\n * (QGIS geometry generator). `\"none\"` disables it. See\n * {@link GeometryGeneratorType}.\n */\n geometryGenerator: GeometryGeneratorType;\n /** Buffer distance in meters for the `\"buffer\"` generator. */\n geometryGeneratorBufferDistance: number;\n /**\n * Attribute driving the `\"buffer\"` generator's distance, so each feature is\n * buffered by its own value in meters (QGIS data-defined override on a\n * geometry-generator symbol). An empty string buffers every feature by the\n * flat {@link geometryGeneratorBufferDistance}, which also stands in for\n * features whose value is missing or non-numeric.\n */\n geometryGeneratorBufferProperty: string;\n /** Fill color (6-digit hex) for generated polygons and centroid points. */\n geometryGeneratorFillColor: string;\n /** Outline color (6-digit hex) for generated geometry. */\n geometryGeneratorStrokeColor: string;\n /** Outline width in pixels for generated geometry. */\n geometryGeneratorStrokeWidth: number;\n /** Fill opacity (0..1) for generated polygons and centroid points. */\n geometryGeneratorOpacity: number;\n /** Circle radius in pixels for generated centroid points. */\n geometryGeneratorCircleRadius: number;\n /**\n * Attribute driving the radius of generated centroid points, scaling them\n * between {@link geometryGeneratorSizeMinRadius} and\n * {@link geometryGeneratorSizeMaxRadius} across\n * {@link geometryGeneratorSizeMinValue} ..\n * {@link geometryGeneratorSizeMaxValue} (proportional symbols on the derived\n * centroids). An empty string draws every centroid at the flat\n * {@link geometryGeneratorCircleRadius}.\n *\n * Deliberately separate from {@link proportionalSizeProperty}: that one also\n * drives line width, so reusing it here would resize a polygon layer's\n * outlines as a side effect of sizing its centroids.\n */\n geometryGeneratorSizeProperty: string;\n geometryGeneratorSizeMinValue: number;\n geometryGeneratorSizeMaxValue: number;\n geometryGeneratorSizeMinRadius: number;\n geometryGeneratorSizeMaxRadius: number;\n rasterBrightnessMin: number;\n rasterBrightnessMax: number;\n rasterSaturation: number;\n rasterContrast: number;\n rasterHueRotate: number;\n /**\n * How this layer composites onto whatever is drawn beneath it. `\"normal\"`\n * (the default) is ordinary alpha compositing; `\"multiply\"` is the classic\n * cartographic case of laying colour over a hillshade so the relief still\n * reads through. Applies to the layer's own rendered geometry and raster\n * tiles, not to its labels, which stay legible on top. See\n * {@link BLEND_MODES} for the available modes and why the list is what it is.\n */\n blendMode?: BlendMode;\n}\n\nexport const DEFAULT_LAYER_STYLE: LayerStyle = {\n minZoom: 0,\n maxZoom: 24,\n fillColor: \"#3b82f6\",\n strokeColor: \"#1e40af\",\n strokeWidth: 2,\n strokeWidthUnit: \"pixels\",\n strokeOpacity: 1,\n strokeDash: [],\n strokeBlur: 0,\n fillOpacity: 0.6,\n circleRadius: 6,\n textColor: \"#111827\",\n textHaloColor: \"#ffffff\",\n textHaloWidth: 2,\n textSize: 16,\n labels: {\n enabled: false,\n field: \"\",\n expression: \"\",\n placement: \"point\",\n size: 13,\n color: \"#111827\",\n haloColor: \"#ffffff\",\n haloWidth: 1.5,\n minZoom: 0,\n maxZoom: 24,\n allowOverlap: false,\n anchor: \"center\",\n offsetX: 0,\n offsetY: 0,\n rotation: 0,\n maxWidth: 10,\n transform: \"none\",\n numberFormatEnabled: false,\n numberDecimals: 0,\n numberLocale: \"\",\n dedupe: \"off\",\n sizeExpression: \"\",\n colorExpression: \"\",\n opacityExpression: \"\",\n visibilityExpression: \"\",\n priorityExpression: \"\",\n },\n extrusionEnabled: false,\n extrusionColor: \"#3b82f6\",\n extrusionOpacity: 0.8,\n extrusionHeightProperty: \"height\",\n extrusionHeightScale: 1,\n extrusionBase: 0,\n extrusionBaseProperty: \"\",\n extrusionBaseScale: 1,\n extrusionFloorHeight: 0,\n extrusionAdvancedStyleEnabled: false,\n extrusionColorExpression: \"\",\n extrusionHeightExpression: \"\",\n extrusionBaseExpression: \"\",\n elevation3dEnabled: false,\n elevation3dVerticalScale: 1,\n elevation3dOffset: 0,\n vectorStyleMode: \"single\",\n vectorStyleProperty: \"\",\n vectorStyleClassCount: 5,\n vectorStyleColorRamp: \"viridis\",\n vectorStyleClassificationScheme: \"equal-interval\",\n vectorStyleStops: [\n { value: 0, color: \"#dbeafe\" },\n { value: 1, color: \"#2563eb\" },\n ],\n vectorStyleExpression: \"\",\n vectorRules: [],\n proportionalSizeEnabled: false,\n proportionalSizeProperty: \"\",\n proportionalSizeMinValue: 0,\n proportionalSizeMaxValue: 100,\n proportionalSizeMinRadius: 4,\n proportionalSizeMaxRadius: 24,\n fillPattern: \"none\",\n fillPatternColor: \"#1e40af\",\n fillPatternSvg: \"\",\n markerEnabled: false,\n markerShape: \"circle\",\n markerColor: \"#3b82f6\",\n markerSize: 18,\n markerSvg: \"\",\n simpleStyleEnabled: false,\n diagramType: \"none\",\n diagramFields: [],\n diagramSizeMode: \"fixed\",\n diagramSize: 30,\n diagramSizeProperty: \"\",\n diagramMinZoom: 0,\n diagramDeclutter: false,\n pointRenderer: \"single\",\n heatmapRadius: 30,\n heatmapIntensity: 1,\n heatmapColorRamp: \"turbo\",\n heatmapWeightProperty: \"\",\n clusterRadius: 50,\n clusterMaxZoom: 14,\n invertedFillEnabled: false,\n lineDecoration: \"none\",\n lineDecorationColor: \"\",\n lineDecorationSize: 12,\n lineDecorationSpacing: 80,\n geometryGenerator: \"none\",\n geometryGeneratorBufferDistance: 1000,\n geometryGeneratorBufferProperty: \"\",\n geometryGeneratorFillColor: \"#f59e0b\",\n geometryGeneratorStrokeColor: \"#b45309\",\n geometryGeneratorStrokeWidth: 2,\n geometryGeneratorOpacity: 0.4,\n geometryGeneratorCircleRadius: 5,\n geometryGeneratorSizeProperty: \"\",\n geometryGeneratorSizeMinValue: 0,\n geometryGeneratorSizeMaxValue: 100,\n geometryGeneratorSizeMinRadius: 4,\n geometryGeneratorSizeMaxRadius: 24,\n rasterBrightnessMin: 0,\n rasterBrightnessMax: 1,\n rasterSaturation: 0,\n rasterContrast: 0,\n rasterHueRotate: 0,\n blendMode: DEFAULT_BLEND_MODE,\n};\n\n/**\n * Read a layer style property, falling back to the shared default when the\n * layer does not define it. Shared by `@geolibre/map` and the desktop app so\n * the two consumers cannot drift.\n */\nexport function styleValue<K extends keyof LayerStyle>(style: LayerStyle, key: K): LayerStyle[K] {\n return style[key] ?? DEFAULT_LAYER_STYLE[key];\n}\n\n/**\n * Feature-count threshold above which a local vector (GeoJSON) layer is rendered\n * through client-side vector tiles (geojson-vt / supercluster served by a custom\n * MapLibre protocol) instead of one in-memory geojson source pushed via\n * `setData`. Small layers stay on the simpler inline path. Mirrors the\n * `MAX_CEREUS_FEATURES` precedent in the desktop SQL engine.\n */\nexport const LARGE_VECTOR_FEATURE_THRESHOLD = 50_000;\n\n/**\n * Decide whether a vector layer should use the tiled rendering path.\n *\n * @param data - The layer's feature collection or GeoArrow batch (may be undefined for\n * non-vector layers).\n * @returns `true` when the feature count exceeds\n * {@link LARGE_VECTOR_FEATURE_THRESHOLD}.\n */\nexport function shouldUseTiledRendering(\n data: GeoJSON.FeatureCollection | GeoArrowBatch | undefined,\n): boolean {\n if (!data) return false;\n if (\"featureCount\" in data && typeof data.featureCount === \"number\") {\n return data.featureCount > LARGE_VECTOR_FEATURE_THRESHOLD;\n }\n if (\"features\" in data && Array.isArray(data.features)) {\n return data.features.length > LARGE_VECTOR_FEATURE_THRESHOLD;\n }\n return false;\n}\n\n/**\n * Match statistics from the last time a {@link LayerJoin} was applied, shown in\n * the Joins UI so silent key mismatches are visible (mirrors QGIS's join\n * feedback). Recomputed on every apply; persisted harmlessly with the join.\n */\nexport interface LayerJoinStats {\n /** Target features whose key matched a join-table row. */\n matchedCount: number;\n /** Target features with no matching join-table row (their joined columns are null). */\n unmatchedTargetCount: number;\n /** Join-table rows (with a non-empty key) that matched no target feature. */\n unmatchedJoinCount: number;\n}\n\n/**\n * A persistent attribute join attached to a layer (QGIS Layer Properties →\n * Joins): a live left join that augments the layer's attribute table with\n * columns from another layer (typically a geometry-less table added via\n * Delimited Text with no coordinate fields) matched on a key field. Unlike the\n * Processing → Vector attribute join, the layer keeps its identity — styles,\n * labels, and position — and the joined columns refresh whenever the join\n * table's data changes. Definitions persist in `.geolibre.json` and re-resolve\n * on project load. See `joins.ts` for the engine.\n */\nexport interface LayerJoin {\n /** Stable id for list edits. */\n id: string;\n /** Id of the layer providing the joined columns (its geometry is ignored). */\n joinLayerId: string;\n /** Key field on the layer that owns this join. */\n targetField: string;\n /** Key field on the join layer. */\n joinField: string;\n /**\n * Join-layer fields to bring over; `undefined` brings every field except the\n * key. Names are the join layer's own (pre-prefix) field names.\n */\n fields?: string[];\n /** Optional prefix prepended to every joined column name (as in QGIS). */\n prefix?: string;\n /** `false` detaches the joined columns without deleting the definition. */\n enabled?: boolean;\n /**\n * Bookkeeping written by the engine: the output column names this join added\n * to the layer's features. Applying joins strips these first, which makes\n * re-application idempotent without keeping a duplicate copy of the base\n * data (base columns always win a name collision, so a joined column never\n * shadows one). Not user-editable.\n */\n addedFields?: string[];\n /** Last-run match statistics; see {@link LayerJoinStats}. */\n stats?: LayerJoinStats;\n}\n\n/** Edit-widget kinds the Attribute Form designer can assign to a field. */\nexport type AttributeFormWidget = \"text\" | \"number\" | \"range\" | \"checkbox\" | \"date\" | \"valueMap\";\n\n/** One selectable entry of a `valueMap` widget (stored value + display label). */\nexport interface AttributeFormValueMapEntry {\n /** The value written to the feature property (compared as a string). */\n value: string;\n /** Human-readable label shown in the dropdown; defaults to {@link value}. */\n label?: string;\n}\n\n/**\n * Per-field configuration authored in the Attribute Form designer (layer\n * properties → Attributes Form, QGIS-style): which edit widget the attribute\n * editing surfaces render for the field, plus optional expression-based\n * constraints and conditional visibility. Consumed by the attribute table's\n * inline editor and the Field Collection capture form; helpers live in\n * `attribute-form.ts`.\n */\nexport interface AttributeFormFieldConfig {\n /** Feature property key this configuration applies to. */\n field: string;\n widget: AttributeFormWidget;\n /** Display label override shown in forms instead of the raw field name. */\n alias?: string;\n /**\n * When true, a null/empty value fails validation. Checkbox widgets are\n * exempt: unchecked is a valid state, not a missing value.\n */\n required?: boolean;\n /** Dropdown entries for the `valueMap` widget. */\n valueMap?: AttributeFormValueMapEntry[];\n /**\n * Declared storage type of the field. When set, coercion follows it instead\n * of guessing from the value map (a text field whose codes are \"1\" and \"2\"\n * still stores strings), and validation rejects values of another type or,\n * for `integer`, fractional numbers. Service-derived configs (ArcGIS domains)\n * set it from the field's declared type; designer-authored configs leave it\n * unset.\n */\n valueType?: \"string\" | \"number\" | \"integer\";\n /** Lower bound for `number`/`range` widgets (inclusive). */\n min?: number;\n /** Upper bound for `number`/`range` widgets (inclusive). */\n max?: number;\n /** Step for the `range` widget's input. */\n step?: number;\n /**\n * Boolean MapLibre expression that must evaluate to `true` against the\n * feature's (candidate) properties for the value to be accepted, e.g.\n * `[\">\", [\"get\", \"population\"], 0]`. Stored as the expression source string\n * the Expression Builder edits.\n */\n constraintExpression?: string;\n /** Human-readable message shown when the constraint fails. */\n constraintDescription?: string;\n /**\n * Boolean MapLibre expression controlling whether the field is shown in\n * attribute forms; `false` hides the field (and skips its validation).\n * Empty/invalid expressions fail open so a typo cannot hide data entry.\n */\n visibilityExpression?: string;\n}\n\n/** The Attribute Form designer's whole per-layer configuration. */\nexport interface AttributeFormConfig {\n fields: AttributeFormFieldConfig[];\n}\n\n/** Point of contact recorded in a layer's {@link LayerDescriptiveMetadata}. */\nexport interface LayerMetadataContact {\n name?: string;\n email?: string;\n organization?: string;\n}\n\n/** A related resource recorded in a layer's {@link LayerDescriptiveMetadata}. */\nexport interface LayerMetadataLink {\n /** Absolute URL of the resource. */\n href: string;\n /** Relation type (STAC/IANA link relation such as `\"license\"` or `\"related\"`). */\n rel?: string;\n /** Human-readable label. */\n title?: string;\n}\n\n/**\n * Time span the layer's data covers. Each bound is an ISO 8601 date\n * (`YYYY-MM-DD`) or date-time; either may be omitted for an open interval.\n */\nexport interface LayerMetadataTemporalExtent {\n start?: string;\n end?: string;\n}\n\n/**\n * User-authored, catalog-style description of a layer (issue #2858), edited in\n * the layer's Metadata dialog and exported as a STAC Item. Distinct from the\n * internal {@link GeoLibreLayer.metadata} record the renderers and plugins key\n * off: every field here is free-form documentation that changes nothing about\n * how the layer renders. Helpers live in `layer-descriptive-metadata.ts`.\n */\nexport interface LayerDescriptiveMetadata {\n title?: string;\n /** Free-text summary of the data (STAC `description`, ISO abstract). */\n abstract?: string;\n keywords?: string[];\n /** SPDX license identifier (e.g. `\"CC-BY-4.0\"`) or free text. */\n license?: string;\n /** Credit line for the data's producers. */\n attribution?: string;\n contact?: LayerMetadataContact;\n /** How the data was produced: sources and processing steps. */\n lineage?: string;\n temporalExtent?: LayerMetadataTemporalExtent;\n links?: LayerMetadataLink[];\n}\n\n/**\n * How a popup renders one field's value (issue #2113). `\"auto\"` reproduces the\n * untyped rendering the Identify popup has always done — sanitized KML\n * `description` markup, an inline `data:image/*;base64` value as a thumbnail,\n * anything else stringified. The remaining kinds are explicit author choices\n * so a popup never has to guess from the value.\n */\nexport type PopupFieldKind = \"auto\" | \"text\" | \"number\" | \"date\" | \"link\" | \"image\";\n\n/** How a `\"date\"` field's value is written out. */\nexport type PopupDateFormat = \"date\" | \"datetime\" | \"time\" | \"iso\" | \"year\";\n\n/** Value formatting for one popup field. Every part is optional. */\nexport interface PopupFieldFormat {\n /** Fixed number of decimals for a `\"number\"` field. */\n decimals?: number;\n /** Group thousands with the locale's separator (`\"number\"` fields). */\n thousands?: boolean;\n /** Date rendering for a `\"date\"` field; defaults to `\"date\"`. */\n dateFormat?: PopupDateFormat;\n /** Text placed before the formatted value. */\n prefix?: string;\n /** Text placed after the formatted value, e.g. a unit suffix. */\n suffix?: string;\n /** Link text for a `\"link\"` field; the value itself is used when unset. */\n linkLabel?: string;\n}\n\n/** One field's entry in a layer's popup configuration. */\nexport interface PopupFieldConfig {\n /** Feature property key. */\n field: string;\n /** Display label shown instead of the raw field name. */\n label?: string;\n /** How the value renders; defaults to `\"auto\"`. */\n kind?: PopupFieldKind;\n format?: PopupFieldFormat;\n /** Include this field in the hover tooltip's short subset. */\n hover?: boolean;\n}\n\n/**\n * Per-layer configuration for what a viewer sees when they interact with a\n * feature (issue #2113): which fields the Identify popup shows, in what order,\n * under what labels and formatting, plus an optional hover tooltip.\n *\n * A layer with no `popup` block behaves exactly as it did before this existed:\n * the layer name as the heading, then every property as a key/value row.\n * {@link GeoLibreLayer.fieldVisibility} stays authoritative — a `\"hidden\"` or\n * `\"excluded\"` field is dropped even when a popup config names it.\n */\nexport interface LayerPopupConfig {\n /** `false` suppresses the Identify popup for this layer. Defaults to `true`. */\n click?: boolean;\n /** `true` shows a hover tooltip built from the `hover` fields. Defaults to `false`. */\n hover?: boolean;\n /** Field whose value titles the popup instead of the layer name. */\n titleField?: string;\n /**\n * MapLibre expression source producing the popup title. Wins over\n * {@link titleField}; when it fails or produces nothing the title falls\n * through to `titleField`, and only then to the layer name.\n */\n titleExpression?: string;\n /**\n * MapLibre expression source producing the whole popup body as text, for\n * authors who want a sentence rather than a table. When it evaluates, it\n * replaces the field rows.\n */\n bodyExpression?: string;\n /** `false` drops the synthetic `id` row. Defaults to `true`. */\n showFeatureId?: boolean;\n /**\n * Widest the click popup may grow, in CSS pixels. Unset keeps the default\n * (520px, or 420px for a popup carrying an image). Clamped to the range\n * `resolvePopupMaxWidth` enforces and always capped by the viewport, so a\n * value wider than the window still leaves the map usable.\n */\n maxWidth?: number;\n /**\n * Tallest an `\"image\"` field's thumbnail may draw inside the popup, in CSS\n * pixels. Unset keeps the default (`min(50vh, 420px)`). Clamped by\n * `resolvePopupImageHeight`. Pair it with {@link maxWidth} for a\n * bigger picture: the thumbnail keeps its aspect ratio, so widening the\n * popup is what lets a landscape photo use the extra height.\n */\n imageHeight?: number;\n /**\n * The fields to show and their order. An empty or absent list keeps today's\n * behavior: every visible property, in the feature's own key order.\n */\n fields?: PopupFieldConfig[];\n}\n\n/**\n * A virtual field attached to a vector layer (QGIS Field Calculator → \"Create\n * virtual field\", issue #1321): a column defined by a MapLibre expression that\n * recomputes live instead of being written once as static values. The engine\n * (`virtual-fields.ts`) materializes the computed values into the layer's\n * feature properties — so the attribute table, Expression Builder,\n * data-driven styling, labels, and selection all see the column with no\n * further wiring — and re-derives them whenever the layer's data (or its\n * joins) change. Definitions persist in `.geolibre.json` and re-resolve on\n * project load; the expression is a declarative MapLibre expression (never\n * arbitrary code), so re-evaluating it from a shared project file is safe.\n */\nexport interface LayerVirtualField {\n /** Stable id for list edits. */\n id: string;\n /** The output column name. A name already taken by a base column is skipped. */\n name: string;\n /**\n * MapLibre expression source (JSON text, e.g. `[\"/\", [\"get\", \"pop\"],\n * [\"get\", \"area_km2\"]]`) evaluated against each feature.\n */\n expression: string;\n /** `false` detaches the computed column without deleting the definition. */\n enabled?: boolean;\n /**\n * Bookkeeping written by the engine: the column name actually materialized\n * on the last apply, absent when the field was disabled, failed to compile,\n * or was skipped because the name collided with an existing column.\n * Applying virtual fields strips these first, which makes re-application\n * idempotent (an existing column is never shadowed, so stripping exactly\n * restores the pre-apply properties). Not user-editable.\n */\n addedField?: string;\n /** Compile error from the last apply, when the expression failed to parse. */\n error?: string;\n /**\n * Features whose evaluation threw at runtime on the last apply (their cell\n * is null). Surfaced so the UI can warn without one bad feature aborting\n * the whole column.\n */\n errorCount?: number;\n}\n\n/**\n * Quick filters (issue #2114): the data-driven filter controls a layer offers\n * in its Quick Filters section. What persists is the *control state* — field,\n * kind, chosen values — not the compiled output; `quick-filters.ts` compiles it\n * to a MapLibre filter at sync time so a saved filter can always be reopened\n * and edited.\n */\n\n/** Which control a quick filter renders, and how it compiles. */\nexport type QuickFilterKind = \"categorical\" | \"range\" | \"date\" | \"text\";\n\n/** The comparison a `text` quick filter applies (always case-insensitive). */\nexport type QuickFilterTextOperator = \"contains\" | \"startsWith\" | \"equals\";\n\n/**\n * How a date field stores its values, deciding whether a `date` quick filter\n * compares ISO text or epoch numbers. `iso` covers both `YYYY-MM-DD` and full\n * `YYYY-MM-DDTHH:MM:SSZ` timestamps: only the leading `YYYY-MM-DD` slice is\n * compared, so a trailing time, a milliseconds fraction, or a `Z` cannot break\n * a boundary. Mirrors (deliberately, in a smaller form) the value kinds the\n * Time Slider's `detectValueKind` reports.\n */\nexport type QuickFilterDateKind = \"iso\" | \"epochMs\" | \"epochS\";\n\n/** A single filter control persisted on a layer. */\nexport interface LayerQuickFilter {\n /** Stable id, used as the React key and for edits/removal. */\n id: string;\n /** The feature property this control narrows. */\n field: string;\n kind: QuickFilterKind;\n /**\n * `false` keeps the control configured but inert, so a filter can be muted\n * without losing the values chosen for it. Defaults to enabled.\n */\n enabled?: boolean;\n /**\n * `categorical`: the chosen values. An empty (or omitted) selection means\n * \"every value\" and compiles to nothing — unchecking the last box clears the\n * filter rather than emptying the map.\n */\n values?: (string | number | boolean)[];\n /** `range`: inclusive bounds. `null`/omitted leaves that side open. */\n min?: number | null;\n max?: number | null;\n /**\n * `date`: inclusive `YYYY-MM-DD` bounds. The end day is included in full, so\n * a timestamp field filtered to a single day keeps that whole day.\n */\n start?: string | null;\n end?: string | null;\n /** `date`: how the field stores its values. Defaults to `iso`. */\n dateKind?: QuickFilterDateKind;\n /** `text`: the comparison. Defaults to `contains`. */\n operator?: QuickFilterTextOperator;\n /** `text`: the needle. Blank means no constraint. */\n text?: string;\n}\n\n/**\n * Floor picker (issue #3341): which floors of a multi-level layer are drawn.\n *\n * `all` (the default) draws every floor; `only` draws the chosen one; `below`\n * draws it and every floor under it, the cutaway view of a building with the\n * upper storeys lifted off.\n */\nexport type LevelFilterMode = \"all\" | \"only\" | \"below\";\n\n/**\n * A layer linked to the on-map floor picker. The picker lists the levels found\n * in {@link field} across every linked layer and writes the choice back here.\n *\n * {@link values} holds the raw attribute values that match the choice, so the\n * compiled filter is a plain `in` test every renderer evaluates the same way,\n * even for OpenStreetMap's multi-level values such as `\"0;1\"` or `\"0-2\"`.\n */\nexport interface LayerLevelFilter {\n /** The feature property that holds the floor, e.g. `level` or `ordinal`. */\n field: string;\n /** Which floors are drawn. Defaults to `all`. */\n mode?: LevelFilterMode;\n /** The chosen level, as the picker labels it (`\"-1\"`, `\"0\"`, `\"G\"`). */\n level?: string | null;\n /**\n * The raw `field` values that match {@link level} under {@link mode},\n * resolved from the layer's features when the choice is made. Ignored in\n * `all` mode. Absent while unresolved, which draws every floor until the\n * picker resolves it.\n */\n values?: (string | number)[];\n}\n\n/** Persisted refresh policy and most recent synchronization result for a layer. */\nexport interface LayerConnection {\n /** Owning layer id. Repeated here so records remain self-describing when exported. */\n layerId: string;\n /** Automatic refresh cadence in seconds, or null for manual synchronization only. */\n interval: number | null;\n /** ISO timestamp of the most recent successful synchronization. */\n lastSyncedAt: string | null;\n /** Most recent synchronization error. Cleared by a successful synchronization. */\n lastError: string | null;\n /** Whether a failed synchronization retains the last good data or clears it. */\n onFailure: \"keep-last\" | \"clear\";\n}\n\n/**\n * Configuration for automatic feature editor tracking (Issue #1677).\n * Maintains `created_by`, `created_at`, `edited_by`, `edited_at` fields\n * automatically when features are created or updated.\n */\nexport interface EditorTrackingConfig {\n /** `true` enables automatic creation/edit timestamp and author stamping. */\n enabled: boolean;\n /** Field name for creation author (default `\"created_by\"`). */\n createdByField?: string;\n /** Field name for creation timestamp (default `\"created_at\"`). */\n createdAtField?: string;\n /** Field name for last-edit author (default `\"edited_by\"`). */\n editedByField?: string;\n /** Field name for last-edit timestamp (default `\"edited_at\"`). */\n editedAtField?: string;\n}\n\n/**\n * Visibility of a layer's attribute field.\n * - \"hidden\": Not shown in the attribute table, identify popup, tooltips, or field pickers, but remains in the data.\n * - \"excluded\": Removed entirely from the data when the project is shared or exported.\n */\nexport type FieldVisibility = \"hidden\" | \"excluded\";\n\n/**\n * Explicit capability flags defining what actions a user/session may perform on a layer.\n * Any omitted capability falls back to the inferred default behavior for that layer's source.\n */\nexport interface LayerCapabilities {\n /** Can the layer's features or attributes be queried / identified. */\n query?: boolean;\n /** Can new features be created/added to the layer. */\n create?: boolean;\n /** Can existing features/attributes be modified. */\n update?: boolean;\n /** Can features be deleted from the layer. */\n delete?: boolean;\n /** Can the layer data/symbology be exported/downloaded. */\n export?: boolean;\n}\n\n/**\n * Application privilege identifiers defining discrete capabilities in GeoLibre.\n */\nexport type AppPrivilege =\n | \"layers:edit\"\n | \"layers:add-remote\"\n | \"layers:add-local\"\n | \"processing:run\"\n | \"processing:sidecar\"\n | \"project:save\"\n | \"project:share\"\n | \"project:share-public\"\n | \"plugins:install\"\n | \"assistant:use\"\n | \"connections:manage\"\n | \"export:data\"\n | \"export:image\"\n | \"settings:manage\";\n\n/**\n * Standard named roles bundling application privileges.\n */\nexport type AppRole = \"viewer\" | \"editor\" | \"publisher\" | \"administrator\" | \"custom\";\n\n/**\n * Ephemeral application capabilities state defining the active role, effective privileges,\n * and optional restriction reason for the current session/deployment.\n */\nexport interface AppCapabilities {\n /**\n * The assigned application role. `custom` once an ad-hoc `grantAppPrivilege` /\n * `revokeAppPrivilege` has moved the set away from the bundle a named role\n * defines, so the role never claims a shape the privileges do not have.\n */\n role: AppRole;\n /** List of granted privileges for the active role or custom configuration. */\n privileges: AppPrivilege[];\n /**\n * Optional human-readable reason covering the whole set (e.g. \"Action disabled\n * by deployment policy\"), used for any privilege without its own.\n */\n reason?: string;\n /**\n * Per-privilege reasons, and why they exist: two privileges can be withheld by\n * different causes — a role bundle plus a licence limit, say — and a single\n * `reason` would make the second revocation relabel the first, so every gate\n * would explain itself with whichever cause happened to be recorded last.\n * Takes precedence over `reason` for the privileges it names.\n */\n privilegeReasons?: Partial<Record<AppPrivilege, string>>;\n}\n\nexport interface GeoLibreLayer {\n id: string;\n name: string;\n type: LayerType;\n source: Record<string, unknown>;\n visible: boolean;\n opacity: number;\n style: LayerStyle;\n metadata: Record<string, unknown>;\n beforeId?: string;\n geojson?: FeatureCollection;\n /**\n * Optional binary GeoArrow representation for high-performance vector rendering,\n * zero-copy Web Worker vector tiling, and compact in-memory storage.\n */\n geoarrow?: GeoArrowBatch;\n /** A SQL Server write needs a successful table reread before it may be submitted again. */\n mssqlWritebackPending?: boolean;\n /**\n * Explicit capability set for the layer (query, create, update, delete, export).\n * Unset capabilities default to the inferred behavior for the layer's source kind.\n */\n capabilities?: LayerCapabilities;\n /**\n * Automatic editor tracking configuration for feature creation/updates.\n */\n editorTracking?: EditorTrackingConfig;\n /**\n * Field-level visibility overrides. Fields marked as \"excluded\" are physically\n * removed from the data during export and sharing.\n */\n fieldVisibility?: Record<string, FieldVisibility>;\n /**\n * Per-field edit-widget, constraint, and visibility configuration authored\n * in the Attribute Form designer. Applied by the attribute editing surfaces\n * (attribute table inline editor, Field Collection capture form); persists\n * with the project like {@link joins}.\n */\n attributeForm?: AttributeFormConfig;\n /**\n * Popup and hover-tooltip design for this layer, authored in the Style\n * panel's Popup section. Absent means the default full-property dump.\n */\n popup?: LayerPopupConfig;\n /**\n * User-authored catalog metadata (title, abstract, keywords, license,\n * contact, lineage, temporal extent, links), edited in the layer's Metadata\n * dialog. Absent when nothing has been entered; an empty record is never\n * written to a project file.\n */\n descriptiveMetadata?: LayerDescriptiveMetadata;\n /**\n * Persistent attribute joins applied to this layer's features, in order.\n * The joined columns are materialized into `geojson` feature properties (so\n * the attribute table, Expression Builder, styling, and labels all see them)\n * and re-derived whenever the layer's or a join table's data changes.\n */\n joins?: LayerJoin[];\n /**\n * Expression-backed virtual fields computed for this layer's features, in\n * order. Applied after joins (so an expression can read joined columns) and\n * materialized into `geojson` feature properties; re-derived whenever the\n * layer's data changes. See {@link LayerVirtualField}.\n */\n virtualFields?: LayerVirtualField[];\n /**\n * Transient MapLibre filter expression applied on top of every rendered\n * sub-layer's geometry filter. The Time Slider plugin sets this on a bound\n * vector layer so scrubbing the timeline narrows the visible features to the\n * current time window, while the layer's own styling and opacity stay\n * untouched. It is derived from the slider's current date, so it is NOT\n * persisted (stripped by `prepareLayerForSave`); the binding config lives in\n * `metadata.timeBinding` and the filter is recomputed live on the next\n * activation. `undefined` means no time filter is applied.\n */\n timeFilter?: unknown[];\n /** Transient MapLibre expression applied by the iframe embed API. */\n embedFilter?: unknown[];\n /**\n * Project-persisted boolean MapLibre expression that narrows the features\n * rendered for this layer. Unlike a selection, this leaves the source data\n * intact and keeps non-matching features hidden until the filter is cleared.\n * It is composed with transient filters, quick filters, and rule visibility\n * by the map renderers.\n */\n filterExpression?: unknown[];\n /**\n * Data-driven filter controls authored in the layer's Quick Filters section\n * (issue #2114). Unlike {@link timeFilter} and {@link embedFilter} this is\n * persisted control *state*, not a compiled expression: `@geolibre/map`\n * compiles it at sync time (see `compileQuickFilters`) and combines the\n * result with the transient filters and the rule-based visibility filter, so\n * a host page's filter and a user's filter narrow the layer together.\n */\n quickFilters?: LayerQuickFilter[];\n /**\n * Floor picker link (issue #3341). Persisted control state: the picker\n * compiles it into the layer's filter alongside {@link filterExpression} and\n * {@link quickFilters}, so it narrows the layer together with the Time Slider\n * window and every other filter.\n */\n levelFilter?: LayerLevelFilter;\n sourcePath?: string;\n /**\n * Id of the {@link LayerGroup} this layer belongs to, or `undefined` when the\n * layer sits at the top level of the layer panel. Layers sharing a `groupId`\n * are kept contiguous in the store's flat `layers` array so the group renders\n * as one block; see `@geolibre/core`'s `layer-groups` helpers.\n */\n groupId?: string;\n /**\n * Project-persisted connection policy for reloadable layers. Runtime timers\n * are reconstructed from this record when a project opens.\n */\n connection?: LayerConnection;\n}\n\n/**\n * Options for {@link AppState.addTileLayer}: a native raster tile layer (XYZ,\n * WMS, or WMTS) that appears in the Layers panel and persists with the project,\n * just like a layer added through the Add Data dialog. Mirrors the raster\n * `source` fields MapLibre understands so an external plugin can register tile\n * layers without touching the map directly.\n */\nexport interface AddTileLayerOptions {\n /**\n * One or more XYZ tile URL templates (with `{x}`/`{y}`/`{z}` placeholders).\n * At least one non-empty template is required, or the layer renders nothing.\n */\n tiles: string[];\n /**\n * Layer discriminator, controlling how the layer is labelled and (for WMS)\n * dev-server proxied. Defaults to `\"xyz\"`. The layer's `source.type` is\n * always `\"raster\"`, so any other value (such as `\"vector-tiles\"` from an\n * untyped JS caller) throws rather than persisting a mislabelled source.\n */\n type?: \"xyz\" | \"wms\" | \"wmts\" | \"raster\";\n /** Service or base URL recorded on the source for display and restore. */\n url?: string;\n /** Tile size in pixels (default 256). */\n tileSize?: number;\n /** Attribution string shown in the map's attribution control. */\n attribution?: string;\n /** Visible extent as `[west, south, east, north]` in WGS84 degrees. */\n bounds?: [number, number, number, number];\n /** Minimum zoom at which tiles are requested. */\n minzoom?: number;\n /** Maximum zoom at which tiles are requested. */\n maxzoom?: number;\n /**\n * Tile y-axis scheme. `\"tms\"` flips the row. A `{-y}` token is a separate\n * flip and does not set this field; the two flips cancel when a tile is\n * requested. Defaults to `\"xyz\"`.\n */\n scheme?: \"xyz\" | \"tms\";\n /** Initial visibility (default true). */\n visible?: boolean;\n /** Initial opacity in [0, 1] (default 1). */\n opacity?: number;\n /**\n * Extra source fields merged onto the layer's `source` (e.g. the WMS\n * `layers`/`styles`/`format` recorded for restore). The required `type`,\n * `tiles`, and `tileSize` always win over keys supplied here.\n */\n source?: Record<string, unknown>;\n /** Extra metadata merged onto the layer record. */\n metadata?: Record<string, unknown>;\n}\n\n/**\n * A named, collapsible folder in the layer panel that organizes a contiguous\n * run of layers. Groups may be nested by referencing another group as their\n * parent; the root groups omit `parentId`.\n *\n * The group's `visible` flag and `opacity` multiplier are folded into each\n * child layer's effective render state by `applyGroupEffects` before the map\n * syncs, so children keep their own stored `visible`/`opacity` values.\n */\nexport interface LayerGroup {\n id: string;\n name: string;\n /** Parent folder id, or undefined when this folder is at the panel root. */\n parentId?: string;\n /** When true, the group's children are hidden in the panel (not on the map). */\n collapsed: boolean;\n /** Group-level visibility; ANDed with each child layer's own visibility. */\n visible: boolean;\n /** Group-level opacity in [0, 1]; multiplied into each child's opacity. */\n opacity: number;\n}\n\n/**\n * Metadata `sourceKind` marking a live SQL query layer: a GeoJSON-backed layer\n * created from a SQL Workspace result whose DuckDB statement is stored on the\n * layer metadata and re-executed on refresh. Defined here so the desktop app\n * (which owns the query/refresh logic) and `@geolibre/plugins` (which excludes\n * these layers from in-place geometry editing) share one value.\n */\nexport const SQL_QUERY_SOURCE_KIND = \"sql-query\";\n\n/**\n * Metadata `sourceKind` marking a NetCDF/HDF grid baked into an `image` overlay,\n * as opposed to the KML ground overlays that otherwise use that layer type.\n *\n * Defined here so `@geolibre/map` (which must not run its feature-query identify\n * on these) and the desktop app (which owns the symbology panel, the pixel\n * readout, and the spectral profile) share one value.\n */\nexport const NETCDF_IMAGE_SOURCE_KIND = \"netcdf-image\";\n\n/**\n * Detect a DuckDB query layer rendered through the plugin's external deck.gl\n * overlay. Shared by `@geolibre/map`, `@geolibre/plugins`, and the desktop\n * app so the detection criteria cannot drift.\n */\nexport function isDuckDBQueryLayer(\n layer: Pick<GeoLibreLayer, \"metadata\" | \"type\"> | undefined,\n): boolean {\n return (\n layer?.type === \"duckdb-query\" &&\n layer.metadata.sourceKind === \"duckdb-query\" &&\n layer.metadata.externalDeckLayer === true\n );\n}\n\nexport interface MapViewState {\n center: [number, number];\n zoom: number;\n bearing: number;\n pitch: number;\n bbox?: [number, number, number, number];\n}\n\n/**\n * Multi-map (split/grid) support. The workspace can show several map panes in a\n * `rows x cols` grid. Pane 0 is always the *primary* map: it keeps the existing\n * single-map wiring (the global `mapView` + `basemap*` fields, the layer/style\n * panels, plugins, deck.gl). Panes 1..N are *secondary* maps described by\n * `SecondaryMapView` records: every pane shares the same basemap and the same\n * `layers`, but each secondary pane may override which layers are visible so\n * different panes can show different layers. When `syncView` is on,\n * panning/zooming any pane mirrors the camera to every other pane.\n */\nexport interface MapGridLayout {\n /** Grid rows (>= 1). */\n rows: number;\n /** Grid columns (>= 1). `rows * cols` is the total number of panes. */\n cols: number;\n /** When true, all panes share a synchronized camera. */\n syncView: boolean;\n}\n\n/**\n * Which engine draws a map pane.\n *\n * `\"maplibre\"` is the 2D MapLibre GL map that owns the app's plugin, styling,\n * and deck.gl integrations. `\"cesium\"` is the 3D globe (see `CesiumCanvas`),\n * which renders the same shared store state — camera, basemap, layers, group\n * effects — through CesiumJS. `\"mapbox\"` is Mapbox GL JS and `\"arcgis\"` the\n * ArcGIS Maps SDK for JavaScript, loaded from Esri's CDN at runtime (see\n * `ArcgisCanvas`); both draw the same store state through their own engines.\n *\n * Used both for secondary panes ({@link SecondaryMapView.viewKind}) and for the\n * primary workspace ({@link GeoLibreProject.primaryRenderer}), so the two never\n * drift apart.\n */\nexport type MapRendererKind = \"maplibre\" | \"cesium\" | \"mapbox\" | \"arcgis\";\n\n/**\n * The engine that draws the primary map area when a project says nothing. The\n * 2D map: it is the renderer every tool, plugin, and panel is wired to, so an\n * existing project (and a new one) opens exactly as it always did.\n */\nexport const DEFAULT_PRIMARY_RENDERER: MapRendererKind = \"maplibre\";\n\n/**\n * A non-primary map pane: shares the primary map's basemap and layers, with its\n * own camera and per-layer visibility overrides.\n */\nexport interface SecondaryMapView {\n /** Stable id, used as the React key and the sync-group registration id. */\n id: string;\n view: MapViewState;\n /** Optional user-entered label shown on the pane (e.g. a date or scenario). */\n label?: string;\n /**\n * Which rendering engine draws this pane. Defaults to `\"maplibre\"` (the 2D\n * map) when absent, so existing projects and panes are unchanged. `\"cesium\"`\n * renders a 3D globe (see {@link CesiumCanvas}) over the same shared layers.\n */\n viewKind?: MapRendererKind;\n /**\n * Per-layer visibility overrides keyed by layer id. A layer absent from this\n * map inherits the primary map's visibility (`layer.visible`); an entry forces\n * the layer visible (`true`) or hidden (`false`) in this pane only.\n */\n layerVisibility: Record<string, boolean>;\n}\n\n/** The default single-map grid (one pane, sync enabled so it turns on cleanly). */\nexport const DEFAULT_MAP_GRID_LAYOUT: MapGridLayout = {\n rows: 1,\n cols: 1,\n syncView: true,\n};\n\n/** Maximum rows or columns in the map grid (so at most a 4x4 = 16-pane grid). */\nexport const MAX_MAP_GRID_DIM = 4;\n\n/**\n * Live multi-user collaboration (issue #307). These types describe the\n * *ephemeral* session state the store holds while a live session is active. It\n * is intentionally never written to the `.geolibre.json` project file (the\n * `project.ts` serializers never read it) and never tracked in undo history (the\n * store's `partialize` never lists it), so it resets cleanly on reload.\n */\nexport type CollaborationRole = \"host\" | \"guest\";\n\n/** Whether guests may edit (`co-edit`) or only watch (`view-only`). */\nexport type CollaborationMode = \"view-only\" | \"co-edit\";\n\nexport interface ParticipantIdentity {\n provider: string;\n userId: string;\n username: string;\n}\n\nexport interface CollabInvite {\n token: string;\n role: CollaborationMode;\n createdAt: number;\n maxUses?: number;\n useCount: number;\n revoked: boolean;\n}\n\nexport interface CollaborationParticipant {\n clientId: string;\n displayName: string;\n color: string;\n role: CollaborationRole;\n /**\n * Host-set per-participant edit override (#754). `null` follows the session\n * `mode`; `true`/`false` pins this participant to can-edit / view-only. Always\n * `null` for the host (the host can always edit).\n */\n editOverride: boolean | null;\n /** Optional account identity when signed-in identity binding is enabled. */\n identity?: ParticipantIdentity | null;\n}\n\n/** A remote participant's live cursor + viewport, used to render presence. */\nexport interface CollaborationPresence {\n displayName: string;\n color: string;\n cursor?: { lng: number; lat: number } | null;\n view?: MapViewState | null;\n}\n\n/** One in-session chat message (#754). Ephemeral; never persisted to a project. */\nexport interface CollaborationChatMessage {\n /** Server-assigned id (stable React key / dedupe). */\n id: string;\n /** clientId of the author. */\n clientId: string;\n displayName: string;\n color: string;\n text: string;\n /** Optional map coordinate the author attached; clickable to recenter. */\n coordinate?: { lng: number; lat: number } | null;\n /** Server-assigned epoch-ms timestamp. */\n ts: number;\n}\n\nexport interface CollaborationState {\n /** True once connected and joined to a session. */\n isActive: boolean;\n /** True while connecting/reconnecting (UI shows a spinner). */\n connecting: boolean;\n sessionId: string | null;\n clientId: string | null;\n role: CollaborationRole | null;\n mode: CollaborationMode;\n selfName: string;\n selfColor: string;\n participants: CollaborationParticipant[];\n /** Remote presence keyed by participant clientId (never includes self). */\n presence: Record<string, CollaborationPresence>;\n /** When true, this participant's camera follows the host's viewport. */\n followHost: boolean;\n /** Recent session chat, oldest first, capped to a bounded window (#754). */\n chat: CollaborationChatMessage[];\n /** Session flag requiring participants to be signed in. */\n requireIdentity: boolean;\n /**\n * Whether the connected relay has an identity issuer configured. False (the\n * default) means it cannot verify a sign-in, so the host UI hides the\n * \"require a signed-in account\" toggle instead of offering a gate that would\n * lock every guest out.\n */\n identitySupported: boolean;\n /** Layer IDs marked locked by the host. */\n lockedLayerIds: string[];\n /** Active session invites minted by host. */\n invites: CollabInvite[];\n /** Last human-readable error, surfaced in the Collaborate dialog. */\n error: string | null;\n}\n\n/** Map projection the renderer uses. Mirrors the GlobeControl toggle. */\nexport type MapProjection = \"globe\" | \"mercator\";\n\n/**\n * Unit system the scale bar reports distances in. `\"metric\"` uses m/km,\n * `\"imperial\"` uses ft/mi, and `\"nautical\"` uses nautical miles.\n */\nexport type MapScaleUnit = \"metric\" | \"imperial\" | \"nautical\";\n\nexport interface MapPreferences {\n restrictBounds: boolean;\n bounds: [number, number, number, number];\n minZoom: number;\n maxZoom: number;\n maxPitch: number;\n renderWorldCopies: boolean;\n projection: MapProjection;\n /**\n * Celestial body / ellipsoid the project's coordinates describe (keys into the\n * ellipsoid registry in `@geolibre/core`). Drives measurement radii and pairs\n * with planetary basemaps. Defaults to `\"earth\"` (WGS 84).\n */\n ellipsoidId: string;\n /**\n * Unit system the scale bar displays. Defaults to `\"metric\"`; switch to\n * `\"imperial\"` for feet/miles or `\"nautical\"` for nautical miles.\n */\n scaleUnit: MapScaleUnit;\n /**\n * Whether the status bar resolves and shows the ground elevation under the\n * pointer (issue #1813). **Defaults to `false`**: with 3D terrain off the\n * lookup falls back to the public Open-Meteo service, so hovering would send\n * coordinates off the device for a readout the user never asked for. Toggled\n * from Controls -> Elevation.\n */\n showPointerElevation: boolean;\n /** Whether the built-in 3D terrain control and terrain surface are enabled. */\n terrainEnabled: boolean;\n /**\n * Optional Cesium Ion terrain asset used by the globe when terrain is enabled.\n * Ignored by 2D renderers; absent means the renderer's normal global source.\n */\n terrainIonAssetId?: number;\n /** Mapbox-only style. New projects use Streets; absent follows the shared basemap. */\n mapboxStyleUrl?: string;\n /**\n * ArcGIS-only basemap: an Esri basemap style id (`arcgis/streets`,\n * `arcgis/imagery`, `osm/standard`, ...). New projects use Streets. Absent\n * follows the shared basemap, translated to tiles the SDK can draw; the id\n * is also set aside when no ArcGIS API key is configured, since Esri's\n * basemap styles service requires one.\n */\n arcgisBasemap?: string;\n /**\n * ArcGIS-only projection of the flat map, as a WKID the ArcGIS Maps SDK\n * resolves (`54099` Spilhaus, `54030` Robinson, ...). Absent means Web\n * Mercator. Vector layers are reprojected on the client; tiled layers and\n * basemaps cannot be, so they do not draw in another projection. The globe\n * (a `SceneView`) ignores it. See `arcgis-projection.ts`.\n */\n arcgisWkid?: number;\n /**\n * Coordinate reference system of the flat MapLibre map (issue #1128): a\n * preset id from `MAP_CRS_PRESETS` (polar and simple-cylindrical grids of\n * Earth, the Moon, Mars and other bodies) or `\"EPSG:<code>\"`. Absent means\n * the default Web Mercator map or globe. Tiles are not reprojected, so only\n * GeoJSON, image/video overlays and WMS or ArcGIS export layers (asked for\n * each tile's box in this CRS) draw; XYZ, vector tile and plugin layers are\n * set aside, and the globe and terrain are off. See `map-crs.ts`.\n */\n mapCrs?: string;\n /** Cesium imagery override; absent follows the shared project basemap. */\n cesiumBasemap?: import(\"./cesium-imagery\").CesiumBasemapId;\n /**\n * Notation the status bar reports the pointer coordinate in: `\"dd\"` decimal\n * degrees (default), `\"dms\"` degrees/minutes/seconds, `\"ddm\"` degrees and\n * decimal minutes, `\"utm\"` zone easting/northing, `\"mgrs\"`/`\"usng\"` grid\n * references, or `\"epsg\"` x/y in the CRS named by `coordinateEpsgCode`.\n * Stored as a string rather than a union so `@geolibre/core` does not have to\n * depend on the formatter, which lives with the app's DMS helpers and the\n * Gridlines plugin's UTM projection; the app normalises unknown values to\n * `\"dd\"`.\n */\n coordinateFormat: string;\n /**\n * EPSG code the `\"epsg\"` coordinate format projects the pointer into, e.g.\n * `3857` or `32618`. Kept beside `coordinateFormat` rather than inside it so\n * the code survives cycling through the other notations. Absent means the\n * app default (EPSG:3857).\n */\n coordinateEpsgCode?: number;\n /**\n * Whether the map fits to a layer's extent after data is added (Add Data\n * menus, drag-and-drop, file import, tool outputs). Defaults to `true`; turn\n * it off to keep the current view. Explicit \"Zoom to layer\" actions always\n * fit regardless of this setting.\n */\n zoomToNewLayers: boolean;\n}\n\nexport interface RuntimeEnvironmentVariable {\n key: string;\n value: string;\n enabled: boolean;\n /**\n * Absent or `true`: the value is a secret (desktop keeps it in the OS\n * keychain; every egress removes it). `false`: an ordinary value saved in the\n * project and kept on share/export.\n */\n secret?: boolean;\n}\n\ndeclare global {\n interface Window {\n // Runtime environment variables published from project preferences. Shared\n // here so the desktop app and plugins type the global from one source.\n __GEOLIBRE_RUNTIME_ENV__?: Record<string, string>;\n }\n}\n\n/**\n * Geocoding backend selection persisted in the project. The provider id keys\n * into the geocoding registry in `@geolibre/core`; API keys are stored per\n * provider so switching backends does not discard the others' keys. Empty\n * endpoint overrides fall back to the provider's default endpoints.\n */\nexport interface GeocodingPreferences {\n providerId: string;\n /** Per-provider API key / access token, keyed by provider id. */\n apiKeys: Record<string, string>;\n /** Optional custom forward endpoint (else the provider default). */\n forwardEndpoint?: string;\n /** Optional custom reverse endpoint (else the provider default). */\n reverseEndpoint?: string;\n /** Contact email sent to identify the client (used by Nominatim). */\n email?: string;\n /** Named organization-hosted ArcGIS GeocodeServer locators. */\n locators?: GeocodingLocator[];\n /** The {@link GeocodingLocator.id} used by the `arcgis-locator` provider. */\n locatorId?: string;\n}\n\n/**\n * An organization-hosted ArcGIS GeocodeServer locator. Everything here is\n * nonsecret: the URL is stored without its query string, so a pasted token never\n * reaches the project file, and a secured locator borrows the token of the\n * portal named by `portalUrl` at request time.\n *\n * The capability fields are read from the service's `?f=json` metadata when the\n * locator is checked in Settings; they are absent until then.\n */\nexport interface GeocodingLocator {\n /** Stable id, unique within the project. */\n id: string;\n /** Display name, e.g. \"County Parcel Locator\". */\n name: string;\n /** The GeocodeServer base URL, ending in `/GeocodeServer`. */\n url: string;\n /** Portal whose sign-in authorizes this locator, when it is secured. */\n portalUrl?: string;\n /** The single-line input field the locator publishes (usually `SingleLine`). */\n singleLineField?: string;\n /** The structured input fields the locator publishes, in service order. */\n addressFields?: string[];\n /** Whether the service advertises `reverseGeocode`. */\n reverse?: boolean;\n /** Whether the service advertises `suggest`. */\n suggest?: boolean;\n /**\n * The published input field a search string goes to, chosen by the user for\n * a locator whose inputs are structured (an asset or parcel ID field, say).\n * Unset means the single-line field, or the only published field.\n */\n searchField?: string;\n}\n\nexport interface ProjectPreferences {\n map: MapPreferences;\n environmentVariables: RuntimeEnvironmentVariable[];\n geocoding: GeocodingPreferences;\n}\n\nexport type ProjectPluginControlPosition =\n | \"top-left\"\n | \"top-right\"\n | \"bottom-left\"\n | \"bottom-right\";\n\nexport interface ProjectPluginState {\n manifestUrls: string[];\n activePluginIds: string[];\n mapControlPositions: Record<string, ProjectPluginControlPosition>;\n settings: Record<string, unknown>;\n}\n\nexport const DEFAULT_PROJECT_PREFERENCES: ProjectPreferences = {\n map: {\n restrictBounds: false,\n bounds: [-180, -85, 180, 85],\n minZoom: 0,\n maxZoom: 24,\n maxPitch: 85,\n renderWorldCopies: true,\n projection: \"globe\",\n ellipsoidId: \"earth\",\n scaleUnit: \"metric\",\n // Off by default: turning it on can send pointer coordinates to a public\n // elevation service, which should be an explicit choice.\n showPointerElevation: false,\n terrainEnabled: false,\n coordinateFormat: \"dd\",\n zoomToNewLayers: true,\n mapboxStyleUrl: \"mapbox://styles/mapbox/standard\",\n arcgisBasemap: \"arcgis/streets\",\n // With an Ion token this is the globe's photographic default. The\n // availability gate transparently falls back to the project basemap when\n // no token is configured.\n cesiumBasemap: \"bing-aerial\",\n },\n environmentVariables: [],\n geocoding: {\n providerId: \"nominatim\",\n apiKeys: {},\n },\n};\n\n/**\n * A single user override for one legend item, keyed in {@link LegendConfig.overrides}\n * by a stable item key (a layer id for a whole entry, or `${layerId}::${index}`\n * for an individual class within a graduated/categorized entry).\n */\nexport interface LegendItemOverride {\n /** User-supplied label that replaces the auto-generated one. */\n label?: string;\n /** When true, the item is omitted from the rendered legend. */\n hidden?: boolean;\n}\n\n/** Swatch shape for a hand-authored legend item. */\nexport type LegendCustomShape = \"square\" | \"circle\" | \"line\";\n\n/** One hand-authored legend item: a color swatch plus its label. */\nexport interface LegendCustomItem {\n /** Display label (e.g. an NLCD class name). */\n label: string;\n /** Swatch color as a CSS color (typically `#rrggbb`). */\n color: string;\n /** Swatch shape; defaults to `\"square\"`. */\n shape?: LegendCustomShape;\n /**\n * Symbol size in map pixels (circle radius / line width). Carried so that\n * customizing a proportional-symbol entry keeps the graduated sizes it was\n * seeded from instead of flattening every symbol to one size.\n */\n size?: number;\n}\n\n/**\n * A hand-authored legend entry. Keyed by layer id it REPLACES that layer's\n * auto-derived classes (the fallback when automatic derivation is wrong or\n * impossible, e.g. a paletted land-cover raster like NLCD); keyed by a\n * `custom:` id it renders as a standalone section not tied to any layer.\n */\nexport interface LegendCustomEntry {\n /** Optional heading; a layer-keyed entry falls back to the layer name. */\n title?: string;\n /** The items to render, top to bottom. */\n items: LegendCustomItem[];\n}\n\n/** Map corner the on-map legend panel docks to. */\nexport type LegendPanelPosition = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n\n/**\n * User customizations for the legend (the on-map Legend panel and the Print\n * Layout legend). The legend itself is always derived from the visible layers'\n * symbology; this record only stores the edits layered on top (title, ordering,\n * per-item rename/hide, hand-authored entries), so it survives layer additions\n * and removals and is persisted in the `.geolibre.json` project.\n */\nexport interface LegendConfig {\n /** Heading drawn above the legend entries. */\n title: string;\n /** When true, classes are grouped under a per-layer heading. */\n groupByLayer: boolean;\n /**\n * Custom top-level entry order by layer id, top-first. Layer ids not listed\n * keep their default order after the listed ones.\n */\n order: string[];\n /** Per-item overrides keyed by stable item key. */\n overrides: Record<string, LegendItemOverride>;\n /**\n * Hand-authored entries keyed by layer id (replacing that layer's\n * auto-derived classes) or by a standalone `custom:` id. See\n * {@link LegendCustomEntry}.\n */\n customEntries?: Record<string, LegendCustomEntry>;\n /** Whether the on-map Legend panel is open (persisted with the project). */\n panelVisible?: boolean;\n /** Whether the open panel is collapsed to just its header bar. */\n panelCollapsed?: boolean;\n /** Map corner the on-map Legend panel docks to; defaults to `\"top-left\"`. */\n panelPosition?: LegendPanelPosition;\n /**\n * User-resized panel width in px (via the corner drag handles). Absent means\n * the default width; height absent means auto-fit to content within the map.\n */\n panelWidth?: number;\n /** User-resized panel height in px. See {@link LegendConfig.panelWidth}. */\n panelHeight?: number;\n}\n\n// Frozen so the shared singleton can be safely spread (`{ ...DEFAULT_LEGEND_CONFIG }`)\n// at call sites without risk of a future in-place mutation corrupting the nested\n// `order`/`overrides` references that the spread keeps sharing.\nexport const DEFAULT_LEGEND_CONFIG: LegendConfig = Object.freeze({\n title: \"Legend\",\n groupByLayer: true,\n order: Object.freeze([] as string[]) as string[],\n overrides: Object.freeze({} as Record<string, LegendItemOverride>) as Record<\n string,\n LegendItemOverride\n >,\n});\n\n/**\n * Camera target captured for a story chapter. `pitch` and `bearing` are\n * optional in memory: a location built in code may omit them, and every engine\n * then keeps that part of the current camera (see `storyLocationView`). A\n * chapter read from a project file or a CSV never omits them: loading fills an\n * absent value with 0 (`normalizeStoryChapter`, `parseStoryMapCsv`), so a file\n * without them still plays north-up and flat, as it always has.\n */\nexport interface StoryChapterLocation {\n center: [number, number];\n zoom: number;\n pitch?: number;\n bearing?: number;\n}\n\n/** Where a chapter's text panel sits over the map. */\nexport type StoryChapterAlignment = \"left\" | \"center\" | \"right\" | \"full\";\n\n/** How the map transitions to a chapter's location. */\nexport type StoryChapterAnimation = \"flyTo\" | \"easeTo\" | \"jumpTo\";\n\n/** A layer opacity change triggered when a chapter is entered or exited. */\nexport interface StoryLayerOpacityChange {\n /** Stable identity for React list keys; optional for older project files. */\n id?: string;\n /** GeoLibre store layer id whose opacity should change. */\n layerId: string;\n opacity: number;\n /** Transition duration in milliseconds. */\n duration?: number;\n}\n\n/** A single scene in a story map. */\nexport interface StoryChapter {\n id: string;\n title: string;\n description: string;\n /** Optional image shown in the chapter panel (URL or data URI). */\n image?: string;\n alignment: StoryChapterAlignment;\n /** Hide the text panel while still transitioning the map. */\n hidden: boolean;\n location: StoryChapterLocation;\n mapAnimation: StoryChapterAnimation;\n /** Slowly rotate the camera once the transition settles. */\n rotateAnimation: boolean;\n /** Autoplay dwell time after the camera arrives, in seconds (1–3600; default 15). */\n durationSeconds?: number;\n onChapterEnter: StoryLayerOpacityChange[];\n onChapterExit: StoryLayerOpacityChange[];\n}\n\nexport type StoryInsetPosition = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n\n/**\n * A non-chapter intro/outro slide shown before the first or after the last\n * chapter (#998). `\"none\"` disables it. The remaining modes are:\n * - `\"blank\"` a solid screen in the panel theme color,\n * - `\"black\"` a solid black screen,\n * - `\"global\"` a zoomed-out global view of the map with no text,\n * - `\"adjacent\"` the camera of the neighboring chapter (the first chapter for\n * the start slide, the last chapter for the closing slide) with all text and\n * media hidden, so the presenter can zoom into the full content next.\n */\nexport type StorySlideMode = \"none\" | \"blank\" | \"black\" | \"global\" | \"adjacent\";\n\n/** A start/closing slide that is actually shown (every mode except `\"none\"`). */\nexport type StoryActiveSlideMode = Exclude<StorySlideMode, \"none\">;\n\n/** Story map authored on top of a GeoLibre project. */\nexport interface StoryMap {\n title: string;\n subtitle: string;\n byline: string;\n footer: string;\n theme: \"light\" | \"dark\";\n showMarkers: boolean;\n markerColor: string;\n inset: boolean;\n insetPosition: StoryInsetPosition;\n /**\n * Start the presentation with the chapter itinerary/navigation panel hidden,\n * revealing chapters one at a time instead of listing every upcoming location\n * up front (#995). The presenter still offers a toggle to open the list.\n */\n hideChapterNav: boolean;\n /** Optional intro slide shown before the first chapter (#998). */\n startSlide: StorySlideMode;\n /** Optional closing slide shown after the last chapter (#998). */\n endSlide: StorySlideMode;\n chapters: StoryChapter[];\n}\n\nexport const DEFAULT_STORY_MAP: StoryMap = {\n title: \"\",\n subtitle: \"\",\n byline: \"\",\n footer: \"\",\n theme: \"dark\",\n showMarkers: false,\n markerColor: \"#3fb1ce\",\n inset: false,\n insetPosition: \"bottom-left\",\n hideChapterNav: false,\n startSlide: \"none\",\n endSlide: \"none\",\n chapters: [],\n};\n\n/**\n * One step in a {@link ProcessingModel}: a processing tool invoked with a fixed\n * set of parameters. The runner chains steps by feeding each step's output layer\n * into the next step's input layer parameter (`inputParam`, default `\"layer\"`),\n * so a step's stored `parameters` for that input is ignored for every step after\n * the first.\n */\nexport interface ProcessingModelStep {\n /** Stable id, unique within the model (used as the React key and run label). */\n id: string;\n /** The processing tool's registry id (e.g. `\"buffer\"`). */\n toolId: string;\n /** Parameter values keyed by the tool's parameter ids. */\n parameters: Record<string, unknown>;\n /**\n * Which `type: \"layer\"` parameter receives the previous step's output. Defaults\n * to `\"layer\"`; set it for tools whose primary input is named differently.\n */\n inputParam?: string;\n}\n\n/**\n * What flows along a model edge. Vector nodes exchange FeatureCollections;\n * raster nodes exchange GeoTIFF bytes. A port declaring `\"any\"` accepts either\n * and is resolved to a concrete kind at run time by whatever is wired into it.\n */\nexport type ModelPortKind = \"vector\" | \"raster\" | \"raster-collection\" | \"boolean\" | \"any\";\n\n/** One connection point on a {@link ModelGraphNode}. */\nexport interface ModelGraphPort {\n /**\n * Port id, unique within its node and direction. For a tool node's inputs\n * this is the underlying tool parameter id, so wiring an edge and setting the\n * parameter by hand are the same operation.\n */\n id: string;\n label: string;\n kind: ModelPortKind;\n /** Inputs only: the run fails when nothing is wired in and no value is set. */\n required?: boolean;\n}\n\n/**\n * What a node does. `input` sources an existing project layer, `tool` runs a\n * processing algorithm, `output` names a result to add back to the map,\n * `iterate-features` and `iterate-rasters` execute loops, `collect-results` gathers\n * iteration outputs, and `conditional-switch` / `conditional-merge` route execution.\n */\nexport type ModelGraphNodeKind =\n | \"input\"\n | \"tool\"\n | \"output\"\n | \"iterate-features\"\n | \"iterate-rasters\"\n | \"collect-results\"\n | \"conditional-switch\"\n | \"conditional-merge\";\n\n/** One node on the Model Builder canvas. */\nexport interface ModelGraphNode {\n /** Stable id, unique within the graph; referenced by {@link ModelGraphEdge}. */\n id: string;\n kind: ModelGraphNodeKind;\n /** Canvas position in graph coordinates (unscaled by zoom). */\n x: number;\n y: number;\n /** `input` nodes: the project layer id this node sources. */\n layerId?: string;\n /**\n * `tool` nodes: the tool's id within {@link provider}'s registry. Kept\n * separate from the provider so the same short id can exist in both.\n */\n toolId?: string;\n /** `tool` nodes: which registry resolves {@link toolId}. */\n provider?: ModelToolProvider;\n /** `tool` / control nodes: parameter values for configuration. */\n parameters?: Record<string, unknown>;\n /** `output` nodes: the layer name given to the result added to the map. */\n name?: string;\n /** Optional nested body graph for iterator nodes. */\n body?: ProcessingModelGraph;\n}\n\n/**\n * A directed connection from one node's output port to another node's input\n * port. Ports are named, so a tool with several inputs (Clip's target and\n * overlay, say) wires each one unambiguously.\n */\nexport interface ModelGraphEdge {\n id: string;\n from: string;\n fromPort: string;\n to: string;\n toPort: string;\n}\n\n/** The node-and-edge graph authored on the Model Builder canvas. */\nexport interface ProcessingModelGraph {\n nodes: ModelGraphNode[];\n edges: ModelGraphEdge[];\n}\n\n/** Which registry a {@link ModelGraphNode.toolId} is resolved against. */\nexport type ModelToolProvider = \"vector\" | \"whitebox\";\n\n/**\n * A reusable processing pipeline (\"model\" in QGIS Graphical Modeler / ArcGIS\n * ModelBuilder terms), saved in the project file so it can be reloaded and\n * re-run.\n *\n * Two shapes coexist. {@link steps} is the original strictly linear chain, and\n * remains the only thing older builds understand. {@link graph} is the\n * Model Builder's directed graph, which supports multi-input tools, branches\n * and merges. When both are present `graph` wins; a model saved by the canvas\n * also writes a `steps` projection whenever its graph happens to be a single\n * chain, so older builds can still run it.\n */\nexport interface ProcessingModel {\n id: string;\n name: string;\n steps: ProcessingModelStep[];\n graph?: ProcessingModelGraph;\n}\n\n/**\n * Which dialog family executed a {@link ProcessingRun}. Drives the History\n * panel's re-run routing: each kind maps to the dialog (and store open-flag)\n * that can be reopened pre-filled with the run's parameters.\n */\nexport type ProcessingRunKind =\n | \"vector\"\n | \"statistics\"\n | \"network\"\n | \"whitebox\"\n | \"raster\"\n | \"conversion\"\n | \"algorithm\";\n\nexport type ProcessingRunStatus = \"success\" | \"error\";\n\n/** Upper bound on persisted processing-history entries (oldest are dropped). */\nexport const MAX_PROCESSING_HISTORY = 100;\n\n/**\n * One recorded processing tool run (issue #1292). Appended by every processing\n * dialog when a run finishes and persisted in the project file, so a saved\n * project documents how its derived layers were produced. Parameter values are\n * stored exactly as the dialog dispatched them (layer parameters hold layer\n * ids), which is what re-run pre-fills and \"Copy as Python\" emit.\n */\nexport interface ProcessingRun {\n /** Stable id, unique within the project (React key and update key). */\n id: string;\n kind: ProcessingRunKind;\n /** The tool's registry id (e.g. `\"buffer\"`, `\"slope\"`). */\n toolId: string;\n /** Human-readable tool name captured at run time. */\n toolName: string;\n /** Engine that executed the run: `client`, `wasm`, `sidecar`, `pyodide`, `browser`. */\n engine: string;\n /** Parameter values keyed by the tool's parameter ids. */\n parameters: Record<string, unknown>;\n /** Names of input layers referenced by layer parameters, keyed by layer id. */\n inputLayerNames?: Record<string, string>;\n /** Names of layers the run added to the map. */\n outputLayerNames?: string[];\n /** Input file path/name, for file-based tools (raster/conversion). */\n inputPath?: string;\n /** Output file path/name, for file-based tools (raster/conversion). */\n outputPath?: string;\n /** ISO timestamp of when the run started. */\n startedAt: string;\n /** Wall-clock duration of the run in milliseconds. */\n durationMs?: number;\n status: ProcessingRunStatus;\n /** Error message when `status` is `\"error\"`. */\n error?: string;\n}\n\n/**\n * A pending \"re-run from History\" request. The History panel writes it to the\n * store and opens the target dialog; the dialog consumes it (pre-filling its\n * parameter form) and clears it. `autoRun` asks the dialog to start the run\n * immediately after pre-filling (plain Re-run vs Edit & re-run).\n */\nexport interface ProcessingRerunRequest {\n kind: ProcessingRunKind;\n toolId: string;\n parameters: Record<string, unknown>;\n /** Engine to preselect, when the dialog offers a choice. */\n engine?: string;\n autoRun?: boolean;\n}\n\n/** Column-count bounds for the Dashboard panel's widget grid. */\nexport const MIN_DASHBOARD_COLUMNS = 1;\nexport const MAX_DASHBOARD_COLUMNS = 6;\nexport const DEFAULT_DASHBOARD_COLUMNS = 2;\n\n/** The chart a {@link DashboardWidget} draws. Mirrors the attribute Charts\n * panel's types so a widget reuses the same rendering. The `\"indicator\"` type\n * is a non-chart KPI tile (issue #1381). */\nexport type DashboardWidgetType =\n | \"histogram\"\n | \"scatter\"\n | \"bar\"\n | \"line\"\n | \"box\"\n | \"pie\"\n | \"indicator\"\n | \"selector\"\n | \"list\";\n\n/** How a bar widget reduces its category groups. */\nexport type DashboardWidgetAggregation = \"count\" | \"sum\" | \"mean\";\n\n/**\n * One chart in the Dashboard panel: a chart type bound to a layer and the\n * field(s) it plots. Which `field*`/`category`/`aggregation` keys apply depends\n * on `type` (histogram/line/box use `field`; scatter uses `xField`/`yField`;\n * bar uses `category` + `aggregation` and, for sum/mean, `valueField`). Unused\n * keys are simply ignored, so the record stays flat and easy to hand-edit.\n * Saved in the project file so a dashboard reopens with its widgets intact.\n */\n/**\n * A saved map view in the Bookmarks panel. Mirrors `MapBookmark` from\n * maplibre-gl-components so the panel can load it unchanged.\n */\nexport interface ProjectBookmark {\n /** Unique within the project. */\n id: string;\n name: string;\n /** Camera center longitude, -180..180. */\n lng: number;\n /** Camera center latitude, -90..90. */\n lat: number;\n zoom: number;\n /** Camera tilt in degrees, 0..85. */\n pitch: number;\n /** Camera rotation in degrees. */\n bearing: number;\n /** Creation time, milliseconds since the epoch. */\n createdAt: number;\n /** The {@link ProjectBookmarkGroup} it belongs to; absent when ungrouped. */\n groupId?: string;\n /**\n * Host state captured with the view. GeoLibre stores `visibleLayerIds`\n * (the layers to show when the bookmark is opened).\n */\n extra?: Record<string, unknown>;\n}\n\n/** A folder in the Bookmarks panel. Folders do not nest. */\nexport interface ProjectBookmarkGroup {\n id: string;\n name: string;\n /** Whether the folder is collapsed in the panel. */\n collapsed: boolean;\n}\n\nexport interface DashboardWidget {\n /** Stable id, unique within the project (React key and store key). */\n id: string;\n /** The layer whose features feed this widget. */\n layerId: string;\n /** The chart to draw. */\n type: DashboardWidgetType;\n /** Optional custom title; the panel derives a label from the fields if absent. */\n title?: string;\n /** Optional hex color (`#rgb`/`#rrggbb`) for the chart's marks. Single-series\n * charts use it as the series color; bar/pie use it as the base of a\n * monochromatic ramp. Defaults to the theme primary / multi-color palette. */\n color?: string;\n /** Value field for histogram/line/box. */\n field?: string;\n /** X-axis field for scatter. */\n xField?: string;\n /** Y-axis field for scatter. */\n yField?: string;\n /** Number of bins for a histogram. */\n bins?: number;\n /** Category field for a bar chart. */\n category?: string;\n /** Aggregation for a bar chart (default `count`). */\n aggregation?: DashboardWidgetAggregation;\n /** Value field a bar chart's sum/mean reduces (ignored for `count`). */\n valueField?: string;\n /** Indicator widget: aggregation function for the KPI value (issue #1381).\n * Extends bar aggregation with min, max, and median. */\n indicatorAggregation?: IndicatorAggregation;\n /** Indicator widget: optional prefix (e.g. \"€\", \"$\"). */\n prefix?: string;\n /** Indicator widget: optional suffix (e.g. \" kg\", \" ha\"). */\n suffix?: string;\n /** Selector widget: whether multiple values can be picked (default false). */\n multiple?: boolean;\n /** List widget: columns to display. */\n listFields?: string[];\n /** List widget: field to sort by. */\n sortBy?: string;\n /** List widget: sort direction (default \"desc\"). */\n sortDir?: \"asc\" | \"desc\";\n /** List widget: max rows to show (default 20). */\n limit?: number;\n}\n\n/** Aggregation functions for indicator widgets (issue #1381). Extends the bar\n * widget aggregation with min, max, and median. */\nexport type IndicatorAggregation = \"count\" | \"sum\" | \"mean\" | \"min\" | \"max\" | \"median\";\n\n/**\n * What slice of a layer's styling a Style Manager entry captures (issue #1294).\n *\n * - `\"style\"`: the layer's complete {@link LayerStyle} snapshot.\n * - `\"symbol\"`: fill/stroke/marker/pattern symbology only, so it can restyle a\n * layer without touching its labels or renderer configuration.\n * - `\"labels\"`: the {@link LabelStyle} block only.\n * - `\"ramp\"`: the color ramp + classification settings only (ramp name, class\n * count, classification scheme), independent of the attribute the target\n * layer classifies on.\n */\nexport type StyleLibraryEntryKind = \"style\" | \"symbol\" | \"labels\" | \"ramp\";\n\n/**\n * One saved, reusable style in the Style Manager library (issue #1294). The\n * payload is a {@link LayerStyle} subset chosen by {@link kind}; applying an\n * entry merges that subset onto the target layer's style. Entries live either\n * in the app-level library (persisted across projects) or embedded in a\n * project file's `styleLibrary` array.\n */\nexport interface StyleLibraryEntry {\n /** Stable id, used as the IndexedDB/store key; upserts overwrite by id. */\n id: string;\n /** Display name shown in the Style Manager. */\n name: string;\n /** Which style subset {@link style} carries. */\n kind: StyleLibraryEntryKind;\n /** Free-form tags for filtering the library. */\n tags: string[];\n /** The saved {@link LayerStyle} subset (see {@link kind}). */\n style: Partial<LayerStyle>;\n /** ISO timestamp of the last save; empty for built-in presets. */\n updatedAt: string;\n}\n\n/**\n * One saved, re-addable layer in the Layer Library (issue #1520) — the \"My\n * Data\" section of the Browser panel. Stores the layer's **source\n * specification** plus its full presentation state (style, labels, filters,\n * joins, virtual fields, attribute form), deliberately *not* the data, so the\n * library stays small and an entry always reflects the current contents of its\n * source.\n *\n * The exception is a layer whose features exist only in memory (drawn features,\n * processing output) or in a local file: those have no re-fetchable source, so\n * their features are embedded in {@link geojson} behind a size cap. See\n * `layer-library.ts` for how an entry is captured and re-added.\n */\nexport interface LayerLibraryEntry {\n /** Stable id, used as the IndexedDB/store key; upserts overwrite by id. */\n id: string;\n /** Display name shown in the Browser panel's My Data section. */\n name: string;\n /** ISO timestamp of the last save. */\n addedAt: string;\n /** The layer type to recreate ({@link GeoLibreLayer.type}). */\n layerType: LayerType;\n /** The MapLibre/plugin source spec to recreate the layer from. */\n source: Record<string, unknown>;\n /** The saved layer's complete {@link LayerStyle} (labels included). */\n style: LayerStyle;\n /** Layer opacity in [0, 1]. */\n opacity: number;\n /** The layer metadata the renderers and plugin sync modules key off. */\n metadata: Record<string, unknown>;\n /** Absolute local path, for a layer read from a file on disk. */\n sourcePath?: string;\n /** Persistent attribute joins to reapply. */\n joins?: LayerJoin[];\n /** Expression-backed virtual fields to reapply. */\n virtualFields?: LayerVirtualField[];\n /** Per-field edit-widget/constraint configuration to reapply. */\n attributeForm?: AttributeFormConfig;\n /** Popup/tooltip design to reapply. */\n popup?: LayerPopupConfig;\n /** User-authored catalog metadata to reapply. */\n descriptiveMetadata?: LayerDescriptiveMetadata;\n /**\n * Embedded features, present only for a layer whose source cannot be\n * re-read (in-memory features) or whose local file may be unavailable.\n */\n geojson?: FeatureCollection;\n /**\n * True when the entry can only be re-added by a host that can read\n * {@link sourcePath} — i.e. its features were too large to embed, so the\n * desktop app must re-read the file and the browser build cannot.\n */\n needsLocalFile?: boolean;\n}\n\n/** One saved template in the Template Library. */\nexport interface ProjectTemplateEntry {\n id: string;\n name: string;\n description?: string;\n project: GeoLibreProject;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface GeoLibreProject {\n id?: string;\n version: string;\n name: string;\n mapView: MapViewState;\n basemapStyleUrl: string;\n basemapVisible: boolean;\n basemapOpacity: number;\n /** Custom colour for the Blank background. Null uses the current theme default. */\n blankBackgroundColor?: string | null;\n layers: GeoLibreLayer[];\n /**\n * Layer selected in the Layers panel when the project was saved. Omitted by\n * legacy projects; `null` deliberately restores no active layer.\n */\n selectedLayerId?: string | null;\n /** Named folders that organize the flat `layers` list in the layer panel. */\n layerGroups?: LayerGroup[];\n styles: Record<string, LayerStyle>;\n preferences: ProjectPreferences;\n plugins?: ProjectPluginState;\n /** User customizations for the Print Layout legend. */\n legend?: LegendConfig;\n /**\n * Print Layout composer settings (title, page size, orientation, blocks,\n * atlas). Omitted while the composer is untouched, so projects that never\n * opened it are unaffected (GeoLibre discussion #1992).\n */\n printLayout?: PrintLayoutConfig;\n storymap?: StoryMap;\n /** Saved processing pipelines (batch/model chaining; issue #344). */\n models?: ProcessingModel[];\n /** Recorded processing tool runs (Processing History; issue #1292). */\n processingHistory?: ProcessingRun[];\n /**\n * Object-Based Analysis workbench state and run provenance (issue #3053):\n * settings, classes, and the tool calls, parameters, seeds and engine\n * versions behind the last segmentation, measurement and classification.\n * A versioned JSON object owned by the desktop app; omitted until the\n * workbench is used.\n */\n obia?: Record<string, unknown>;\n /** Saved Dashboard panel chart widgets (issue #401). */\n widgets?: DashboardWidget[];\n /**\n * Saved map views (the Bookmarks panel), in panel order. Omitted when there\n * are none. Before #2869 these lived only in the browser's localStorage.\n */\n bookmarks?: ProjectBookmark[];\n /** Folders the {@link bookmarks} are organized into. Omitted when none. */\n bookmarkGroups?: ProjectBookmarkGroup[];\n /** Number of columns in the Dashboard widget grid; omitted when default. */\n dashboardColumns?: number;\n /**\n * Multi-map grid layout; omitted (single 1x1 pane) for default projects so\n * legacy readers and single-map files are unaffected.\n */\n mapLayout?: MapGridLayout;\n /**\n * Secondary map panes (everything past the primary pane). Omitted when the\n * grid is a single pane. The primary pane uses the top-level `mapView` /\n * `basemap*` fields.\n */\n secondaryMapViews?: SecondaryMapView[];\n /** User-entered label for the primary pane; omitted when empty. */\n primaryMapLabel?: string;\n /**\n * Which engine draws the primary map area (issue #2217). Omitted for the\n * default 2D map, so every project written before this existed — and every\n * project that never leaves MapLibre — is byte-identical to before. A project\n * saved as `\"cesium\"` reopens directly on the 3D globe.\n *\n * This is independent of {@link mapLayout}: a 1x1 workspace can be either\n * renderer, and a multi-pane grid can still mix the two through each pane's\n * {@link SecondaryMapView.viewKind}.\n */\n primaryRenderer?: MapRendererKind;\n /**\n * Project-scoped Style Manager entries (issue #1294), so a project can carry\n * its reusable styles to teammates. Omitted when empty; the app-level\n * library is persisted outside the project file and never serialized here.\n */\n styleLibrary?: StyleLibraryEntry[];\n /** Anchored review comments on map points or features (issue #1518). */\n comments?: ProjectComment[];\n /**\n * Interaction state applied when the project opens: the Identify target and\n * which map controls and toolbar panels start shown (issue #2688). Written by\n * the Python `geolibre` package from `set_identify` / `show_control`, so a\n * saved or HTML-exported notebook map opens the way it was set up. Omitted\n * by default; the app keeps what it loaded and writes it back unchanged.\n */\n interaction?: ProjectInteraction;\n metadata: Record<string, unknown>;\n}\n\n/**\n * Startup interaction state carried by a project (see\n * {@link GeoLibreProject.interaction}).\n */\nexport interface ProjectInteraction {\n /**\n * Identify target on open: a layer id, `\"all\"` for every visible queryable\n * layer, a list of layer ids to identify only those, or `null` for off.\n */\n identify?: string | string[] | null;\n /**\n * Map control or toolbar panel name (`\"search\"`, `\"bookmark\"`, `\"globe\"`,\n * ...) to whether it starts shown, or to a {@link ProjectControlSetting}\n * that also carries the control's options. Unknown names are ignored on open.\n */\n controls?: Record<string, boolean | ProjectControlSetting>;\n}\n\n/**\n * A control's startup state with options, for example\n * `{ \"visible\": true, \"collapsed\": true, \"distanceUnit\": \"feet\" }` for the\n * Measure panel (issue #3241). Measure takes `collapsed`, `distanceUnit` and\n * `areaUnit`; Minimap takes `collapsed`, `zoomOffset`, `width` and `height`.\n */\nexport interface ProjectControlSetting {\n /** Whether the control starts shown. */\n visible: boolean;\n /** Control-specific options; the app validates them when it applies them. */\n [option: string]: string | number | boolean;\n}\n\nexport type CommentAnchor =\n | { type: \"point\"; lngLat: [number, number] }\n | { type: \"feature\"; layerId: string; featureId: string | number; lngLat?: [number, number] };\n\nexport interface CommentAuthor {\n name: string;\n color: string;\n}\n\nexport interface CommentReply {\n id: string;\n author: CommentAuthor;\n body: string;\n createdAt: string;\n}\n\nexport interface ProjectComment {\n id: string;\n anchor: CommentAnchor;\n author: CommentAuthor;\n body: string;\n createdAt: string;\n resolved: boolean;\n replies: CommentReply[];\n}\n\nexport interface RecentProjectEntry {\n path: string;\n name: string;\n openedAt: string;\n}\n\n/**\n * Size at or above which a local vector file is read through DuckDB instead of\n * the in-memory JavaScript readers, and the feature count above which the user\n * is asked before every feature is materialized as GeoJSON.\n *\n * These live in core so the desktop loaders (`duckdb-vector-guard.ts`) and the\n * Add Vector Layer panel (`@geolibre/plugins`, which configures the\n * `maplibre-gl-vector` control's `autoThreshold`) switch strategy at the *same*\n * numbers. Before they were unified the two entry points disagreed — the panel\n * tiled at 25 MB / 50k while drag-and-drop stayed in memory to 100 MB — so the\n * same file behaved differently depending on how it was added, and no single\n * number could be documented.\n */\nexport const DUCKDB_VECTOR_ROUTE_BYTES = 100 * 1024 * 1024; // 100 MB\nexport const DUCKDB_VECTOR_FEATURE_WARN_COUNT = 100_000;\n\n/** A collection whose coordinates cannot be WGS84 longitude/latitude. */\nexport interface NonGeographicCoordinates {\n /** How many coordinates were inspected. */\n sampled: number;\n /** The largest |x| seen — a longitude may not exceed 180. */\n maxAbsX: number;\n /** The largest |y| seen — a latitude may not exceed 90. */\n maxAbsY: number;\n}\n\nconst MAX_WGS84_LON = 180;\nconst MAX_WGS84_LAT = 90;\n\n/**\n * Detect a collection that declares (or is assumed to be) WGS84 but carries\n * projected coordinates — the failure mode where a layer loads cleanly, appears\n * in the Layers panel, and renders nowhere because its \"longitude\" is a easting\n * in metres or feet.\n *\n * GeoLibre honours whatever CRS a file declares, so a file that declares\n * `CRS84`/`GCS_WGS_1984` while holding State Plane or Albers coordinates is\n * passed through untouched and lands off the map with no error. Callers use\n * this to warn instead of failing silently; it does not guess the true CRS,\n * which only the user knows.\n *\n * Sampling stops at `sampleLimit` coordinates: out-of-range values are a\n * property of the whole file, so a prefix is enough and a 3-million-coordinate\n * collection is not walked twice.\n *\n * @returns Details when a coordinate is out of geographic range, else null.\n */\nexport function detectNonGeographicCoordinates(\n geojson: GeoJSON.FeatureCollection | undefined,\n sampleLimit = 1000,\n): NonGeographicCoordinates | null {\n if (!geojson?.features?.length) return null;\n let sampled = 0;\n let maxAbsX = 0;\n let maxAbsY = 0;\n let offending = false;\n\n const visit = (coords: unknown): void => {\n if (sampled >= sampleLimit || !Array.isArray(coords)) return;\n if (typeof coords[0] === \"number\" && typeof coords[1] === \"number\") {\n const x = Math.abs(coords[0]);\n const y = Math.abs(coords[1]);\n sampled += 1;\n // Gated on finiteness for the same reason as `offending` below: an\n // Infinity would otherwise be reported as the offending magnitude in the\n // warning, hiding the real out-of-range value.\n if (Number.isFinite(x) && x > maxAbsX) maxAbsX = x;\n if (Number.isFinite(y) && y > maxAbsY) maxAbsY = y;\n // NaN/Infinity are a different defect (a broken file, not a CRS mismatch),\n // so only finite out-of-range values count.\n if (Number.isFinite(x) && Number.isFinite(y) && (x > MAX_WGS84_LON || y > MAX_WGS84_LAT)) {\n offending = true;\n }\n return;\n }\n for (const part of coords) {\n if (sampled >= sampleLimit) return;\n visit(part);\n }\n };\n\n for (const feature of geojson.features) {\n if (sampled >= sampleLimit) break;\n const geometry = feature?.geometry as {\n coordinates?: unknown;\n geometries?: { coordinates?: unknown }[];\n } | null;\n // A GeometryCollection holds its coordinates one level down, under\n // `geometries[]`, so it has no `coordinates` of its own to visit.\n if (geometry?.coordinates !== undefined) visit(geometry.coordinates);\n else if (Array.isArray(geometry?.geometries)) {\n for (const member of geometry.geometries) {\n if (sampled >= sampleLimit) break;\n if (member?.coordinates !== undefined) visit(member.coordinates);\n }\n }\n }\n\n return offending ? { sampled, maxAbsX, maxAbsY } : null;\n}\n","/**\n * Raster basemaps for regions the default catalog does not serve well.\n *\n * Today that means mainland China: GeoLibre's defaults (OpenFreeMap, Protomaps)\n * and almost every provider in the Basemaps control are hosted outside it with\n * no presence inside, so from there they range from slow to unreachable. These\n * entries are served from inside China and give those users a basemap that\n * loads.\n *\n * The mechanism mirrors {@link PlanetaryBasemap}: `styleUrl` is a\n * `geolibre://regional-basemap/<id>` sentinel that the map controller's\n * `resolveMapStyle` expands into a raster style at apply time (it is not a\n * fetchable URL). A separate sentinel prefix from the planetary one keeps the\n * two apart, because selecting a planetary basemap also switches the project's\n * celestial body, which must not happen here.\n */\n\n/**\n * The regions the pickers group these basemaps under. Each gets its own\n * heading and explanatory note inside the single \"Regional\" section, so a\n * future region slots in without the section itself becoming country-specific.\n */\nexport type RegionalBasemapRegionId = \"china\";\n\n/**\n * A raster basemap for a region, rendered from XYZ (or TMS) tiles.\n */\nexport interface RegionalBasemap {\n id: string;\n /** Which region heading this basemap sits under. */\n region: RegionalBasemapRegionId;\n /**\n * Display name, in the language of the region the basemap serves. These are\n * Chinese-market services whose users know them by their Chinese names, and\n * the section heading already says which region they belong to, so the labels\n * are not translated the way UI strings are.\n */\n name: string;\n /**\n * The tile provider's brand name in Latin script (\"Amap\", \"Tencent Maps\"),\n * for messages read outside the region, such as the confirmation shown when\n * an opened project names this basemap. A proper noun, so not translated.\n */\n provider: string;\n /** Sentinel stored as the basemap style URL. */\n styleUrl: string;\n /** XYZ (or TMS, see {@link scheme}) tile template. */\n tileUrl: string;\n /**\n * An optional transparent overlay drawn above {@link tileUrl}, so a satellite\n * basemap can ship with its roads and labels burnt in as one selectable\n * basemap rather than something the user has to stack by hand.\n */\n overlayTileUrl?: string;\n /**\n * Tile row ordering. Tencent numbers rows from the bottom (**TMS**); Amap is\n * standard XYZ. Omit for XYZ; MapLibre defaults to `\"xyz\"` when absent.\n */\n scheme?: \"tms\";\n /** Max native zoom of the source (MapLibre overzooms beyond this). */\n maxZoom: number;\n /** Attribution shown on the map. */\n attribution: string;\n /**\n * True when the tiles are drawn in GCJ-02, the offset datum Chinese law\n * mandates for public map services. Neither GeoLibre nor MapLibre applies the\n * shift, so WGS84 data laid over these lands roughly 100 to 700 m off (see\n * docs/getting-started.md). Not every regional basemap is offset — Tianditu\n * publishes in CGCS2000 and lines up — so this records which are, rather than\n * leaving a caveat that applies to some entries implied by the region.\n */\n gcj02?: boolean;\n}\n\nexport const REGIONAL_BASEMAP_SENTINEL_PREFIX = \"geolibre://regional-basemap/\";\n\nconst AMAP_ATTRIBUTION = '&copy; <a href=\"https://www.amap.com\">高德地图 Amap</a>';\nconst TENCENT_ATTRIBUTION = '&copy; <a href=\"https://map.qq.com\">腾讯地图 Tencent Maps</a>';\n\nconst sentinel = (id: string) => `${REGIONAL_BASEMAP_SENTINEL_PREFIX}${id}`;\n\n// Amap serves each product from four numbered hosts; MapLibre takes a single\n// template per source, so pin host 01. Browsers multiplex over HTTP/2, which\n// makes the sharding a non-issue. `style` selects the product: 7 street, 6\n// imagery, 8 roads-and-labels.\nconst AMAP_STREET_URL =\n \"https://wprd01.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scl=1&style=7&x={x}&y={y}&z={z}\";\nconst AMAP_SATELLITE_URL = \"https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}\";\nconst AMAP_LABELS_URL = \"https://webst01.is.autonavi.com/appmaptile?style=8&x={x}&y={y}&z={z}\";\n\nconst tencentUrl = (styleId: number) =>\n `https://rt0.map.gtimg.com/tile?z={z}&x={x}&y={y}&styleid=${styleId}&scene=0`;\n\n/**\n * Basemaps served from inside mainland China.\n *\n * Zoom limits are each source's probed maximum, so MapLibre overzooms (blurs)\n * rather than painting a placeholder: Amap's imagery returns a \"no imagery\"\n * tile rather than a 404 past zoom 18, and Tencent 400s past 19 with zoom 19\n * already blank.\n */\nexport const CHINA_BASEMAPS: readonly RegionalBasemap[] = [\n {\n id: \"amap-street\",\n region: \"china\",\n name: \"高德地图\",\n provider: \"Amap\",\n styleUrl: sentinel(\"amap-street\"),\n tileUrl: AMAP_STREET_URL,\n maxZoom: 19,\n attribution: AMAP_ATTRIBUTION,\n gcj02: true,\n },\n {\n id: \"amap-satellite\",\n region: \"china\",\n name: \"高德卫星\",\n provider: \"Amap\",\n styleUrl: sentinel(\"amap-satellite\"),\n tileUrl: AMAP_SATELLITE_URL,\n maxZoom: 18,\n attribution: AMAP_ATTRIBUTION,\n gcj02: true,\n },\n {\n id: \"amap-hybrid\",\n region: \"china\",\n name: \"高德混合\",\n provider: \"Amap\",\n styleUrl: sentinel(\"amap-hybrid\"),\n tileUrl: AMAP_SATELLITE_URL,\n overlayTileUrl: AMAP_LABELS_URL,\n maxZoom: 18,\n attribution: AMAP_ATTRIBUTION,\n gcj02: true,\n },\n {\n id: \"tencent-street\",\n region: \"china\",\n name: \"腾讯地图\",\n provider: \"Tencent Maps\",\n styleUrl: sentinel(\"tencent-street\"),\n tileUrl: tencentUrl(1),\n scheme: \"tms\",\n maxZoom: 18,\n attribution: TENCENT_ATTRIBUTION,\n gcj02: true,\n },\n {\n id: \"tencent-dark\",\n region: \"china\",\n name: \"腾讯深色\",\n provider: \"Tencent Maps\",\n styleUrl: sentinel(\"tencent-dark\"),\n tileUrl: tencentUrl(4),\n scheme: \"tms\",\n maxZoom: 18,\n attribution: TENCENT_ATTRIBUTION,\n gcj02: true,\n },\n];\n\n/** Every regional basemap, across all regions. */\nexport const REGIONAL_BASEMAPS: readonly RegionalBasemap[] = CHINA_BASEMAPS;\n\n/**\n * The regional basemaps grouped for display, one entry per region. Both the New\n * Project and Change Basemap panels render this inside a single collapsible\n * \"Regional\" section, so adding a region is a change here rather than in two\n * dialogs.\n */\nexport const REGIONAL_BASEMAP_GROUPS: readonly {\n id: RegionalBasemapRegionId;\n basemaps: readonly RegionalBasemap[];\n}[] = [{ id: \"china\", basemaps: CHINA_BASEMAPS }];\n\n/** Look up a regional basemap by its `geolibre://regional-basemap/<id>` sentinel. */\nexport function getRegionalBasemapByStyleUrl(\n styleUrl: string | undefined,\n): RegionalBasemap | undefined {\n if (!styleUrl) return undefined;\n return REGIONAL_BASEMAPS.find((basemap) => basemap.styleUrl === styleUrl);\n}\n\n/** Look up a regional basemap by id. */\nexport function getRegionalBasemapById(id: string | undefined): RegionalBasemap | undefined {\n if (!id) return undefined;\n return REGIONAL_BASEMAPS.find((basemap) => basemap.id === id);\n}\n\n/** Whether a style URL is a regional-basemap sentinel, resolvable or not. */\nexport function isRegionalBasemapSentinel(styleUrl: string | undefined): boolean {\n return Boolean(styleUrl?.startsWith(REGIONAL_BASEMAP_SENTINEL_PREFIX));\n}\n\n/**\n * How the store treats a regional basemap that arrives without the user picking\n * it (a project file, share link, or startup restore naming one by its\n * `styleUrl`). The tiles come from servers in mainland China, so loading them\n * unasked sends the user's map requests there.\n *\n * - `\"allow\"`: apply it as is. This is the store default, so library consumers\n * keep their behaviour; the desktop and web app switch to `\"prompt\"`.\n * - `\"prompt\"`: apply the default basemap and hold the regional one for the\n * user to confirm (see {@link RegionalBasemapHold}).\n * - `\"block\"`: the deployment disabled regional basemaps. Always fall back to\n * the default basemap, including when code sets one directly.\n */\nexport type RegionalBasemapPolicy = \"allow\" | \"prompt\" | \"block\";\n\n/**\n * A regional basemap the store set aside when a project was loaded. The default\n * basemap renders in its place until the user answers (`\"prompt\"`), or for good\n * (`\"blocked\"`, which the app shows as a notice only).\n */\nexport interface RegionalBasemapHold {\n /** The regional sentinel the project asked for. */\n styleUrl: string;\n /** Why the default basemap is showing instead. */\n reason: \"prompt\" | \"blocked\";\n}\n\n/**\n * Decides which basemap a project load actually applies.\n *\n * Only a resolvable regional sentinel is gated. Every other style URL (and an\n * unknown regional id, which the map controller already replaces with the\n * default basemap) passes through untouched.\n *\n * @param styleUrl - The basemap style URL the project requests.\n * @param policy - The store's current {@link RegionalBasemapPolicy}.\n * @param fallbackStyleUrl - The basemap to apply instead (the default basemap).\n * @param trusted - True when the user already chose this basemap in this\n * session (a template, an undo step, a duplicate), so `\"prompt\"` does not\n * apply. `\"block\"` still does.\n * @returns The style URL to apply and, when it was replaced, why.\n */\nexport function gateRegionalBasemap(\n styleUrl: string,\n policy: RegionalBasemapPolicy,\n fallbackStyleUrl: string,\n trusted = false,\n): { styleUrl: string; held?: RegionalBasemapHold[\"reason\"] } {\n if (!getRegionalBasemapByStyleUrl(styleUrl)) return { styleUrl };\n if (policy === \"block\") return { styleUrl: fallbackStyleUrl, held: \"blocked\" };\n if (policy === \"prompt\" && !trusted) return { styleUrl: fallbackStyleUrl, held: \"prompt\" };\n return { styleUrl };\n}\n","/**\n * Maps the project's basemap onto imagery the Cesium globe can render.\n *\n * The 2D panes render `basemapStyleUrl` through MapLibre, which understands\n * vector GL styles and GeoLibre's `geolibre://` sentinels. Cesium understands\n * neither: it draws imagery from tile providers only. So a globe pane that\n * simply mounted the store basemap would show nothing, which is why the pane\n * used to hardcode Ion World Imagery (with a token) or OpenStreetMap (without)\n * and ignore the user's choice entirely — leaving a bright globe beside dark 2D\n * panes and breaking the \"same map, different projection\" promise the multi-map\n * grid makes.\n *\n * This module is the translation. It is deliberately **engine-free** — it\n * returns a plain descriptor and never imports Cesium — so it lives in\n * `@geolibre/core` beside the basemap catalogs it reads, the 2D and 3D\n * renderers share one source of truth, and the Cesium chunk boundary in\n * `vite.config.ts` stays intact.\n *\n * Three outcomes are possible, and the caller has to handle all three (see\n * {@link CesiumBasemapImagery}): tiles to draw, nothing to draw, or \"no raster\n * equivalent exists — pick your own default\".\n */\n\nimport { getPlanetaryBasemapByStyleUrl } from \"./ellipsoids\";\nimport { getRegionalBasemapByStyleUrl } from \"./regional-basemaps\";\nimport { BLANK_BASEMAP, DEFAULT_BASEMAP } from \"./types\";\n\n/** Stable project IDs and the corresponding Cesium bundled thumbnails / ion assets. */\nexport const CESIUM_BASEMAPS = [\n { id: \"project\", name: \"Project basemap\", icon: \"naturalEarthII.png\" },\n { id: \"natural-earth\", name: \"Natural Earth II\", icon: \"naturalEarthII.png\" },\n { id: \"bing-aerial\", name: \"Bing Maps Aerial\", icon: \"bingAerial.png\", assetId: 2 },\n {\n id: \"bing-labels\",\n name: \"Bing Maps Aerial with Labels\",\n icon: \"bingAerialLabels.png\",\n assetId: 3,\n },\n { id: \"bing-roads\", name: \"Bing Maps Roads\", icon: \"bingRoads.png\", assetId: 4 },\n { id: \"sentinel-2\", name: \"Sentinel-2\", icon: \"sentinel-2.png\", assetId: 3954 },\n { id: \"blue-marble\", name: \"Blue Marble\", icon: \"blueMarble.png\", assetId: 3845 },\n { id: \"earth-at-night\", name: \"Earth at night\", icon: \"earthAtNight.png\", assetId: 3812 },\n {\n id: \"google-satellite\",\n name: \"Google Maps Satellite\",\n icon: \"googleSatellite.png\",\n assetId: 3830182,\n },\n {\n id: \"google-labels\",\n name: \"Google Maps Satellite with Labels\",\n icon: \"googleSatelliteLabels.png\",\n assetId: 3830183,\n },\n { id: \"google-roads\", name: \"Google Maps Roadmap\", icon: \"googleRoadmap.png\", assetId: 3830184 },\n {\n id: \"google-contour\",\n name: \"Google Maps Contour\",\n icon: \"googleContour.png\",\n assetId: 3830186,\n },\n { id: \"azure-aerial\", name: \"Azure Maps Aerial\", icon: \"azureAerial.png\", assetId: 3891168 },\n { id: \"azure-roads\", name: \"Azure Maps Roads\", icon: \"azureRoads.png\", assetId: 3891169 },\n {\n id: \"esri-imagery\",\n name: \"ArcGIS World Imagery\",\n icon: \"ArcGisMapServiceWorldImagery.png\",\n category: \"Other\",\n service: \"World_Imagery\",\n },\n {\n id: \"esri-hillshade\",\n name: \"ArcGIS World Hillshade\",\n icon: \"ArcGisMapServiceWorldHillshade.png\",\n category: \"Other\",\n service: \"Elevation/World_Hillshade\",\n },\n {\n id: \"esri-ocean\",\n name: \"Esri World Ocean\",\n icon: \"ArcGisMapServiceWorldOcean.png\",\n category: \"Other\",\n service: \"Ocean/World_Ocean_Base\",\n },\n { id: \"osm\", name: \"OpenStreetMap\", icon: \"openStreetMap.png\", category: \"Other\" },\n] as const;\n\nexport type CesiumBasemapId = (typeof CESIUM_BASEMAPS)[number][\"id\"];\n\nexport function normalizeCesiumBasemap(value: unknown): CesiumBasemapId {\n return CESIUM_BASEMAPS.find((entry) => entry.id === value)?.id ?? \"project\";\n}\n\n/**\n * Missing credentials fall back to keyless imagery without changing the saved\n * choice.\n *\n * The fallback is ArcGIS World Imagery rather than the project background: a\n * globe wants to look like the Earth, and the project's own basemap is usually\n * a vector street style whose raster analogue is mostly empty ocean from\n * orbit. It needs no key, so the globe looks the same with or without an Ion\n * token — only terrain and the Ion-only basemaps are lost.\n */\nexport function availableCesiumBasemap(value: unknown, hasIonToken: boolean): CesiumBasemapId {\n const keyless = \"esri-imagery\";\n const requested = value ?? (hasIonToken ? \"bing-aerial\" : keyless);\n const entry = CESIUM_BASEMAPS.find((entry) => entry.id === requested);\n return entry && (!(\"assetId\" in entry) || hasIonToken) ? entry.id : keyless;\n}\n\n/**\n * What the globe should draw underneath the project's data layers.\n *\n * - `xyz` — a tile template to render, with the credit and zoom bounds that go\n * with it.\n * - `none` — the user chose the blank basemap; draw a bare ellipsoid.\n * - `default` — this basemap has no raster equivalent (a custom style URL, a\n * provider style, an offline archive). The renderer falls back to whatever it\n * can offer, which is Ion World Imagery when a token is configured and\n * OpenStreetMap otherwise.\n */\nexport type CesiumBasemapImagery =\n | { kind: \"none\" }\n | { kind: \"default\" }\n | { kind: \"ion\"; assetId: number }\n | { kind: \"natural-earth\" }\n | { kind: \"arcgis\"; url: string }\n | {\n kind: \"xyz\";\n /** Tile template with `{z}`/`{x}`/`{y}` placeholders. */\n template: string;\n /** Credit to show on the globe, as the HTML the 2D map already uses. */\n attribution: string;\n /** Max native zoom of the source, so the globe overzooms rather than 404s. */\n maximumLevel?: number;\n /**\n * Tile row ordering. `\"tms\"` numbers rows from the bottom; absent means\n * standard XYZ. Cesium has no `scheme` option, so a TMS template needs\n * `{y}` swapped for its `{reverseY}` placeholder at render time.\n */\n scheme?: \"tms\";\n /**\n * A transparent overlay drawn directly above {@link template} — the\n * roads-and-labels tiles a hybrid basemap (Amap Hybrid) bakes in, so one\n * basemap selection still yields a labelled satellite globe.\n */\n overlayTemplate?: string;\n };\n\n/**\n * A keyless raster basemap standing in for a vector style of the same tone.\n *\n * \"Keyless\" is the binding constraint, and it rules out most of the obvious\n * candidates: CARTO's raster endpoints (which the 2D basemap control still\n * lists) now stamp every tile with an \"API KEY REQUIRED\" watermark, and\n * Stadia/Stamen and Thunderforest have required keys for years. What is left\n * and still permissively licensed is OpenStreetMap's own tiles and EOX Maps'\n * WMTS layers (CC BY-SA), which between them cover the three tones GeoLibre's\n * vector basemaps span.\n */\ninterface RasterAnalogue {\n template: string;\n attribution: string;\n /** Max native zoom of the source; the globe overzooms past it rather than 404s. */\n maximumLevel: number;\n}\n\nconst OSM_CREDIT =\n '© <a href=\"https://www.openstreetmap.org/copyright\">OpenStreetMap</a> contributors';\n/** EOX Maps serves these layers keyless under CC BY-SA 4.0. */\nconst EOX_CREDIT = '<a href=\"https://maps.eox.at\">EOX Maps</a> (CC BY-SA 4.0)';\n\nconst eoxLayer = (layer: string, extension: string): string =>\n `https://tiles.maps.eox.at/wmts/1.0.0/${layer}/default/g/{z}/{y}/{x}.${extension}`;\n\n/** Colourful streets — the tone of Liberty and Bright. */\nconst STREETS: RasterAnalogue = {\n template: \"https://tile.openstreetmap.org/{z}/{x}/{y}.png\",\n attribution: OSM_CREDIT,\n maximumLevel: 19,\n};\n\n/** Light and muted — the tone of Positron and the pale Protomaps flavors. */\nconst LIGHT: RasterAnalogue = {\n template: eoxLayer(\"terrain-light_3857\", \"jpg\"),\n attribution: `Terrain Light · ${OSM_CREDIT} · ${EOX_CREDIT}`,\n maximumLevel: 16,\n};\n\n/**\n * Dark — the tone of Dark, Fiord, and the dark Protomaps flavors. NASA's Black\n * Marble night lights is the one keyless dark raster basemap left, and it suits\n * a globe: the point of matching here is that a dark project does not end up\n * with a glaring bright globe in the corner.\n */\nconst DARK: RasterAnalogue = {\n template: eoxLayer(\"blackmarble_3857\", \"jpg\"),\n attribution: `Black Marble © NASA Earth Observatory · ${EOX_CREDIT}`,\n // A low-resolution global product; past this the globe blurs rather than 404s.\n maximumLevel: 8,\n};\n\n/** Raster analogue per OpenFreeMap style name (the last path segment). */\nconst OPENFREEMAP_ANALOGUES: Record<string, RasterAnalogue> = {\n liberty: STREETS,\n bright: STREETS,\n positron: LIGHT,\n dark: DARK,\n fiord: DARK,\n};\n\n/** Raster analogue per Protomaps v5 flavor. */\nconst PROTOMAPS_ANALOGUES: Record<string, RasterAnalogue> = {\n light: LIGHT,\n white: LIGHT,\n grayscale: LIGHT,\n dark: DARK,\n black: DARK,\n};\n\nfunction toImagery(analogue: RasterAnalogue): CesiumBasemapImagery {\n return {\n kind: \"xyz\",\n template: analogue.template,\n attribution: analogue.attribution,\n maximumLevel: analogue.maximumLevel,\n };\n}\n\n/**\n * Whether two descriptors would draw the same thing.\n *\n * Descriptors are built fresh on every call, so two basemaps resolving to the\n * same analogue — OpenFreeMap `liberty` and `bright` are both the streets\n * tone — yield equal but distinct objects. A renderer that remembers the\n * descriptor it drew (to skip redundant work) must compare by value: a\n * reference check would miss that case and tear down and re-add an identical\n * tile provider, costing a flash and a round of re-requested tiles for no\n * visible change.\n */\nexport function sameCesiumImagery(a: CesiumBasemapImagery, b: CesiumBasemapImagery): boolean {\n if (a.kind !== b.kind) return false;\n if (a.kind === \"arcgis\" && b.kind === \"arcgis\") return a.url === b.url;\n if (a.kind === \"ion\" && b.kind === \"ion\") return a.assetId === b.assetId;\n // `none` and `default` carry no fields, so matching kinds is the whole test.\n if (a.kind !== \"xyz\" || b.kind !== \"xyz\") return true;\n return (\n a.template === b.template &&\n a.attribution === b.attribution &&\n a.maximumLevel === b.maximumLevel &&\n a.scheme === b.scheme &&\n a.overlayTemplate === b.overlayTemplate\n );\n}\n\n/**\n * `decodeURIComponent` that reports failure instead of throwing. A URL keeps a\n * malformed escape (`%E0%A4`) in its pathname quite happily, but decoding one\n * throws `URIError` — and this runs inside a React render, so an unhandled throw\n * would take the globe pane down instead of falling through to the default.\n */\nfunction safeDecode(value: string): string | undefined {\n try {\n return decodeURIComponent(value);\n } catch {\n return undefined;\n }\n}\n\n/**\n * The raster analogue for a vector GL style URL, or undefined when the style is\n * not one GeoLibre ships (a provider style, a user's own URL, a Mapbox style).\n * Matching on host + path rather than searching the URL for \"dark\"/\"light\"\n * keeps an unrelated style whose *key* happens to contain one of those words\n * from being mapped to the wrong tone.\n */\nfunction vectorStyleAnalogue(styleUrl: string): RasterAnalogue | undefined {\n let parsed: URL;\n try {\n parsed = new URL(styleUrl);\n } catch {\n return undefined;\n }\n if (parsed.hostname === \"tiles.openfreemap.org\") {\n // https://tiles.openfreemap.org/styles/<name>\n const name = parsed.pathname.split(\"/\").filter(Boolean).at(-1);\n return name ? OPENFREEMAP_ANALOGUES[name] : undefined;\n }\n if (parsed.hostname === \"api.protomaps.com\") {\n // https://api.protomaps.com/styles/v5/<flavor>/<lang>.json?key=…\n const segments = parsed.pathname.split(\"/\").filter(Boolean);\n const flavor = segments[0] === \"styles\" ? segments[2] : undefined;\n const decoded = flavor ? safeDecode(flavor) : undefined;\n return decoded ? PROTOMAPS_ANALOGUES[decoded] : undefined;\n }\n return undefined;\n}\n\n/**\n * Translate a basemap style URL into imagery for the Cesium globe.\n *\n * Planetary and regional basemaps are already raster tile sets, so they carry\n * straight over — the globe shows the same Mars/Moon/Amap tiles the 2D panes\n * do. GeoLibre's own vector styles map to a keyless raster basemap of matching\n * tone, so switching the project to a dark basemap darkens the globe too.\n * Everything else reports `default`, leaving the choice of fallback imagery to\n * the renderer (which knows whether an Ion token is configured).\n *\n * A `geolibre://` sentinel that no longer resolves is treated as the default\n * basemap, mirroring what `resolveMapStyle` does for the 2D map rather than\n * handing the sentinel onward as if it were fetchable.\n *\n * @param styleUrl - The project's `basemapStyleUrl`.\n * @returns What the globe should draw beneath the data layers.\n */\nexport function basemapToCesiumImagery(\n styleUrl: string | undefined,\n cesiumBasemap: CesiumBasemapId = \"project\",\n): CesiumBasemapImagery {\n const entry = CESIUM_BASEMAPS.find((entry) => entry.id === cesiumBasemap);\n if (entry && \"service\" in entry) {\n return {\n kind: \"arcgis\",\n url: `https://services.arcgisonline.com/ArcGIS/rest/services/${entry.service}/MapServer`,\n };\n }\n if (entry && \"assetId\" in entry) return { kind: \"ion\", assetId: entry.assetId };\n if (cesiumBasemap === \"natural-earth\") return { kind: \"natural-earth\" };\n if (cesiumBasemap === \"osm\") return toImagery(STREETS);\n if (styleUrl === BLANK_BASEMAP) return { kind: \"none\" };\n const url = styleUrl ?? DEFAULT_BASEMAP;\n\n const planetary = getPlanetaryBasemapByStyleUrl(url);\n if (planetary) {\n return {\n kind: \"xyz\",\n template: planetary.tileUrl,\n attribution: planetary.attribution,\n maximumLevel: planetary.maxZoom,\n ...(planetary.scheme ? { scheme: planetary.scheme } : {}),\n };\n }\n\n const regional = getRegionalBasemapByStyleUrl(url);\n if (regional) {\n return {\n kind: \"xyz\",\n template: regional.tileUrl,\n attribution: regional.attribution,\n maximumLevel: regional.maxZoom,\n ...(regional.scheme ? { scheme: regional.scheme } : {}),\n ...(regional.overlayTileUrl ? { overlayTemplate: regional.overlayTileUrl } : {}),\n };\n }\n\n // Every remaining sentinel kind — an offline PMTiles archive, a planetary or\n // regional id that has since been renamed — has no raster form to show, so\n // fall back the way the 2D map does instead of leaving the globe blank.\n // Reading the fallback back out of DEFAULT_BASEMAP keeps the two in step if\n // the default ever changes.\n const analogue = vectorStyleAnalogue(url.startsWith(\"geolibre://\") ? DEFAULT_BASEMAP : url);\n return analogue ? toImagery(analogue) : { kind: \"default\" };\n}\n","/**\n * Projections the ArcGIS renderer can draw its flat map in (issue #2708).\n *\n * MapLibre and Mapbox only draw Web Mercator and a globe, but the ArcGIS Maps\n * SDK reprojects vector layers on the client into any projected coordinate\n * system it knows by WKID. `preferences.map.arcgisWkid` names that system;\n * absent means the default Web Mercator map. The SDK cannot reproject tiled\n * layers (XYZ, Esri basemaps), so in another projection those do not draw.\n */\n\n/** A projection offered in the settings picker. */\nexport interface ArcgisProjectionPreset {\n /** The Esri or EPSG well-known ID the SDK resolves. */\n wkid: number;\n /** English name; the UI translates it by `id`. */\n name: string;\n /** Stable key for the translated name (`settings.map.arcgisProjections.<id>`). */\n id: string;\n}\n\n/** WKIDs that are Web Mercator, i.e. the default map. */\nconst WEB_MERCATOR_WKIDS: ReadonlySet<number> = new Set([3857, 102100, 102113, 900913]);\n\n/** Presets, verified to load and frame in the SDK pinned by `ARCGIS_SDK_VERSION`. */\nexport const ARCGIS_PROJECTION_PRESETS: readonly ArcgisProjectionPreset[] = [\n { id: \"spilhaus\", wkid: 54099, name: \"Spilhaus (World Ocean)\" },\n { id: \"equalEarth\", wkid: 8857, name: \"Equal Earth\" },\n { id: \"robinson\", wkid: 54030, name: \"Robinson\" },\n { id: \"winkelTripel\", wkid: 54042, name: \"Winkel Tripel\" },\n { id: \"mollweide\", wkid: 54009, name: \"Mollweide\" },\n { id: \"plateCarree\", wkid: 4326, name: \"Plate Carrée (WGS 84)\" },\n { id: \"northPolar\", wkid: 3995, name: \"Arctic Polar Stereographic\" },\n { id: \"southPolar\", wkid: 3031, name: \"Antarctic Polar Stereographic\" },\n];\n\n/**\n * Normalize a stored ArcGIS projection WKID.\n *\n * Args:\n * value: The raw `preferences.map.arcgisWkid` value.\n *\n * Returns:\n * A positive integer WKID other than Web Mercator, or `undefined` for the\n * default Web Mercator map (absent, malformed or a Web Mercator code).\n */\nexport function normalizeArcgisWkid(value: unknown): number | undefined {\n // Digits only: `Number` would also read \"1e3\" or \"0x10\" as a code.\n const wkid =\n typeof value === \"string\" ? (/^\\s*\\d+\\s*$/.test(value) ? Number(value) : undefined) : value;\n if (typeof wkid !== \"number\" || !Number.isSafeInteger(wkid) || wkid <= 0) return undefined;\n return WEB_MERCATOR_WKIDS.has(wkid) ? undefined : wkid;\n}\n","/**\n * Map coordinate reference systems for the MapLibre renderer (issue #1128).\n *\n * MapLibre GL JS 6.15 can draw a flat map in any planar CRS registered with\n * `maplibregl.addProjection`: tiles are placed on a square quad grid laid over\n * the CRS plane and are not reprojected, so every tile has to be made in that\n * CRS already. A WMS does that on request (the `{bbox}` tile token asks for each\n * tile's box in CRS units), which is how the presets below get native polar and\n * simple-cylindrical basemaps of Earth, the Moon, Mars and the outer bodies.\n *\n * `preferences.map.mapCrs` names the CRS: a preset id from\n * {@link MAP_CRS_PRESETS}, or `\"EPSG:<code>\"` for any code the bundled EPSG\n * tables resolve. Absent means the default Web Mercator / globe map.\n *\n * This module is pure data and string handling. Registering the projection and\n * swapping the style live in `@geolibre/map` (`map-crs.ts`).\n */\n\n/** A square quad tile grid over a CRS plane, as MapLibre's `TileMatrixSet`. */\nexport interface MapCrsTileMatrixSet {\n /** Top-left corner of tile 0/0/0, in CRS units. */\n origin: [number, number];\n /** Width (and height) of tile 0/0/0, in CRS units. */\n extentAtZoom0: number;\n}\n\n/** A WMS basemap served natively in a preset's CRS. */\nexport interface MapCrsBasemap {\n /** GetMap template whose `BBOX` is MapLibre's `{bbox}` token. */\n tiles: string;\n /** Attribution HTML. */\n attribution: string;\n /** Deepest zoom worth requesting; MapLibre overzooms past it. */\n maxZoom: number;\n}\n\n/** Which group a preset is listed under in the settings picker. */\nexport type MapCrsBody =\n | \"earth\"\n | \"moon\"\n | \"mars\"\n | \"mercury\"\n | \"venus\"\n | \"io\"\n | \"europa\"\n | \"ganymede\"\n | \"callisto\"\n | \"titan\"\n | \"pluto\"\n | \"charon\";\n\n/** A CRS offered in the settings picker. */\nexport interface MapCrsPreset {\n /** Stable id persisted in `preferences.map.mapCrs`; also the i18n key. */\n id: string;\n /** English name; the UI translates it by `id`. */\n name: string;\n /**\n * The CRS's authority code, registered with MapLibre under this name and\n * written into the `SRS`/`CRS` of WMS layers drawn on the map.\n */\n code: string;\n /**\n * The label WMS and ArcGIS export layers on the map ask for, when servers\n * do not read `code`. MapServer's PROJ rejects the planetary IAU codes, so\n * those presets use the labels USGS Astrogeology serves their grids under.\n */\n wmsSrs?: string;\n /** PROJ string of the CRS, used by proj4 on the page and in the workers. */\n definition: string;\n /** Ellipsoid registry id the preset's coordinates describe. */\n ellipsoidId: MapCrsBody;\n tileMatrixSet: MapCrsTileMatrixSet;\n /** Box the map opens on when the preset is chosen, `[west, south, east, north]`. */\n bounds: [number, number, number, number];\n basemap: MapCrsBasemap;\n}\n\n/**\n * Window event the map fires when it enters or leaves a CRS, or is torn down\n * while in one. It carries no detail: the plugin API's `getMapCrs` reads the\n * new value, and its `onMapCrsChange` listens for this.\n */\nexport const MAP_CRS_CHANGE_EVENT = \"geolibre:map-crs-change\";\n\n/** Codes that are the default Web Mercator map rather than a custom CRS. */\nconst WEB_MERCATOR_CODES: ReadonlySet<number> = new Set([3857, 900913, 102100, 102113]);\n\nconst USGS_WMS = \"https://planetarymaps.usgs.gov/cgi-bin/mapserv\";\nconst USGS_ATTRIBUTION = '<a href=\"https://astrogeology.usgs.gov/\">USGS Astrogeology</a>';\nconst GIBS_ATTRIBUTION =\n 'Imagery: <a href=\"https://earthdata.nasa.gov/gibs\">NASA GIBS</a> / Blue Marble';\n\n/**\n * A WMS 1.1.1 GetMap template. 1.1.1 because its BBOX is always x,y (east,\n * north) order, which is what `{bbox}` fills in; 1.3.0 flips it for\n * geographic CRSs.\n */\nfunction getMapTemplate(base: string, srs: string, layer: string, format: string): string {\n return (\n `${base}${base.includes(\"?\") ? \"&\" : \"?\"}SERVICE=WMS&VERSION=1.1.1&REQUEST=GetMap` +\n `&SRS=${srs}&STYLES=&LAYERS=${layer}&WIDTH=512&HEIGHT=512&FORMAT=${format}` +\n \"&TRANSPARENT=TRUE&BBOX={bbox}\"\n );\n}\n\n/** A USGS Astrogeology MapServer layer in the CRS of its `.map` file. */\nfunction usgsBasemap(mapFile: string, srs: string, layer: string, maxZoom: number): MapCrsBasemap {\n return {\n tiles: getMapTemplate(`${USGS_WMS}?map=${mapFile}`, srs, layer, \"image/png\"),\n attribution: USGS_ATTRIBUTION,\n maxZoom,\n };\n}\n\n/** A NASA GIBS Blue Marble layer from the WMS endpoint of one of its grids. */\nfunction gibsBasemap(epsg: number): MapCrsBasemap {\n return {\n tiles: getMapTemplate(\n `https://gibs.earthdata.nasa.gov/wms/epsg${epsg}/best/wms.cgi`,\n `EPSG:${epsg}`,\n \"BlueMarble_ShadedRelief_Bathymetry\",\n \"image/jpeg\",\n ),\n attribution: GIBS_ATTRIBUTION,\n maxZoom: 7,\n };\n}\n\n/** Lng/lat degrees on the whole world, centered so MapLibre can find its edges. */\nconst GEOGRAPHIC_GRID: MapCrsTileMatrixSet = { origin: [-180, 180], extentAtZoom0: 360 };\n\n/** A square centered on a pole, `halfWidth` CRS units to each side. */\nfunction polarGrid(halfWidth: number): MapCrsTileMatrixSet {\n return { origin: [-halfWidth, halfWidth], extentAtZoom0: 2 * halfWidth };\n}\n\nconst sphere = (radius: number) => `+R=${radius}`;\nconst longlat = (radius: number) => `+proj=longlat ${sphere(radius)} +no_defs`;\nconst polarStereographic = (north: boolean, radius: number) =>\n `+proj=stere +lat_0=${north ? 90 : -90} +lon_0=0 +k=1 +x_0=0 +y_0=0 ${sphere(radius)} +units=m +no_defs`;\n\nconst MOON_RADIUS = 1737400;\nconst MARS_RADIUS = 3396190;\n\n/**\n * A simple-cylindrical (lng/lat degrees) preset for one body's USGS map file.\n *\n * Args:\n * body: The body, which is also the ellipsoid id.\n * name: English name of the preset.\n * naifCode: The body's IAU 2015 code (NAIF id × 100), e.g. `49900` for Mars.\n * radius: Sphere radius in metres.\n * mapFile: The USGS `.map` file path.\n * layer: The WMS layer drawn as the basemap.\n *\n * Returns:\n * The preset.\n */\nfunction cylindrical(\n body: MapCrsBody,\n name: string,\n naifCode: number,\n radius: number,\n mapFile: string,\n layer: string,\n): MapCrsPreset {\n return {\n id: `${body}-cylindrical`,\n name,\n code: `IAU_2015:${naifCode}`,\n // Servers read a lng/lat degree grid as EPSG:4326, whatever the body.\n wmsSrs: \"EPSG:4326\",\n definition: longlat(radius),\n ellipsoidId: body,\n tileMatrixSet: GEOGRAPHIC_GRID,\n bounds: [-180, -90, 180, 90],\n // The simple-cylindrical map files list only EPSG:4326 and their IAU code,\n // and MapServer's PROJ fails on the IAU code, so ask in EPSG:4326 degrees:\n // the same lng/lat grid, labelled the way the server can read it.\n basemap: usgsBasemap(mapFile, \"EPSG:4326\", layer, 7),\n };\n}\n\n/**\n * Presets verified to render against their WMS on 2026-10-19.\n *\n * The USGS polar map files serve their native polar stereographic grid under\n * `EPSG:32661`/`EPSG:32761` (MapServer's PROJ rejects the IAU codes they also\n * list), centred on the pole with no false origin, so those labels are used for\n * the basemap requests while the map registers the body's IAU code.\n */\nexport const MAP_CRS_PRESETS: readonly MapCrsPreset[] = [\n {\n id: \"earth-arctic\",\n name: \"Arctic Polar Stereographic\",\n code: \"EPSG:3413\",\n definition:\n \"+proj=stere +lat_0=90 +lat_ts=70 +lon_0=-45 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs\",\n ellipsoidId: \"earth\",\n // NASA GIBS' grid for EPSG:3413.\n tileMatrixSet: polarGrid(4194304),\n bounds: [-180, 60, 180, 90],\n basemap: gibsBasemap(3413),\n },\n {\n id: \"earth-antarctic\",\n name: \"Antarctic Polar Stereographic\",\n code: \"EPSG:3031\",\n definition:\n \"+proj=stere +lat_0=-90 +lat_ts=-71 +lon_0=0 +k=1 +x_0=0 +y_0=0 +datum=WGS84 +units=m +no_defs\",\n ellipsoidId: \"earth\",\n tileMatrixSet: polarGrid(4194304),\n bounds: [-180, -90, 180, -60],\n basemap: gibsBasemap(3031),\n },\n {\n id: \"earth-cylindrical\",\n name: \"Plate Carrée (WGS 84)\",\n code: \"EPSG:4326\",\n definition: \"+proj=longlat +datum=WGS84 +no_defs\",\n ellipsoidId: \"earth\",\n tileMatrixSet: GEOGRAPHIC_GRID,\n bounds: [-180, -90, 180, 90],\n basemap: gibsBasemap(4326),\n },\n cylindrical(\n \"moon\",\n \"Moon Simple Cylindrical\",\n 30100,\n MOON_RADIUS,\n \"/maps/earth/moon_simp_cyl.map\",\n \"LROC_WAC\",\n ),\n {\n id: \"moon-north-polar\",\n name: \"Moon North Polar Stereographic\",\n code: \"IAU_2015:30118\",\n wmsSrs: \"EPSG:32661\",\n definition: polarStereographic(true, MOON_RADIUS),\n ellipsoidId: \"moon\",\n // About 60°N at the edge, where the LRO WAC polar mosaic ends.\n tileMatrixSet: polarGrid(931000),\n bounds: [-180, 70, 180, 90],\n basemap: usgsBasemap(\"/maps/earth/moon_npole.map\", \"EPSG:32661\", \"LRO_WAC_north\", 8),\n },\n {\n id: \"moon-south-polar\",\n name: \"Moon South Polar Stereographic\",\n code: \"IAU_2015:30120\",\n wmsSrs: \"EPSG:32761\",\n definition: polarStereographic(false, MOON_RADIUS),\n ellipsoidId: \"moon\",\n tileMatrixSet: polarGrid(931000),\n bounds: [-180, -90, 180, -70],\n basemap: usgsBasemap(\"/maps/earth/moon_spole.map\", \"EPSG:32761\", \"LRO_WAC_south\", 8),\n },\n cylindrical(\n \"mars\",\n \"Mars Simple Cylindrical\",\n 49900,\n MARS_RADIUS,\n \"/maps/mars/mars_simp_cyl.map\",\n \"MOLA_color\",\n ),\n {\n id: \"mars-north-polar\",\n name: \"Mars North Polar Stereographic\",\n code: \"IAU_2015:49918\",\n wmsSrs: \"EPSG:32661\",\n definition: polarStereographic(true, MARS_RADIUS),\n ellipsoidId: \"mars\",\n // The extent of the USGS north polar mosaics, about 38°N at the edges.\n tileMatrixSet: polarGrid(2357030),\n bounds: [-180, 65, 180, 90],\n basemap: usgsBasemap(\"/maps/mars/mars_npole.map\", \"EPSG:32661\", \"MOLA_color_north\", 7),\n },\n {\n id: \"mars-south-polar\",\n name: \"Mars South Polar Stereographic\",\n code: \"IAU_2015:49920\",\n wmsSrs: \"EPSG:32761\",\n definition: polarStereographic(false, MARS_RADIUS),\n ellipsoidId: \"mars\",\n tileMatrixSet: polarGrid(2357030),\n bounds: [-180, -90, 180, -65],\n basemap: usgsBasemap(\"/maps/mars/mars_spole.map\", \"EPSG:32761\", \"MOLA_color_south\", 7),\n },\n cylindrical(\n \"mercury\",\n \"Mercury Simple Cylindrical\",\n 19900,\n 2439400,\n \"/maps/mercury/mercury_simp_cyl.map\",\n \"MESSENGER\",\n ),\n cylindrical(\n \"venus\",\n \"Venus Simple Cylindrical\",\n 29900,\n 6051800,\n \"/maps/venus/venus_simp_cyl.map\",\n \"MAGELLAN\",\n ),\n cylindrical(\n \"io\",\n \"Io Simple Cylindrical\",\n 50100,\n 1821600,\n \"/maps/jupiter/io_simp_cyl.map\",\n \"SSI_color\",\n ),\n cylindrical(\n \"europa\",\n \"Europa Simple Cylindrical\",\n 50200,\n 1560800,\n \"/maps/jupiter/europa_simp_cyl.map\",\n \"GALILEO_VOYAGER\",\n ),\n cylindrical(\n \"ganymede\",\n \"Ganymede Simple Cylindrical\",\n 50300,\n 2631200,\n \"/maps/jupiter/ganymede_simp_cyl.map\",\n \"GALILEO_VOYAGER\",\n ),\n cylindrical(\n \"callisto\",\n \"Callisto Simple Cylindrical\",\n 50400,\n 2410300,\n \"/maps/jupiter/callisto_simp_cyl.map\",\n \"GALILEO_VOYAGER\",\n ),\n cylindrical(\n \"titan\",\n \"Titan Simple Cylindrical\",\n 60600,\n 2574730,\n \"/maps/saturn/titan_simp_cyl.map\",\n \"Titan_ISS_Controlled_Mosaic\",\n ),\n cylindrical(\n \"pluto\",\n \"Pluto Simple Cylindrical\",\n 99900,\n 1188300,\n \"/maps/pluto/pluto_simp_cyl.map\",\n \"NEWHORIZONS_PLUTO_MOSAIC\",\n ),\n cylindrical(\n \"charon\",\n \"Charon Simple Cylindrical\",\n 90100,\n 606000,\n \"/maps/pluto/charon_simp_cyl.map\",\n \"NEWHORIZONS_CHARON_MOSAIC\",\n ),\n];\n\n/**\n * Look a preset up by its id.\n *\n * Args:\n * id: A `preferences.map.mapCrs` value.\n *\n * Returns:\n * The preset, or `undefined` when `id` is not a preset id.\n */\nexport function getMapCrsPreset(id: string | undefined): MapCrsPreset | undefined {\n return id === undefined ? undefined : MAP_CRS_PRESETS.find((preset) => preset.id === id);\n}\n\n/**\n * Read an `EPSG:<code>` map CRS value.\n *\n * Args:\n * value: A `preferences.map.mapCrs` value.\n *\n * Returns:\n * The numeric EPSG code, or `null` when the value is not an EPSG CRS.\n */\nexport function parseMapCrsEpsgCode(value: string | undefined): number | null {\n const match = /^\\s*EPSG:(\\d+)\\s*$/i.exec(value ?? \"\");\n if (!match) return null;\n const code = Number(match[1]);\n return Number.isSafeInteger(code) && code > 0 ? code : null;\n}\n\n/**\n * Normalize a stored map CRS.\n *\n * Args:\n * value: The raw `preferences.map.mapCrs` value.\n *\n * Returns:\n * A preset id, `\"EPSG:<code>\"`, or `undefined` for the default Web Mercator\n * map (absent, malformed or a Web Mercator code).\n */\nexport function normalizeMapCrs(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n if (getMapCrsPreset(value)) return value;\n const code = parseMapCrsEpsgCode(value);\n if (code === null || WEB_MERCATOR_CODES.has(code)) return undefined;\n // A code a preset uses (EPSG:3413, 3031, 4326) is that preset: it carries a\n // basemap, and MapLibre registers one grid per code, so a derived grid for\n // the bare code would clash with the preset's.\n const epsg = `EPSG:${code}`;\n return MAP_CRS_PRESETS.find((preset) => preset.code === epsg)?.id ?? epsg;\n}\n\n/**\n * The map preferences that draw a map in a CRS, as a Settings choice writes them.\n *\n * Args:\n * mapCrs: A normalized map CRS, or `null` for the default map.\n *\n * Returns:\n * The preferences to merge. A CRS brings the body it maps, so measurements\n * follow it (the EPSG tables describe Earth); the default map leaves the body\n * alone.\n */\nexport function mapCrsPreferencePatch(mapCrs: string | null): {\n mapCrs: string | undefined;\n ellipsoidId?: string;\n} {\n if (mapCrs === null) return { mapCrs: undefined };\n return { mapCrs, ellipsoidId: getMapCrsPreset(mapCrs)?.ellipsoidId ?? \"earth\" };\n}\n\n/**\n * Point a Web Mercator WMS or ArcGIS export template at a map CRS.\n *\n * GeoLibre's WMS layers request `BBOX={bbox-epsg-3857}` in `EPSG:3857`. On a\n * map in another CRS each tile is a box in that CRS instead, which MapLibre\n * writes into the `{bbox}` token, so the template asks for `{bbox}` in `code`.\n * WMS requests move to version 1.1.1, whose `SRS` BBOX is east, north for every\n * CRS (1.3.0 swaps the axes of geographic ones), and an ArcGIS `export`\n * template has its `bboxSR`/`imageSR` set to the EPSG number.\n *\n * Args:\n * template: A raster tile template.\n * code: The map CRS's authority code, e.g. `EPSG:3031` or `IAU_2015:49918`.\n *\n * Returns:\n * The template for the CRS, the template unchanged when it already uses\n * `{bbox}`, or `null` for a template with no bounding box (an XYZ or TMS\n * template), which cannot be drawn in another CRS.\n */\nexport function adaptBboxTemplateToCrs(template: string, code: string): string | null {\n if (template.includes(\"{bbox}\")) return template;\n if (!template.includes(\"{bbox-epsg-3857}\")) return null;\n let url = template.replace(/\\{bbox-epsg-3857\\}/g, \"{bbox}\");\n const epsg = /^EPSG:(\\d+)$/i.exec(code)?.[1];\n // Replace (or append) one query parameter, matching its name in any case.\n // Hand-rolled because URLSearchParams would percent-encode the braces of the\n // MapLibre tokens.\n const setParam = (name: string, value: string) => {\n const pattern = new RegExp(`([?&])${name}=[^&]*`, \"i\");\n url = pattern.test(url)\n ? url.replace(pattern, `$1${name}=${value}`)\n : `${url}${url.includes(\"?\") ? \"&\" : \"?\"}${name}=${value}`;\n };\n const removeParam = (name: string) => {\n url = url.replace(new RegExp(`([?&])${name}=[^&]*(&?)`, \"i\"), (_, lead, trail) =>\n trail ? lead : \"\",\n );\n };\n if (/[?&]bboxSR=/i.test(url)) {\n // An ArcGIS MapServer/ImageServer export: it takes a WKID, not a code.\n if (!epsg) return null;\n setParam(\"bboxSR\", epsg);\n setParam(\"imageSR\", epsg);\n return url;\n }\n removeParam(\"CRS\");\n setParam(\"SRS\", code);\n setParam(\"VERSION\", \"1.1.1\");\n return url;\n}\n\n/**\n * The smallest centered square grid over the part of a CRS worth mapping, for\n * an EPSG code without a published tile grid.\n *\n * Samples a 2° lng/lat lattice and keeps the points `projection` takes forward\n * and back to where they started (a transverse Mercator's far side does not)\n * whose local scale is at most {@link MAX_RELATIVE_SCALE} times the CRS's\n * smallest. A polar stereographic map round-trips almost to the other pole,\n * but out there a kilometre spans thousands of CRS units, and a grid that wide\n * leaves the CRS's own area a speck at every useful zoom. The extent of the\n * kept points is padded by 4%. MapLibre finds the edge of a world projection\n * by walking out from the grid's center, so the grid is centered on them.\n *\n * Args:\n * projection: Lng/lat to CRS and back, the shape of a proj4 converter.\n * geographic: Whether the CRS is in degrees, which gets the whole-world grid.\n *\n * Returns:\n * The grid, or `null` when no sampled point survives the round trip.\n */\nexport function deriveTileMatrixSet(\n projection: {\n forward(position: number[]): number[];\n inverse(position: number[]): number[] | null;\n },\n geographic: boolean,\n): MapCrsTileMatrixSet | null {\n if (geographic) return GEOGRAPHIC_GRID;\n const forward = (lng: number, lat: number): [number, number] | null => {\n try {\n const [x, y] = projection.forward([lng, lat]) ?? [];\n // A billion CRS units (a million km in metres) is no longer a map.\n return Number.isFinite(x) && Number.isFinite(y) && Math.abs(x) <= 1e9 && Math.abs(y) <= 1e9\n ? [x, y]\n : null;\n } catch {\n return null;\n }\n };\n const roundTrips = (point: [number, number], lng: number, lat: number): boolean => {\n let back: number[] | null;\n try {\n back = projection.inverse(point);\n } catch {\n return false;\n }\n if (!back || !Number.isFinite(back[0]) || !Number.isFinite(back[1])) return false;\n const dLng = Math.abs(((((back[0] - lng) % 360) + 540) % 360) - 180);\n return Math.abs(back[1] - lat) <= 1e-4 && dLng <= 1e-4;\n };\n // CRS units per radian of arc, along the meridian and the parallel; the\n // larger is the point's scale. Only ratios between points matter, so the\n // CRS's unit (metre, foot) drops out.\n const STEP = 0.01;\n const radians = (STEP * Math.PI) / 180;\n const samples: { x: number; y: number; scale: number }[] = [];\n for (let lng = -180; lng <= 180; lng += 2) {\n for (let lat = -88; lat <= 88; lat += 2) {\n const point = forward(lng, lat);\n if (!point || !roundTrips(point, lng, lat)) continue;\n const north = forward(lng, lat + STEP);\n const east = forward(lng + STEP, lat);\n if (!north || !east) continue;\n const scale = Math.max(\n Math.hypot(north[0] - point[0], north[1] - point[1]) / radians,\n Math.hypot(east[0] - point[0], east[1] - point[1]) /\n (radians * Math.cos((lat * Math.PI) / 180)),\n );\n if (scale > 0 && Number.isFinite(scale)) samples.push({ x: point[0], y: point[1], scale });\n }\n }\n if (samples.length === 0) return null;\n const limit = MAX_RELATIVE_SCALE * Math.min(...samples.map((sample) => sample.scale));\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n for (const { x, y, scale } of samples) {\n if (scale > limit) continue;\n minX = Math.min(minX, x);\n minY = Math.min(minY, y);\n maxX = Math.max(maxX, x);\n maxY = Math.max(maxY, y);\n }\n const side = Math.max(maxX - minX, maxY - minY) * 1.04;\n if (!(side > 0)) return null;\n return {\n origin: [(minX + maxX - side) / 2, (minY + maxY + side) / 2],\n extentAtZoom0: side,\n };\n}\n\n/**\n * How much more a derived grid's edge may stretch distances than the CRS's\n * least distorted place: about 11°N for an Antarctic polar stereographic map,\n * and some 65° either side of a transverse Mercator's central meridian.\n */\nconst MAX_RELATIVE_SCALE = 2.5;\n","import type { Feature, FeatureCollection, GeoJSON, Geometry, Position } from \"geojson\";\n\n/**\n * Helpers for vector data whose coordinates carry a Z value (elevation), e.g.\n * GPX tracks with `<ele>` readings or LineStringZ/PointZ geometries. MapLibre's\n * 2D style layers ignore the third coordinate, so layers that want to render\n * their Z values use these helpers to detect and rescale elevations before\n * handing the data to a deck.gl overlay. The same data can carry its elevation\n * in a `bbox` member too, which `horizontalBbox` trims back to two dimensions.\n */\n\n/**\n * Reduces a bounding box to its horizontal extent, `[west, south, east, north]`.\n *\n * RFC 7946 §5 lets a `bbox` member carry elevation, in which case it holds six\n * values — `[west, south, minAltitude, east, north, maxAltitude]` — and\n * `@turf/bbox` returns a collection's own member verbatim rather than\n * recomputing it. A consumer reading such a box as four values takes the\n * altitude for a longitude and the east edge for a latitude; that is how a 3D\n * feed reached MapLibre as an out-of-range latitude (#2358).\n *\n * Returns null when there is no usable horizontal extent: a box of any other\n * length, or one carrying a non-finite value anywhere — an empty collection, or\n * one whose features all have a null geometry, bboxes to ±Infinity, and a box\n * whose altitudes are not finite is not one to trust the rest of.\n *\n * @param box - A GeoJSON or Turf bounding box, of four or six values.\n */\nexport function horizontalBbox(\n box: readonly number[] | null | undefined,\n): [number, number, number, number] | null {\n if (!box) return null;\n if (box.length !== 4 && box.length !== 6) return null;\n if (!box.every((value) => Number.isFinite(value))) return null;\n return box.length === 6 ? [box[0], box[1], box[3], box[4]] : [box[0], box[1], box[2], box[3]];\n}\n\n// Proving the *negative* (no Z anywhere) walks every coordinate, so cache the\n// verdict per GeoJSON object — the map sync, the deck overlay, and the Style\n// panel all ask the same question about the same (immutable) data.\nconst hasZCache = new WeakMap<object, boolean>();\nconst hasFiniteZCache = new WeakMap<object, boolean>();\n\n/**\n * Returns true when any coordinate in the GeoJSON carries a finite, non-zero\n * Z value. All-zero Z arrays are treated as flat data because rendering them\n * \"in 3D\" would be indistinguishable from the 2D result. The result is\n * cached per GeoJSON object, so repeated calls on the same data are free.\n *\n * @param geojson - Any GeoJSON object (geometry, feature, or collection).\n * @param includeZero - Accept zero elevations when explicit absolute-height placement matters.\n */\nexport function geojsonHasZCoordinates(\n geojson: GeoJSON | null | undefined,\n includeZero = false,\n): boolean {\n if (!geojson) return false;\n const cache = includeZero ? hasFiniteZCache : hasZCache;\n const cached = cache.get(geojson);\n if (cached !== undefined) return cached;\n const result = someGeometry(geojson, (geometry) =>\n somePosition(geometry, (position) => {\n const z = position[2];\n return typeof z === \"number\" && Number.isFinite(z) && (includeZero || z !== 0);\n }),\n );\n cache.set(geojson, result);\n return result;\n}\n\n/**\n * Returns a copy of the GeoJSON with every coordinate's Z value mapped to\n * `z * verticalScale + offset` (missing or non-finite Z treated as 0). Used\n * to apply vertical exaggeration and a constant altitude offset before 3D\n * rendering. The identity transform (`verticalScale === 1 && offset === 0`)\n * still produces a copy so the non-finite-Z sanitization applies on every\n * path — WebGL does not handle NaN positions gracefully.\n *\n * @param geojson - The feature collection to transform.\n * @param verticalScale - Multiplier applied to each Z value.\n * @param offset - Constant altitude in meters added after scaling.\n */\nexport function transformGeojsonElevation(\n geojson: FeatureCollection,\n verticalScale: number,\n offset: number,\n): FeatureCollection {\n // Positions are normalized to [x, y, z]; a 4th measure value (M), which\n // some producers emit, is intentionally dropped — nothing downstream of\n // this render path consumes it.\n const mapPosition = (position: Position): Position => [\n position[0],\n position[1],\n (typeof position[2] === \"number\" && Number.isFinite(position[2]) ? position[2] : 0) *\n verticalScale +\n offset,\n ];\n return {\n ...geojson,\n features: geojson.features.map((feature) => ({\n ...feature,\n geometry: mapGeometryPositions(feature.geometry, mapPosition),\n })),\n };\n}\n\nfunction someGeometry(geojson: GeoJSON, predicate: (geometry: Geometry) => boolean): boolean {\n switch (geojson.type) {\n case \"FeatureCollection\":\n return geojson.features.some((feature) =>\n feature.geometry ? someGeometry(feature.geometry, predicate) : false,\n );\n case \"Feature\":\n return geojson.geometry ? someGeometry(geojson.geometry, predicate) : false;\n case \"GeometryCollection\":\n return geojson.geometries.some((geometry) => someGeometry(geometry, predicate));\n default:\n return predicate(geojson);\n }\n}\n\nfunction somePosition(geometry: Geometry, predicate: (position: Position) => boolean): boolean {\n switch (geometry.type) {\n case \"Point\":\n return predicate(geometry.coordinates);\n case \"MultiPoint\":\n case \"LineString\":\n return geometry.coordinates.some(predicate);\n case \"MultiLineString\":\n case \"Polygon\":\n return geometry.coordinates.some((ring) => ring.some(predicate));\n case \"MultiPolygon\":\n return geometry.coordinates.some((polygon) => polygon.some((ring) => ring.some(predicate)));\n case \"GeometryCollection\":\n return geometry.geometries.some((child) => somePosition(child, predicate));\n default:\n return false;\n }\n}\n\nfunction mapGeometryPositions(\n geometry: Feature[\"geometry\"],\n mapPosition: (position: Position) => Position,\n): Feature[\"geometry\"] {\n if (!geometry) return geometry;\n switch (geometry.type) {\n case \"Point\":\n return { ...geometry, coordinates: mapPosition(geometry.coordinates) };\n case \"MultiPoint\":\n case \"LineString\":\n return { ...geometry, coordinates: geometry.coordinates.map(mapPosition) };\n case \"MultiLineString\":\n case \"Polygon\":\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((ring) => ring.map(mapPosition)),\n };\n case \"MultiPolygon\":\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((polygon) =>\n polygon.map((ring) => ring.map(mapPosition)),\n ),\n };\n case \"GeometryCollection\":\n return {\n ...geometry,\n geometries: geometry.geometries.map(\n (child) => mapGeometryPositions(child, mapPosition) as Geometry,\n ),\n };\n default:\n return geometry;\n }\n}\n","/**\n * Color-ramp definitions and classification-break helpers shared by the\n * vector graduated-styling panel and the raster symbology panel. Kept in\n * `@geolibre/core` (dependency-light, map-agnostic) so both UIs derive class\n * colors and breaks from the same source. The ramp `value`s intentionally\n * match the named colormaps shipped by `@developmentseed/deck.gl-raster`\n * (`COLORMAP_INDEX` keys) so a ramp choice maps 1:1 onto the raster render\n * path.\n */\n\nexport type ColorRamp = {\n value: string;\n label: string;\n colors: readonly string[];\n};\n\n/** The built-in color ramps offered by the style panels. */\nexport const VECTOR_COLOR_RAMPS: readonly ColorRamp[] = [\n {\n value: \"viridis\",\n label: \"Viridis\",\n colors: [\"#440154\", \"#31688e\", \"#35b779\", \"#fde725\"],\n },\n {\n value: \"plasma\",\n label: \"Plasma\",\n colors: [\"#0d0887\", \"#9c179e\", \"#ed7953\", \"#f0f921\"],\n },\n {\n value: \"inferno\",\n label: \"Inferno\",\n colors: [\"#000004\", \"#781c6d\", \"#ed6925\", \"#fcffa4\"],\n },\n {\n value: \"magma\",\n label: \"Magma\",\n colors: [\"#000004\", \"#721f81\", \"#f1605d\", \"#fcfdbf\"],\n },\n {\n value: \"cividis\",\n label: \"Cividis\",\n colors: [\"#00204d\", \"#575d6d\", \"#a59c74\", \"#ffea46\"],\n },\n {\n value: \"turbo\",\n label: \"Turbo\",\n colors: [\"#30123b\", \"#4777ef\", \"#1ccfd0\", \"#b9e642\", \"#fb8022\", \"#7a0403\"],\n },\n {\n value: \"spectral\",\n label: \"Spectral\",\n colors: [\"#9e0142\", \"#f46d43\", \"#ffffbf\", \"#66c2a5\", \"#5e4fa2\"],\n },\n {\n value: \"blues\",\n label: \"Blues\",\n colors: [\"#eff6ff\", \"#93c5fd\", \"#2563eb\", \"#1e3a8a\"],\n },\n {\n value: \"greens\",\n label: \"Greens\",\n colors: [\"#f0fdf4\", \"#86efac\", \"#16a34a\", \"#14532d\"],\n },\n {\n value: \"oranges\",\n label: \"Oranges\",\n colors: [\"#fff7ed\", \"#fdba74\", \"#f97316\", \"#7c2d12\"],\n },\n {\n value: \"reds\",\n label: \"Reds\",\n colors: [\"#fff5f0\", \"#fcae91\", \"#fb6a4a\", \"#cb181d\", \"#67000d\"],\n },\n {\n value: \"purples\",\n label: \"Purples\",\n colors: [\"#fcfbfd\", \"#bcbddc\", \"#807dba\", \"#54278f\", \"#3f007d\"],\n },\n {\n value: \"terrain\",\n label: \"Terrain\",\n colors: [\"#333399\", \"#21bcb3\", \"#79d05a\", \"#e8e85a\", \"#a87b54\", \"#ffffff\"],\n },\n {\n value: \"rdylgn\",\n label: \"Red-Yellow-Green\",\n colors: [\"#a50026\", \"#f46d43\", \"#ffffbf\", \"#66bd63\", \"#006837\"],\n },\n {\n value: \"rdylbu\",\n label: \"Red-Yellow-Blue\",\n colors: [\"#a50026\", \"#f46d43\", \"#ffffbf\", \"#74add1\", \"#313695\"],\n },\n {\n value: \"rdbu\",\n label: \"Red-Blue\",\n colors: [\"#b2182b\", \"#ef8a62\", \"#f7f7f7\", \"#67a9cf\", \"#2166ac\"],\n },\n {\n value: \"coolwarm\",\n label: \"Cool-Warm\",\n colors: [\"#3b4cc0\", \"#7b9ff9\", \"#dddcdc\", \"#f49a7b\", \"#b40426\"],\n },\n {\n value: \"jet\",\n label: \"Jet\",\n colors: [\"#000080\", \"#0000ff\", \"#00ffff\", \"#ffff00\", \"#ff0000\", \"#800000\"],\n },\n {\n value: \"greys\",\n label: \"Greys\",\n colors: [\"#ffffff\", \"#bdbdbd\", \"#636363\", \"#000000\"],\n },\n {\n value: \"gray\",\n label: \"Grayscale\",\n colors: [\"#000000\", \"#ffffff\"],\n },\n] as const;\n\n/**\n * Resolves a ramp definition by name, falling back to the first ramp when the\n * name is unknown.\n *\n * @param value - The ramp `value` (e.g. \"viridis\").\n * @returns The matching ramp, or the first ramp.\n */\nexport function getVectorColorRamp(value: string): ColorRamp {\n return VECTOR_COLOR_RAMPS.find((colorRamp) => colorRamp.value === value) ?? VECTOR_COLOR_RAMPS[0];\n}\n\n/**\n * Samples a ramp into `count` evenly spaced hex colors by linearly\n * interpolating between the ramp's anchor colors.\n *\n * @param colorRamp - The ramp `value`.\n * @param count - Number of colors to produce.\n * @returns An array of `count` hex colors (a single end color when count <= 1).\n */\nexport function interpolateRampColors(colorRamp: string, count: number): string[] {\n return interpolateColors(getVectorColorRamp(colorRamp).colors, count);\n}\n\n/**\n * Samples an explicit list of anchor colors into `count` evenly spaced hex\n * colors by linear interpolation. Backs both the named ramps and user-defined\n * custom ramps, so a list of hex codes is sampled identically to a built-in.\n *\n * @param colors - The anchor colors (at least one).\n * @param count - Number of colors to produce.\n * @returns An array of `count` hex colors (a single end color when count <= 1).\n */\nexport function interpolateColors(colors: readonly string[], count: number): string[] {\n const anchors = colors.length > 0 ? colors : [\"#000000\"];\n if (count <= 0) return [];\n if (count === 1) return [anchors[anchors.length - 1]];\n if (anchors.length === 1) {\n return Array.from({ length: count }, () => anchors[0]);\n }\n return Array.from({ length: count }, (_, index) => {\n const scaled = (index / (count - 1)) * (anchors.length - 1);\n const lowerIndex = Math.floor(scaled);\n const upperIndex = Math.min(anchors.length - 1, Math.ceil(scaled));\n const ratio = scaled - lowerIndex;\n return interpolateHexColor(anchors[lowerIndex], anchors[upperIndex], ratio);\n });\n}\n\n/**\n * Normalizes a single user-entered color token into a canonical `#rrggbb`\n * lowercase hex string, or null when it is not a valid 3- or 6-digit hex\n * color. Accepts values with or without a leading `#` and expands shorthand\n * (`#abc` → `#aabbcc`).\n *\n * @param token - A raw color token (e.g. \"FF0000\", \"#f00\", \" #AaBbCc \").\n * @returns The canonical `#rrggbb` color, or null when invalid.\n */\nexport function normalizeHexColor(token: string): string | null {\n let value = token.trim().toLowerCase();\n if (value === \"\") return null;\n if (!value.startsWith(\"#\")) value = `#${value}`;\n if (/^#[0-9a-f]{3}$/.test(value)) {\n value = `#${value\n .slice(1)\n .split(\"\")\n .map((channel) => channel + channel)\n .join(\"\")}`;\n }\n return /^#[0-9a-f]{6}$/.test(value) ? value : null;\n}\n\n/**\n * Parses a free-text list of hex color codes into canonical `#rrggbb` colors,\n * for the custom color-ramp inputs. Tokens may be separated by commas,\n * semicolons, or any whitespace (including newlines); invalid tokens are\n * dropped so a partial paste still yields the colors it can.\n *\n * @param input - The raw text the user entered.\n * @returns The valid, normalized colors in input order.\n */\nexport function parseHexColorList(input: string): string[] {\n return input\n .split(/[\\s,;]+/)\n .map(normalizeHexColor)\n .filter((color): color is string => color !== null);\n}\n\n/**\n * Linearly interpolates between two hex colors.\n *\n * @param from - Start hex color (e.g. \"#440154\").\n * @param to - End hex color.\n * @param ratio - Blend factor in [0, 1].\n * @returns The interpolated hex color.\n */\nexport function interpolateHexColor(from: string, to: string, ratio: number): string {\n const start = parseHexColor(from);\n const end = parseHexColor(to);\n return rgbToHex({\n r: Math.round(start.r + (end.r - start.r) * ratio),\n g: Math.round(start.g + (end.g - start.g) * ratio),\n b: Math.round(start.b + (end.b - start.b) * ratio),\n });\n}\n\n/**\n * Parses a `#rrggbb` hex color into its RGB channels.\n *\n * @param value - A `#rrggbb` hex color.\n * @returns The red, green, and blue channels (0-255).\n */\nexport function parseHexColor(value: string): { b: number; g: number; r: number } {\n // Caller must pass a well-formed \"#rrggbb\" string; malformed input parses to\n // NaN and the bitwise ops below coerce it to black ({ r: 0, g: 0, b: 0 }).\n const numeric = Number.parseInt(value.slice(1), 16);\n return {\n r: (numeric >> 16) & 255,\n g: (numeric >> 8) & 255,\n b: numeric & 255,\n };\n}\n\n/**\n * Formats RGB channels as a `#rrggbb` hex color.\n *\n * @param color - The red, green, and blue channels (0-255).\n * @returns A `#rrggbb` hex color.\n */\nexport function rgbToHex(color: { b: number; g: number; r: number }): string {\n return `#${[color.r, color.g, color.b]\n .map((channel) => channel.toString(16).padStart(2, \"0\"))\n .join(\"\")}`;\n}\n\n/**\n * Builds `count` evenly spaced break values across [min, max].\n *\n * @param min - The minimum value.\n * @param max - The maximum value.\n * @param count - Number of breaks to produce.\n * @returns The break values (min..max inclusive when count > 1).\n */\nexport function createEqualIntervalBreaks(min: number, max: number, count: number): number[] {\n return Array.from({ length: count }, (_, index) => {\n const ratio = count === 1 ? 0 : index / (count - 1);\n return min + (max - min) * ratio;\n });\n}\n\n/**\n * Builds `count` quantile break values from a sample of numeric values.\n *\n * These are ramp *edges* spanning the whole sample (the first break is the\n * minimum, the last the maximum), matching {@link createEqualIntervalBreaks}.\n * Graduated vector classes want class *lower bounds* instead; use\n * {@link createGraduatedClassBreaks}.\n *\n * @param values - The sample values.\n * @param count - Number of breaks to produce.\n * @returns The quantile break values.\n */\nexport function createQuantileBreaks(values: number[], count: number): number[] {\n if (count <= 0) return [];\n const sorted = [...values].sort((a, b) => a - b);\n // An empty sample would read `undefined` through the index math below and\n // yield NaN breaks; callers that have no values get an empty result instead.\n if (sorted.length === 0) return [];\n return Array.from({ length: count }, (_, index) => {\n const position = count === 1 ? 0 : (index / (count - 1)) * (sorted.length - 1);\n return quantileAtPosition(sorted, position);\n });\n}\n\n/** Linearly interpolated order statistic at a fractional index into `sorted`. */\nfunction quantileAtPosition(sorted: number[], position: number): number {\n const lowerIndex = Math.floor(position);\n const upperIndex = Math.min(sorted.length - 1, Math.ceil(position));\n const ratio = position - lowerIndex;\n return sorted[lowerIndex] + (sorted[upperIndex] - sorted[lowerIndex]) * ratio;\n}\n\n/** The classification schemes the graduated vector renderer offers. */\nexport type GraduatedClassificationScheme =\n | \"equal-interval\"\n | \"quantile\"\n | \"natural-breaks\"\n | \"standard-deviation\"\n | \"geometric-interval\";\n\n/**\n * Builds the class *lower bounds* for a graduated vector renderer.\n *\n * A graduated renderer's stops are lower bounds, not ramp edges: with N stops\n * sorted ascending, class `i` covers `[stops[i], stops[i + 1])` and the last\n * class is open-ended above. The legend (`≥ value`) and the QML/SLD exporters\n * already read them that way, so the breaks must follow the same convention:\n * `count` classes yield `count` breaks whose first entry is the sample minimum\n * and whose last entry opens the top class. Producing edges instead (ending at\n * the maximum) leaves the top class holding only the single largest feature and\n * puts the interior breaks at the wrong percentiles.\n *\n * The result is strictly ascending: duplicate breaks (a skewed sample can push\n * several quantiles onto the same value) are collapsed, so the caller may get\n * fewer than `count` breaks and must size its colors off `breaks.length`.\n * MapLibre rejects a `step` expression whose inputs are not strictly ascending,\n * so this de-duplication is load-bearing, not cosmetic.\n *\n * Standard deviation and geometric interval derive their breaks from the data,\n * so `count` is a ceiling there: a standard-deviation break that falls outside\n * the sample range is dropped rather than producing an empty class. See\n * {@link standardDeviationClassBreaks} and {@link geometricIntervalClassBreaks}\n * for how each treats its edge cases.\n *\n * @param values - The finite numeric values of the classified property.\n * @param count - Number of classes requested.\n * @param scheme - The classification scheme.\n * @returns Up to `count` strictly ascending class lower bounds.\n */\nexport function createGraduatedClassBreaks(\n values: number[],\n count: number,\n scheme: GraduatedClassificationScheme,\n): number[] {\n if (count <= 0 || values.length === 0) return [];\n if (scheme === \"natural-breaks\") return ascendingUnique(naturalClassBreaks(values, count));\n const sorted = [...values].sort((a, b) => a - b);\n if (scheme === \"quantile\") {\n return ascendingUnique(\n Array.from({ length: count }, (_, index) =>\n quantileAtPosition(sorted, (index / count) * (sorted.length - 1)),\n ),\n );\n }\n if (scheme === \"standard-deviation\") {\n return ascendingUnique(standardDeviationClassBreaks(sorted, count));\n }\n if (scheme === \"geometric-interval\") {\n const breaks = geometricIntervalClassBreaks(sorted, count);\n if (breaks) return ascendingUnique(breaks);\n }\n return ascendingUnique(equalIntervalClassBreaks(sorted, count));\n}\n\n/** Equal-interval lower bounds over the range of an ascending sample. */\nfunction equalIntervalClassBreaks(sorted: number[], count: number): number[] {\n const min = sorted[0];\n const max = sorted[sorted.length - 1];\n return Array.from({ length: count }, (_, index) => min + ((max - min) * index) / count);\n}\n\n/**\n * Standard-deviation lower bounds: classes one standard deviation wide,\n * centered on the mean. An even `count` puts a break on the mean (classes\n * `[mean - σ, mean)` and `[mean, mean + σ)` meet there); an odd `count` makes\n * the middle class straddle it (`[mean - σ/2, mean + σ/2)`). The first break is\n * the sample minimum, and interior breaks outside `(min, max]` are dropped, so\n * the open-ended bottom and top classes absorb the tails and no class is empty.\n * A constant sample (σ = 0) yields the minimum alone.\n *\n * Uses the population standard deviation, as QGIS and ArcGIS do.\n */\nfunction standardDeviationClassBreaks(sorted: number[], count: number): number[] {\n const min = sorted[0];\n const max = sorted[sorted.length - 1];\n const mean = sorted.reduce((sum, value) => sum + value, 0) / sorted.length;\n const variance =\n sorted.reduce((sum, value) => sum + (value - mean) * (value - mean), 0) / sorted.length;\n const deviation = Math.sqrt(variance);\n if (!(deviation > 0)) return [min];\n const interior: number[] = [];\n for (let index = 1; index < count; index += 1) {\n const value = mean + (index - count / 2) * deviation;\n if (value > min && value <= max) interior.push(value);\n }\n return [min, ...interior];\n}\n\n/**\n * Geometric-interval lower bounds: class edges growing by a constant ratio\n * from the smallest positive value to the maximum, so each class spans the\n * same multiple of its lower bound. Suits skewed data spanning orders of\n * magnitude (population, income, concentrations).\n *\n * A geometric series cannot start at or below zero, so it starts at the\n * smallest positive value and the first break stays the sample minimum: zero\n * and negative values all fall into the first class. When no value is\n * positive, the result is `null` and the caller falls back to equal interval.\n */\nfunction geometricIntervalClassBreaks(sorted: number[], count: number): number[] | null {\n const max = sorted[sorted.length - 1];\n const lowest = sorted.find((value) => value > 0);\n if (lowest === undefined) return null;\n // Step in log space: `max / lowest` overflows to Infinity for samples\n // spanning more than ~308 orders of magnitude, while the logs stay finite.\n const logLowest = Math.log(lowest);\n const logStep = (Math.log(max) - logLowest) / count;\n return [\n sorted[0],\n ...Array.from({ length: count - 1 }, (_, index) => Math.exp(logLowest + logStep * (index + 1))),\n ];\n}\n\n/** Drops non-finite breaks and collapses repeats, keeping ascending order. */\nfunction ascendingUnique(breaks: number[]): number[] {\n const out: number[] = [];\n for (const value of breaks) {\n if (!Number.isFinite(value)) continue;\n if (out.length > 0 && value <= out[out.length - 1]) continue;\n out.push(value);\n }\n return out;\n}\n\n/** Cap on the Jenks input size; the DP below is roughly O(n^2 * k). */\nconst MAX_NATURAL_BREAK_SAMPLES = 1000;\n\n/** Evenly thins a sorted array down to at most `maxSamples` entries. */\nfunction downsampleSortedValues(values: number[], maxSamples: number): number[] {\n if (values.length <= maxSamples) return values;\n const result: number[] = [];\n const step = (values.length - 1) / (maxSamples - 1);\n for (let index = 0; index < maxSamples; index += 1) {\n result.push(values[Math.round(index * step)]);\n }\n return result;\n}\n\n/**\n * Jenks natural breaks: the class lower bounds that minimize the summed\n * within-class variance. Returns the sample minimum first, then the first value\n * of each subsequent class.\n */\nfunction naturalClassBreaks(values: number[], count: number): number[] {\n const unique = Array.from(new Set(values)).sort((a, b) => a - b);\n // Cap the input so a large layer does not freeze the Style panel's UI thread.\n const sorted = downsampleSortedValues(unique, MAX_NATURAL_BREAK_SAMPLES);\n if (sorted.length <= count) return sorted;\n\n const lowerClassLimits = Array.from({ length: sorted.length + 1 }, () =>\n Array(count + 1).fill(0),\n );\n const varianceCombinations = Array.from({ length: sorted.length + 1 }, () =>\n Array(count + 1).fill(Number.POSITIVE_INFINITY),\n );\n\n for (let classIndex = 1; classIndex <= count; classIndex += 1) {\n lowerClassLimits[1][classIndex] = 1;\n varianceCombinations[1][classIndex] = 0;\n }\n\n for (let valueIndex = 2; valueIndex <= sorted.length; valueIndex += 1) {\n let sum = 0;\n let sumSquares = 0;\n let weight = 0;\n\n for (let lowerIndex = 1; lowerIndex <= valueIndex; lowerIndex += 1) {\n const currentIndex = valueIndex - lowerIndex + 1;\n const value = sorted[currentIndex - 1];\n weight += 1;\n sum += value;\n sumSquares += value * value;\n const variance = sumSquares - (sum * sum) / weight;\n const previousIndex = currentIndex - 1;\n if (previousIndex === 0) continue;\n\n for (let classIndex = 2; classIndex <= count; classIndex += 1) {\n const candidate = variance + varianceCombinations[previousIndex][classIndex - 1];\n if (varianceCombinations[valueIndex][classIndex] >= candidate) {\n lowerClassLimits[valueIndex][classIndex] = currentIndex;\n varianceCombinations[valueIndex][classIndex] = candidate;\n }\n }\n }\n\n lowerClassLimits[valueIndex][1] = 1;\n varianceCombinations[valueIndex][1] = sumSquares - (sum * sum) / Math.max(1, weight);\n }\n\n // Walk the DP table back from the top class: `lowerClassLimits[i][k]` is the\n // 1-based index in `sorted` where class `k` starts, which is exactly that\n // class's lower bound. Class 1 always starts at the sample minimum.\n const breaks = Array(count).fill(sorted[0]) as number[];\n let valueIndex = sorted.length;\n for (let classIndex = count; classIndex >= 2; classIndex -= 1) {\n const lowerClassLimit = Math.max(1, lowerClassLimits[valueIndex][classIndex]);\n breaks[classIndex - 1] = sorted[lowerClassLimit - 1];\n valueIndex = lowerClassLimit - 1;\n }\n breaks[0] = sorted[0];\n return breaks;\n}\n","import type {\n Feature,\n FeatureCollection,\n GeoJsonProperties,\n LineString,\n MultiLineString,\n} from \"geojson\";\n\n/**\n * Pure TypeScript Google Encoded Polyline algorithm codec.\n *\n * Supports arbitrary precision:\n * - precision = 5: Google Maps standard, OSRM, HERE (factor 1e5, ~1.1m precision)\n * - precision = 6: Valhalla, Mapbox, Open Source Routing Machine 6 (factor 1e6, ~0.11m precision)\n *\n * Coordinates are represented as `[lon, lat]` pairs in GeoJSON format.\n */\n\n/**\n * Unescapes backslashes in a polyline string.\n *\n * When polyline strings are copied from JSON payloads, log files, or source code,\n * backslashes are frequently double-escaped as `\\\\` or formatted with escape sequences.\n *\n * @param str - Input string with potentially escaped backslashes.\n * @returns Cleaned polyline string.\n */\nexport function unescapePolyline(str: string): string {\n if (!str || typeof str !== \"string\") return \"\";\n return str.replace(/\\\\\\\\/g, \"\\\\\").replace(/\\\\\"/g, '\"').replace(/\\\\'/g, \"'\");\n}\n\nexport interface PolylineDecodeResult {\n coordinates: [number, number][];\n complete: boolean;\n}\n\n/**\n * Decodes an encoded polyline string into an array of `[lon, lat]` coordinates,\n * along with a `complete` flag indicating whether the entire string was consumed\n * as complete coordinate pairs without truncation or malformed characters.\n *\n * @param encoded - The ASCII-encoded polyline string.\n * @param precision - Number of decimal digits (default: 5).\n * @param unescape - Whether to unescape double-escaped backslashes before decoding (default: false).\n * @returns Object with decoded `coordinates` and `complete` boolean status.\n */\nexport function decodePolylineDetailed(\n encoded: string,\n precision = 5,\n unescape = false,\n): PolylineDecodeResult {\n if (!encoded || typeof encoded !== \"string\") {\n return { coordinates: [], complete: true };\n }\n const cleanEncoded = unescape ? unescapePolyline(encoded) : encoded;\n const factor = 10 ** precision;\n const len = cleanEncoded.length;\n const coordinates: [number, number][] = [];\n let index = 0;\n let lat = 0;\n let lon = 0;\n\n while (index < len) {\n let shift = 0;\n let result = 0;\n let byte: number;\n\n // Decode latitude delta\n let latComplete = false;\n do {\n if (index >= len) return { coordinates, complete: false };\n byte = cleanEncoded.charCodeAt(index++) - 63;\n if (byte < 0 || byte > 63) return { coordinates, complete: false };\n if (shift > 30 || (shift === 30 && (byte & 0x1f) > 0x03)) {\n return { coordinates, complete: false };\n }\n result |= (byte & 0x1f) << shift;\n shift += 5;\n if (byte < 0x20) {\n latComplete = true;\n break;\n }\n } while (byte >= 0x20);\n\n if (!latComplete) return { coordinates, complete: false };\n\n // Unsigned right shift (>>>) for 32-bit safe zigzag decoding\n lat += result & 1 ? ~(result >>> 1) : result >>> 1;\n\n shift = 0;\n result = 0;\n\n // Decode longitude delta\n let lonComplete = false;\n do {\n if (index >= len) return { coordinates, complete: false };\n byte = cleanEncoded.charCodeAt(index++) - 63;\n if (byte < 0 || byte > 63) return { coordinates, complete: false };\n if (shift > 30 || (shift === 30 && (byte & 0x1f) > 0x03)) {\n return { coordinates, complete: false };\n }\n result |= (byte & 0x1f) << shift;\n shift += 5;\n if (byte < 0x20) {\n lonComplete = true;\n break;\n }\n } while (byte >= 0x20);\n\n if (!lonComplete) return { coordinates, complete: false };\n\n lon += result & 1 ? ~(result >>> 1) : result >>> 1;\n\n coordinates.push([lon / factor, lat / factor]);\n }\n\n return { coordinates, complete: true };\n}\n\n/**\n * Decodes an encoded polyline string into an array of `[lon, lat]` coordinates.\n *\n * @param encoded - The ASCII-encoded polyline string.\n * @param precision - Number of decimal digits (default: 5).\n * @param unescape - Whether to unescape double-escaped backslashes before decoding (default: false).\n * @returns Array of `[lon, lat]` coordinate pairs in GeoJSON order.\n */\nexport function decodePolyline(\n encoded: string,\n precision = 5,\n unescape = false,\n): [number, number][] {\n return decodePolylineDetailed(encoded, precision, unescape).coordinates;\n}\n\n/**\n * Encodes a single integer delta value into polyline character chunks.\n */\nfunction encodeSignedNumber(num: number): string {\n let sgnNum = num < 0 ? ~(num << 1) : num << 1;\n let out = \"\";\n while (sgnNum >= 0x20) {\n out += String.fromCharCode((0x20 | (sgnNum & 0x1f)) + 63);\n sgnNum >>>= 5;\n }\n out += String.fromCharCode(sgnNum + 63);\n return out;\n}\n\n/**\n * Encodes an array of `[lon, lat]` coordinates into a Google Encoded Polyline string.\n *\n * @param coordinates - Array of `[lon, lat]` coordinate pairs.\n * @param precision - Number of decimal digits (default: 5).\n * @returns The encoded polyline string.\n */\nexport function encodePolyline(coordinates: [number, number][], precision = 5): string {\n if (!Array.isArray(coordinates) || coordinates.length === 0) return \"\";\n const factor = 10 ** precision;\n let output = \"\";\n let prevLat = 0;\n let prevLon = 0;\n\n for (const [lon, lat] of coordinates) {\n if (!Number.isFinite(lon) || !Number.isFinite(lat)) continue;\n const roundLat = Math.round(lat * factor);\n const roundLon = Math.round(lon * factor);\n\n const deltaLat = roundLat - prevLat;\n const deltaLon = roundLon - prevLon;\n\n output += encodeSignedNumber(deltaLat);\n output += encodeSignedNumber(deltaLon);\n\n prevLat = roundLat;\n prevLon = roundLon;\n }\n\n return output;\n}\n\n/**\n * Decodes an encoded polyline string into a GeoJSON `Feature<LineString>`.\n *\n * @param encoded - The polyline string.\n * @param precision - Coordinate precision (default: 5).\n * @param properties - Optional feature properties.\n * @returns A GeoJSON LineString Feature.\n */\nexport function polylineStrToGeoJSON(\n encoded: string,\n precision = 5,\n properties: GeoJsonProperties = {},\n): Feature<LineString> {\n return {\n type: \"Feature\",\n properties: { ...properties },\n geometry: {\n type: \"LineString\",\n coordinates: decodePolyline(encoded, precision),\n },\n };\n}\n\n/**\n * Encodes a GeoJSON LineString, MultiLineString, Feature, or FeatureCollection into encoded polyline string(s).\n *\n * @param geojson - The GeoJSON input geometry or feature.\n * @param precision - Coordinate precision (default: 5).\n * @returns An encoded polyline string for LineString, or an array of strings for MultiLineString / FeatureCollection.\n */\nexport function geoJSONToPolylineStr(\n geojson:\n | LineString\n | MultiLineString\n | Feature<LineString | MultiLineString>\n | FeatureCollection<LineString | MultiLineString>,\n precision = 5,\n): string | string[] {\n if (!geojson) return \"\";\n\n // Handle Feature wrapper\n if (geojson.type === \"Feature\") {\n return geoJSONToPolylineStr(geojson.geometry, precision);\n }\n\n // Handle FeatureCollection\n if (geojson.type === \"FeatureCollection\") {\n const results: string[] = [];\n for (const feature of geojson.features) {\n const res = geoJSONToPolylineStr(feature.geometry, precision);\n if (Array.isArray(res)) results.push(...res);\n else if (res) results.push(res);\n }\n return results;\n }\n\n // Handle LineString\n if (geojson.type === \"LineString\") {\n return encodePolyline(geojson.coordinates as [number, number][], precision);\n }\n\n // Handle MultiLineString\n if (geojson.type === \"MultiLineString\") {\n return geojson.coordinates.map((lineCoords) =>\n encodePolyline(lineCoords as [number, number][], precision),\n );\n }\n\n return \"\";\n}\n\nexport interface PolylineBatchOptions {\n /** Coordinate precision (default: 5) */\n precision?: number;\n /** Whether to unescape `\\\\` backslashes (default: true) */\n unescape?: boolean;\n /** Custom delimiter string or RegExp separating individual polylines (default: newline) */\n delimiter?: string | RegExp;\n /** Whether to merge lines into a single MultiLineString feature (default: false) */\n asMultiLine?: boolean;\n /** Optional base properties to assign to output features */\n baseProperties?: GeoJsonProperties;\n}\n\n/**\n * Parses and batch-decodes multiple polylines from a text block or file content.\n *\n * @param input - Multiline string or delimited text containing encoded polylines.\n * @param options - Decoding and parsing configuration.\n * @returns A FeatureCollection containing LineString (or MultiLineString) features.\n */\nexport function batchDecodePolylines(\n input: string,\n options: PolylineBatchOptions = {},\n): FeatureCollection<LineString | MultiLineString> {\n if (!input || typeof input !== \"string\") {\n return { type: \"FeatureCollection\", features: [] };\n }\n\n const {\n precision = 5,\n unescape = true,\n delimiter = /\\r?\\n/,\n asMultiLine = false,\n baseProperties = {},\n } = options;\n\n const rawChunks = input.split(delimiter);\n const validLineCoords: [number, number][][] = [];\n const features: Feature<LineString>[] = [];\n\n for (let i = 0; i < rawChunks.length; i++) {\n const raw = rawChunks[i].trim();\n if (!raw) continue;\n // Strip optional surrounding quotes (e.g. from CSV or JSON extract)\n const cleaned = raw.replace(/^[\"']|[\"']$/g, \"\").trim();\n if (!cleaned) continue;\n\n const decodeResult = decodePolylineDetailed(cleaned, precision, unescape);\n if (decodeResult.complete && decodeResult.coordinates.length >= 2) {\n const coords = decodeResult.coordinates;\n validLineCoords.push(coords);\n features.push({\n type: \"Feature\",\n properties: {\n ...baseProperties,\n line_index: features.length + 1,\n point_count: coords.length,\n },\n geometry: {\n type: \"LineString\",\n coordinates: coords,\n },\n });\n }\n }\n\n if (asMultiLine && validLineCoords.length > 0) {\n const multiFeature: Feature<MultiLineString> = {\n type: \"Feature\",\n properties: {\n ...baseProperties,\n line_count: validLineCoords.length,\n point_count: validLineCoords.reduce((acc, c) => acc + c.length, 0),\n },\n geometry: {\n type: \"MultiLineString\",\n coordinates: validLineCoords,\n },\n };\n return {\n type: \"FeatureCollection\",\n features: [multiFeature],\n };\n }\n\n return {\n type: \"FeatureCollection\",\n features,\n };\n}\n","/**\n * Resolves runtime environment variables shared by the external-service clients\n * (geocoding, routing). Allowlisted build-time vars (`__GEOLIBRE_BUILD_ENV__`,\n * injected by `vite.config.ts`) are overlaid\n * with project-supplied runtime vars (`window.__GEOLIBRE_RUNTIME_ENV__`, set\n * from project preferences) so a self-hosted endpoint can be configured without\n * a rebuild. Carries no React/MapLibre dependency so callers stay unit-testable.\n */\n\n// Injected by `vite.config.ts`'s `define` from an explicit allowlist\n// (BUILD_ENV_KEYS), with credential-bearing names withheld from redistributable\n// builds such as the Jupyter wheel.\n//\n// This deliberately does NOT read `import.meta.env`. That read is a whole-object\n// one — nothing here names a static key — so Vite could not replace it per-key\n// and instead inlined the entire env record into every chunk importing this\n// module. Combined with the bare-to-prefixed shell bridge in `vite.config.ts`,\n// every chunk importing this module would carry whatever the build machine had\n// set. Keep this indirection: reverting to `import.meta.env` silently restores\n// that behaviour, and no test would catch it.\ndeclare const __GEOLIBRE_BUILD_ENV__: Record<string, string> | undefined;\n\nconst buildEnv: Record<string, string | undefined> =\n typeof __GEOLIBRE_BUILD_ENV__ === \"undefined\" ? {} : __GEOLIBRE_BUILD_ENV__;\n\n/**\n * The allowlisted build-time environment, with no runtime overlay.\n *\n * For settings that must be fixed by the build and NOT overridable by a\n * `.geolibre.json` a user opened — the auth gate, the deployment profile, the\n * embed origin allowlist. Routing those through {@link getRuntimeEnvironment}\n * would let project-supplied `__GEOLIBRE_RUNTIME_ENV__` values relax them.\n *\n * Callers should prefer this over reading `import.meta.env` directly: a\n * whole-object read defeats Vite's per-key replacement and inlines every\n * `VITE_` var on the build machine into the chunk.\n *\n * @returns The build-time environment record.\n */\nexport function getBuildEnvironment(): Record<string, string | undefined> {\n return buildEnv;\n}\n\n/**\n * Merges build-time env with project runtime env (the latter wins). Falls back\n * to build-time env alone outside a browser (e.g. in tests).\n *\n * @returns The resolved environment variables.\n */\nexport function getRuntimeEnvironment(): Record<string, string | undefined> {\n if (typeof window === \"undefined\") return buildEnv;\n\n // __GEOLIBRE_RUNTIME_ENV__ is declared globally in ./types.\n return {\n ...buildEnv,\n ...(window.__GEOLIBRE_RUNTIME_ENV__ ?? {}),\n };\n}\n\n/**\n * Resolves a local DuckDB spatial extension path from the runtime environment.\n *\n * When `VITE_DUCKDB_SPATIAL_EXTENSION_PATH` is set, DuckDB consumers (the\n * desktop app's own loader and the Add Vector panel's maplibre-gl-vector\n * control) load the spatial extension from this path with `LOAD '<path>'`\n * instead of installing it from the remote repository, which hangs in\n * sandboxed or firewalled environments. Lives in `@geolibre/core` so every\n * consumer shares one implementation.\n *\n * @param env - Environment record (defaults to the runtime environment);\n * injectable for testing.\n * @returns The trimmed extension path, or undefined when unset.\n */\nexport function getSpatialExtensionPath(\n env?: Record<string, string | undefined>,\n): string | undefined {\n const runtimeEnv = env ?? getRuntimeEnvironment();\n const trimmed = runtimeEnv.VITE_DUCKDB_SPATIAL_EXTENSION_PATH?.trim();\n return trimmed || undefined;\n}\n\n/**\n * Resolves the Protomaps API key from the runtime environment.\n *\n * Protomaps' hosted styles require an API key embedded in the style URL. The\n * key is supplied via `VITE_PROTOMAPS_API_KEY` (baked in at build time for the\n * web demo; see the deploy workflow). When unset, the Protomaps basemaps are\n * unavailable and should be hidden from the UI.\n *\n * @param env - Environment record (defaults to the runtime environment);\n * injectable for testing.\n * @returns The trimmed API key, or undefined when unset.\n */\nexport function getProtomapsApiKey(env?: Record<string, string | undefined>): string | undefined {\n const runtimeEnv = env ?? getRuntimeEnvironment();\n const trimmed = runtimeEnv.VITE_PROTOMAPS_API_KEY?.trim();\n return trimmed || undefined;\n}\n\n/**\n * Resolves the Google Maps API key from the runtime environment.\n *\n * GeoLibre's browser-facing builds normally use `VITE_GOOGLE_MAPS_API_KEY`.\n * The bare `GOOGLE_MAPS_API_KEY` fallback is reached two ways: (1) the desktop\n * Vite config copies it to `VITE_GOOGLE_MAPS_API_KEY` at build time for local\n * shell testing (`vite.config.ts`'s `envPrefix` does not include the bare\n * name), and (2) a project's own runtime environment variables\n * (`window.__GEOLIBRE_RUNTIME_ENV__`), which are not subject to Vite's\n * envPrefix allowlist at all.\n *\n * @param env - Environment record (defaults to the runtime environment);\n * injectable for testing.\n * @returns The trimmed API key, or undefined when unset.\n */\nexport function getGoogleMapsApiKey(env?: Record<string, string | undefined>): string | undefined {\n const runtimeEnv = env ?? getRuntimeEnvironment();\n const trimmed =\n runtimeEnv.VITE_GOOGLE_MAPS_API_KEY?.trim() || runtimeEnv.GOOGLE_MAPS_API_KEY?.trim();\n return trimmed || undefined;\n}\n\n/**\n * Resolves the Mapbox access token from the runtime environment.\n *\n * The basemap control's Mapbox styles authenticate with the user's own token.\n * It is supplied via `VITE_MAPBOX_ACCESS_TOKEN` (baked in at build time from the\n * bare `MAPBOX_TOKEN` env var, which `vite.config.ts` copies into the prefixed\n * name — the spelling Mapbox's own tooling uses) or set at runtime through\n * Settings → Environment variables (`window.__GEOLIBRE_RUNTIME_ENV__`, which\n * bypasses Vite's envPrefix allowlist, so a bare `MAPBOX_TOKEN` entry works\n * there too). When unset, the Mapbox basemaps prompt for a token in the basemap\n * panel's API keys view instead.\n *\n * Note the precedence, shared with {@link getGoogleMapsApiKey} and\n * {@link getCesiumIonToken}: the prefixed name always wins over the bare one,\n * and `getRuntimeEnvironment` merges build-time and runtime vars into one\n * record. So a build that baked in `VITE_MAPBOX_ACCESS_TOKEN` is overridden at\n * runtime by a Settings entry under that *same* prefixed name; a bare\n * `MAPBOX_TOKEN` entry is a fallback for when nothing was baked in, not a way\n * to override a baked token.\n *\n * @param env - Environment record (defaults to the runtime environment);\n * injectable for testing.\n * @returns The trimmed token, or undefined when unset.\n */\nexport function getMapboxAccessToken(env?: Record<string, string | undefined>): string | undefined {\n const runtimeEnv = env ?? getRuntimeEnvironment();\n const trimmed = runtimeEnv.VITE_MAPBOX_ACCESS_TOKEN?.trim() || runtimeEnv.MAPBOX_TOKEN?.trim();\n return trimmed || undefined;\n}\n\n/**\n * Resolves the ArcGIS API key from the runtime environment.\n *\n * The ArcGIS renderer draws the translated project basemap and non-Esri layers\n * without a key; Esri's basemap styles and location services require one\n * (an ArcGIS Location Platform or ArcGIS Online API key credential). It is\n * supplied via `VITE_ARCGIS_API_KEY` (baked in at build time from the bare\n * `ARCGIS_API_KEY` env var, which `vite.config.ts` copies into the prefixed\n * name) or set at runtime through Settings → Environment variables\n * (`window.__GEOLIBRE_RUNTIME_ENV__`, so a bare `ARCGIS_API_KEY` entry works\n * there too). Same precedence as {@link getMapboxAccessToken}.\n *\n * @param env - Environment record (defaults to the runtime environment);\n * injectable for testing.\n * @returns The trimmed key, or undefined when unset.\n */\nexport function getArcgisApiKey(env?: Record<string, string | undefined>): string | undefined {\n const runtimeEnv = env ?? getRuntimeEnvironment();\n const trimmed = runtimeEnv.VITE_ARCGIS_API_KEY?.trim() || runtimeEnv.ARCGIS_API_KEY?.trim();\n return trimmed || undefined;\n}\n\n/**\n * Resolves the Cesium Ion access token from the runtime environment.\n *\n * Cesium World Terrain and Ion World Imagery need a Cesium Ion token. It is\n * supplied via `VITE_CESIUM_TOKEN` (baked in at build time from the bare\n * `CESIUM_TOKEN` env var, which `vite.config.ts` copies into the prefixed name)\n * or set at runtime through Settings → Environment variables\n * (`window.__GEOLIBRE_RUNTIME_ENV__`, which bypasses Vite's envPrefix allowlist,\n * so a bare `CESIUM_TOKEN` entry works there too).\n *\n * The 3D-globe view itself does **not** need one: it draws the project basemap\n * as its base imagery whenever that basemap has a raster form (see\n * {@link basemapToCesiumImagery}), and falls back to Ion World Imagery — or, with\n * no token, to OpenStreetMap — for the ones that do not. So an absent token\n * costs terrain and the quality of that fallback, not the view.\n *\n * @param env - Environment record (defaults to the runtime environment);\n * injectable for testing.\n * @returns The trimmed token, or undefined when unset.\n */\nexport function getCesiumIonToken(env?: Record<string, string | undefined>): string | undefined {\n const runtimeEnv = env ?? getRuntimeEnvironment();\n const trimmed = runtimeEnv.VITE_CESIUM_TOKEN?.trim() || runtimeEnv.CESIUM_TOKEN?.trim();\n return trimmed || undefined;\n}\n\n/**\n * Builds a full Protomaps v5 style URL for a flavor, injecting the API key.\n *\n * @param flavor - The Protomaps flavor name (e.g. `light`, `dark`, `white`,\n * `grayscale`, `black`).\n * @param env - Environment record (defaults to the runtime environment);\n * injectable for testing.\n * @returns The resolved style URL, or undefined when no API key is configured.\n */\nexport function getProtomapsStyleUrl(\n flavor: string,\n env?: Record<string, string | undefined>,\n): string | undefined {\n const key = getProtomapsApiKey(env);\n if (!key) return undefined;\n return `https://api.protomaps.com/styles/v5/${encodeURIComponent(\n flavor,\n )}/en.json?key=${encodeURIComponent(key)}`;\n}\n","import type { Feature } from \"geojson\";\n\n/**\n * How a freshly matched feature set combines with the existing selection,\n * mirroring QGIS's four selection behaviors: replace the selection, add to\n * it, remove from it, or keep only features in both.\n */\nexport type SelectionMode = \"new\" | \"add\" | \"remove\" | \"intersect\";\n\n/** Selection modes in display order. */\nexport const SELECTION_MODES: readonly SelectionMode[] = [\"new\", \"add\", \"remove\", \"intersect\"];\n\n/**\n * The id under which a feature participates in the selection model. Mirrors\n * the convention shared by the attribute table and the map highlight\n * (`String(feature.id ?? index)`): the feature's own `id` when present,\n * otherwise its index in the layer's feature array.\n */\nexport function featureSelectionId(feature: Feature, index: number): string {\n return String(feature.id ?? index);\n}\n\n/**\n * Combines the current selection with a freshly matched id set according to\n * `mode`. The result is deduplicated and order-stable: \"new\" follows the\n * matched order, \"add\" appends unseen matches after the current ids, and\n * \"remove\"/\"intersect\" preserve the current selection's order.\n */\nexport function applySelectionMode(\n current: readonly string[],\n matched: readonly string[],\n mode: SelectionMode,\n): string[] {\n const matchedSet = new Set(matched);\n switch (mode) {\n case \"new\":\n return Array.from(matchedSet);\n case \"add\": {\n const merged = new Set(current);\n for (const id of matched) merged.add(id);\n return Array.from(merged);\n }\n case \"remove\":\n return Array.from(new Set(current.filter((id) => !matchedSet.has(id))));\n case \"intersect\":\n return Array.from(new Set(current.filter((id) => matchedSet.has(id))));\n }\n}\n\n/**\n * The complement of `current` within `allIds` (QGIS \"Invert selection\"):\n * every feature id not currently selected, in layer order. With an empty\n * selection this selects everything.\n */\nexport function invertSelection(allIds: readonly string[], current: readonly string[]): string[] {\n const selected = new Set(current);\n return allIds.filter((id) => !selected.has(id));\n}\n","import { Color, createExpression } from \"@maplibre/maplibre-gl-style-spec\";\nimport type { StyleExpression } from \"@maplibre/maplibre-gl-style-spec\";\nimport type { Feature } from \"geojson\";\nimport { featureSelectionId } from \"./selection\";\n\n/**\n * Shared Expression Builder logic (GH #1306): a curated MapLibre expression\n * function reference, real parse/type validation via the MapLibre style spec,\n * live preview evaluation against a sample feature, `@`-style variable\n * substitution, and attribute field type inference. The UI dialog that\n * consumes this lives in the desktop app; keeping the logic here lets every\n * expression entry point (rule filters, expression style mode, labels, and\n * later the field calculator and atlas) share one implementation.\n */\n\n/** One documented function/operator the builder can insert. */\nexport interface ExpressionFunctionDoc {\n /** The MapLibre operator name, e.g. `get` or `==`. */\n name: string;\n /** The snippet inserted into the expression text (valid JSON). */\n snippet: string;\n /**\n * i18n key suffix (under `style.expressionBuilder.functions`) for the\n * one-line description. Kept as a suffix so this package stays free of\n * i18next; the app resolves it with `t()`.\n */\n docKey: string;\n}\n\n/** A group of related functions shown as one section of the reference tree. */\nexport interface ExpressionFunctionCategory {\n /** Stable key, also the i18n suffix under `style.expressionBuilder.categories`. */\n key: string;\n functions: ExpressionFunctionDoc[];\n}\n\nconst fn = (name: string, snippet: string, docKey: string): ExpressionFunctionDoc => ({\n name,\n snippet,\n docKey,\n});\n\n/**\n * The curated function reference, grouped by category. Snippets are valid,\n * standalone-compilable MapLibre expressions (enforced by tests) so inserting\n * one into an empty editor always yields a working starting point.\n */\nexport const EXPRESSION_FUNCTION_CATEGORIES: ExpressionFunctionCategory[] = [\n {\n key: \"data\",\n functions: [\n fn(\"get\", '[\"get\", \"field\"]', \"get\"),\n fn(\"has\", '[\"has\", \"field\"]', \"has\"),\n fn(\"id\", '[\"id\"]', \"id\"),\n fn(\"properties\", '[\"properties\"]', \"properties\"),\n fn(\"geometry-type\", '[\"geometry-type\"]', \"geometryType\"),\n fn(\"at\", '[\"at\", 0, [\"literal\", [1, 2, 3]]]', \"at\"),\n fn(\"in\", '[\"in\", \"value\", [\"get\", \"field\"]]', \"in\"),\n fn(\"index-of\", '[\"index-of\", \"value\", [\"get\", \"field\"]]', \"indexOf\"),\n fn(\"length\", '[\"length\", [\"get\", \"field\"]]', \"length\"),\n fn(\"literal\", '[\"literal\", [\"a\", \"b\"]]', \"literal\"),\n fn(\"typeof\", '[\"typeof\", [\"get\", \"field\"]]', \"typeofOp\"),\n ],\n },\n {\n key: \"decision\",\n functions: [\n fn(\"case\", '[\"case\", [\"==\", [\"get\", \"field\"], \"value\"], \"#ff0000\", \"#cccccc\"]', \"caseOp\"),\n fn(\n \"match\",\n '[\"match\", [\"get\", \"field\"], \"a\", \"#2563eb\", \"b\", \"#16a34a\", \"#94a3b8\"]',\n \"match\",\n ),\n fn(\"coalesce\", '[\"coalesce\", [\"get\", \"field\"], \"fallback\"]', \"coalesce\"),\n fn(\"==\", '[\"==\", [\"get\", \"field\"], \"value\"]', \"eq\"),\n fn(\"!=\", '[\"!=\", [\"get\", \"field\"], \"value\"]', \"neq\"),\n fn(\">\", '[\">\", [\"get\", \"field\"], 0]', \"gt\"),\n fn(\">=\", '[\">=\", [\"get\", \"field\"], 0]', \"gte\"),\n fn(\"<\", '[\"<\", [\"get\", \"field\"], 0]', \"lt\"),\n fn(\"<=\", '[\"<=\", [\"get\", \"field\"], 0]', \"lte\"),\n fn(\"all\", '[\"all\", [\">\", [\"get\", \"field\"], 0], [\"<\", [\"get\", \"field\"], 100]]', \"all\"),\n fn(\"any\", '[\"any\", [\"==\", [\"get\", \"field\"], \"a\"], [\"==\", [\"get\", \"field\"], \"b\"]]', \"any\"),\n fn(\"!\", '[\"!\", [\"has\", \"field\"]]', \"not\"),\n ],\n },\n {\n key: \"math\",\n functions: [\n fn(\"+\", '[\"+\", [\"get\", \"field\"], 1]', \"add\"),\n fn(\"-\", '[\"-\", [\"get\", \"field\"], 1]', \"subtract\"),\n fn(\"*\", '[\"*\", [\"get\", \"field\"], 2]', \"multiply\"),\n fn(\"/\", '[\"/\", [\"get\", \"field\"], 2]', \"divide\"),\n fn(\"%\", '[\"%\", [\"get\", \"field\"], 2]', \"modulo\"),\n fn(\"^\", '[\"^\", [\"get\", \"field\"], 2]', \"power\"),\n fn(\"abs\", '[\"abs\", [\"get\", \"field\"]]', \"abs\"),\n fn(\"ceil\", '[\"ceil\", [\"get\", \"field\"]]', \"ceil\"),\n fn(\"floor\", '[\"floor\", [\"get\", \"field\"]]', \"floor\"),\n fn(\"round\", '[\"round\", [\"get\", \"field\"]]', \"round\"),\n fn(\"sqrt\", '[\"sqrt\", [\"get\", \"field\"]]', \"sqrt\"),\n fn(\"ln\", '[\"ln\", [\"get\", \"field\"]]', \"ln\"),\n fn(\"log10\", '[\"log10\", [\"get\", \"field\"]]', \"log10\"),\n fn(\"min\", '[\"min\", [\"get\", \"field\"], 0]', \"min\"),\n fn(\"max\", '[\"max\", [\"get\", \"field\"], 0]', \"max\"),\n ],\n },\n {\n key: \"string\",\n functions: [\n fn(\"concat\", '[\"concat\", [\"get\", \"field\"], \" suffix\"]', \"concat\"),\n fn(\"upcase\", '[\"upcase\", [\"get\", \"field\"]]', \"upcase\"),\n fn(\"downcase\", '[\"downcase\", [\"get\", \"field\"]]', \"downcase\"),\n fn(\"slice\", '[\"slice\", [\"get\", \"field\"], 0, 3]', \"slice\"),\n fn(\n \"number-format\",\n '[\"number-format\", [\"get\", \"field\"], {\"locale\": \"en-US\", \"max-fraction-digits\": 2}]',\n \"numberFormat\",\n ),\n ],\n },\n {\n key: \"conversion\",\n functions: [\n fn(\"to-number\", '[\"to-number\", [\"get\", \"field\"]]', \"toNumber\"),\n fn(\"to-string\", '[\"to-string\", [\"get\", \"field\"]]', \"toString\"),\n fn(\"to-boolean\", '[\"to-boolean\", [\"get\", \"field\"]]', \"toBoolean\"),\n fn(\"to-color\", '[\"to-color\", \"#2563eb\"]', \"toColor\"),\n ],\n },\n {\n key: \"camera\",\n functions: [\n fn(\"zoom\", '[\"zoom\"]', \"zoom\"),\n fn(\n \"interpolate\",\n '[\"interpolate\", [\"linear\"], [\"zoom\"], 5, [\"to-color\", \"#2563eb\"], 12, [\"to-color\", \"#dc2626\"]]',\n \"interpolate\",\n ),\n fn(\"step\", '[\"step\", [\"zoom\"], \"#2563eb\", 10, \"#dc2626\"]', \"step\"),\n fn(\"rgb\", '[\"rgb\", 37, 99, 235]', \"rgb\"),\n fn(\"rgba\", '[\"rgba\", 37, 99, 235, 0.5]', \"rgba\"),\n ],\n },\n];\n\n/**\n * Strips commas that directly precede a closing bracket/brace outside of\n * strings, so user-typed expressions with trailing commas still parse.\n * Shared by {@link parseJsonExpression} (vector-color) and the builder.\n */\nexport function removeTrailingJsonCommas(value: string): string {\n let result = \"\";\n let inString = false;\n let escaped = false;\n\n for (let index = 0; index < value.length; index += 1) {\n const char = value[index];\n\n if (inString) {\n result += char;\n if (escaped) {\n escaped = false;\n } else if (char === \"\\\\\") {\n escaped = true;\n } else if (char === '\"') {\n inString = false;\n }\n continue;\n }\n\n if (char === '\"') {\n inString = true;\n result += char;\n continue;\n }\n\n if (char === \",\") {\n const nextSignificant = value.slice(index + 1).match(/\\S/)?.[0];\n if (nextSignificant === \"]\" || nextSignificant === \"}\") continue;\n }\n\n result += char;\n }\n\n return result;\n}\n\n/** Result types the builder can require of a whole expression. */\nexport type ExpressionExpectedType = \"boolean\" | \"color\" | \"string\" | \"number\";\n\n/** Options for {@link validateMapExpression}. */\nexport interface ValidateMapExpressionOptions {\n /**\n * Variables substituted before compiling, so a numeric `@token` used in a\n * numeric slot type-checks against its value instead of the raw string.\n */\n variables?: ExpressionVariable[];\n /**\n * When set, the style spec type-checks the expression's result against\n * this type (e.g. a filter must produce a boolean), rejecting expressions\n * that compile but cannot fit the destination.\n */\n expectedType?: ExpressionExpectedType;\n}\n\n/**\n * The `rootKey` `createExpression` (style-spec v26+) requires as its second\n * argument — a location label it only surfaces when attaching a runtime\n * warning to `console.warn` during evaluation. These expressions come from the\n * Expression Builder dialog, not a style JSON layer, so a generic label is the\n * honest value; it never appears in the parsing errors we report.\n */\nconst EXPRESSION_ROOT_KEY = \"expression\";\n\n/**\n * Builds the minimal property spec that makes `createExpression` enforce a\n * result type while still allowing zoom- and feature-dependent input.\n *\n * This mirrors the (unexported) `StylePropertySpecification` shape that\n * `@maplibre/maplibre-gl-style-spec` expects — the cast below means the\n * compiler cannot catch a change to that contract, so whenever the\n * style-spec package is bumped (including Dependabot PRs) re-check that\n * expected-type enforcement still works, and that the property spec is still\n * `createExpression`'s third parameter (v26 inserted a `rootKey` at index 1,\n * shifting it from `[1]` to `[2]`). The \"enforces an expected result type\"\n * test in `tests/expressions.test.ts` fails if this shape stops being honored.\n */\nfunction propertySpecFor(expectedType: ExpressionExpectedType) {\n return {\n type: expectedType,\n \"property-type\": \"data-driven\",\n expression: { parameters: [\"zoom\", \"feature\"] },\n } as unknown as NonNullable<Parameters<typeof createExpression>[2]>;\n}\n\n/** Outcome of validating an expression source string. */\nexport interface ExpressionValidation {\n ok: boolean;\n /**\n * Failure class, for callers that want to localize the shape errors:\n * `not-json` / `not-array` / `not-operator` are structural, `compile` means\n * the style spec rejected it (with messages in {@link errors}).\n */\n code?: \"not-json\" | \"not-array\" | \"not-operator\" | \"compile\";\n /** Human-readable problems; empty when `ok`. */\n errors: string[];\n /** The parsed JSON array when the source at least parsed as JSON. */\n parsed?: unknown[];\n}\n\n/**\n * Validates a user-entered MapLibre expression: tolerant JSON parse, shape\n * checks, then a real compile through the MapLibre style spec so operator\n * misuse (wrong arity, unknown operator, type mismatch) is caught in the\n * dialog instead of silently failing on the map. Variables are substituted\n * before compiling (so tokens type-check as their values), and\n * `expectedType` additionally enforces the destination's result type. The\n * returned `parsed` array is the raw, unsubstituted parse. An empty/blank\n * source is valid (surfaces treat it as \"no expression\").\n */\nexport function validateMapExpression(\n source: string,\n options: ValidateMapExpressionOptions = {},\n): ExpressionValidation {\n return compileMapExpression(source, options).validation;\n}\n\n/**\n * The single parse + substitute + compile pass shared by validation and\n * preview evaluation, so a preview never compiles the same source twice.\n * `expression` is set only when the validation succeeded on a non-empty\n * source.\n */\nfunction compileMapExpression(\n source: string,\n options: ValidateMapExpressionOptions,\n): { validation: ExpressionValidation; expression?: StyleExpression } {\n const trimmed = source.trim();\n if (!trimmed) return { validation: { ok: true, errors: [] } };\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(removeTrailingJsonCommas(trimmed));\n } catch (error) {\n return {\n validation: {\n ok: false,\n code: \"not-json\",\n errors: [error instanceof Error ? error.message : \"Invalid JSON\"],\n },\n };\n }\n if (!Array.isArray(parsed)) {\n return { validation: { ok: false, code: \"not-array\", errors: [] } };\n }\n if (typeof parsed[0] !== \"string\") {\n return {\n validation: { ok: false, code: \"not-operator\", errors: [], parsed },\n };\n }\n\n const substituted = substituteExpressionVariables(parsed, options.variables ?? []) as unknown[];\n const compiled = createExpression(\n substituted,\n EXPRESSION_ROOT_KEY,\n options.expectedType ? propertySpecFor(options.expectedType) : undefined,\n );\n if (compiled.result === \"error\") {\n return {\n validation: {\n ok: false,\n code: \"compile\",\n errors: compiled.value.map((issue) =>\n issue.key ? `${issue.key}: ${issue.message}` : issue.message,\n ),\n parsed,\n },\n };\n }\n return { validation: { ok: true, errors: [], parsed }, expression: compiled.value };\n}\n\n/** A named `@` variable with its current value. */\nexport interface ExpressionVariable {\n /** Token including the `@` prefix, e.g. `@project_name`. */\n token: string;\n value: string | number | boolean;\n}\n\n/**\n * Replaces every string node that exactly matches a known `@token` with that\n * variable's value, returning a new structure (the input is not mutated).\n * Unknown `@` strings are left untouched so field values that happen to start\n * with `@` never get mangled.\n */\nexport function substituteExpressionVariables(\n node: unknown,\n variables: ExpressionVariable[],\n): unknown {\n if (typeof node === \"string\" && node.startsWith(\"@\")) {\n const match = variables.find((variable) => variable.token === node);\n return match ? match.value : node;\n }\n if (Array.isArray(node)) {\n return node.map((entry) => substituteExpressionVariables(entry, variables));\n }\n if (node && typeof node === \"object\") {\n return Object.fromEntries(\n Object.entries(node as Record<string, unknown>).map(([key, value]) => [\n key,\n substituteExpressionVariables(value, variables),\n ]),\n );\n }\n return node;\n}\n\n/** Result of a live preview evaluation. */\nexport interface ExpressionPreview {\n kind: \"empty\" | \"error\" | \"value\";\n /** Problems when `kind` is \"error\" (parse or runtime). */\n errors?: string[];\n /** The evaluated value when `kind` is \"value\". */\n value?: unknown;\n}\n\n/** Options for {@link evaluateMapExpression}. */\nexport interface EvaluateMapExpressionOptions {\n /** The sample feature the expression is evaluated against. */\n feature?: Feature | null;\n /** Map zoom used for `[\"zoom\"]`; defaults to 0. */\n zoom?: number;\n /** Variables substituted before compiling. */\n variables?: ExpressionVariable[];\n /** Enforce the destination's result type, as in validation. */\n expectedType?: ExpressionExpectedType;\n}\n\n/**\n * Evaluates an expression source against a sample feature for the builder's\n * live preview. Variables are substituted first, then the expression is\n * compiled with the MapLibre style spec and evaluated with the given zoom.\n * Runtime failures (e.g. `to-color` on garbage) come back as errors instead\n * of throwing.\n */\nexport function evaluateMapExpression(\n source: string,\n options: EvaluateMapExpressionOptions = {},\n): ExpressionPreview {\n const trimmed = source.trim();\n if (!trimmed) return { kind: \"empty\" };\n\n const { validation, expression } = compileMapExpression(trimmed, {\n variables: options.variables,\n expectedType: options.expectedType,\n });\n if (!validation.ok || !expression) {\n return { kind: \"error\", errors: validation.errors };\n }\n\n const feature = options.feature ?? null;\n const geometryType = feature?.geometry?.type ?? \"Unknown\";\n try {\n // evaluateWithoutErrorHandling: the plain evaluate() swallows runtime\n // failures into console.warn, which (a) hides the error from the preview\n // and (b) feeds the app's diagnostics console interceptor from inside a\n // React render, which can re-render the panel and re-evaluate in an\n // endless loop. Throwing lets the catch below surface the message.\n const value = expression.evaluateWithoutErrorHandling({ zoom: options.zoom ?? 0 }, {\n type: geometryType,\n properties: feature?.properties ?? {},\n ...(feature?.id !== undefined ? { id: feature.id } : {}),\n geometry: feature?.geometry,\n } as never);\n return { kind: \"value\", value };\n } catch (error) {\n return {\n kind: \"error\",\n errors: [error instanceof Error ? error.message : String(error)],\n };\n }\n}\n\n/**\n * A compiled per-feature evaluator: the expression is parsed and compiled\n * once, then `evaluate` runs it against each feature. `evaluate` is absent\n * when compilation failed (see `errors`); it throws on runtime evaluation\n * failures (same rationale as {@link evaluateMapExpression}: throwing keeps\n * errors out of console.warn) so callers decide per-feature error handling.\n */\nexport interface CompiledFeatureExpression {\n ok: boolean;\n /** Parse/compile problems when `ok` is false. */\n errors: string[];\n /**\n * Runs the expression against one feature. `zoom` overrides the zoom the\n * expression was compiled with, for callers that re-evaluate a\n * zoom-dependent expression as the camera moves.\n */\n evaluate?: (feature: Feature, zoom?: number) => unknown;\n}\n\n/**\n * Compiles an expression source once for bulk per-feature evaluation (virtual\n * fields, and any other caller that runs one expression over a whole layer).\n * Unlike {@link evaluateMapExpression} the compile cost is paid once, not per\n * feature. An empty source is reported as a failure with no errors — there is\n * nothing to evaluate.\n */\nexport function compileFeatureExpression(\n source: string,\n options: {\n zoom?: number;\n variables?: ExpressionVariable[];\n expectedType?: ExpressionExpectedType;\n } = {},\n): CompiledFeatureExpression {\n const trimmed = source.trim();\n if (!trimmed) return { ok: false, errors: [] };\n\n const { validation, expression } = compileMapExpression(trimmed, {\n variables: options.variables,\n expectedType: options.expectedType,\n });\n if (!validation.ok || !expression) {\n return { ok: false, errors: validation.errors };\n }\n const compiledZoom = options.zoom ?? 0;\n return {\n ok: true,\n errors: [],\n evaluate: (feature, zoom = compiledZoom) =>\n expression.evaluateWithoutErrorHandling({ zoom }, {\n type: feature.geometry?.type ?? \"Unknown\",\n properties: feature.properties ?? {},\n ...(feature.id !== undefined ? { id: feature.id } : {}),\n geometry: feature.geometry,\n } as never),\n };\n}\n\n/** Result of matching a feature array against a boolean expression. */\nexport interface FeatureExpressionMatches {\n /** False when the expression failed to parse or compile. */\n ok: boolean;\n /** Parse/compile problems when `ok` is false. */\n errors: string[];\n /** Selection ids ({@link featureSelectionId}) of the matched features. */\n ids: string[];\n /**\n * Features whose evaluation threw at runtime (e.g. a boolean assertion on\n * a string field). Counted as non-matches so one bad feature cannot abort\n * the whole selection, but surfaced so the UI can warn.\n */\n errorCount: number;\n}\n\n/**\n * Evaluates a boolean expression against every feature and returns the\n * selection ids of the matches (Select by Expression). Unlike\n * {@link evaluateMapExpression} this compiles the source once and reuses the\n * compiled expression across the whole array, so it stays fast on large\n * layers. An empty source is reported as `ok: false` with no errors — there\n * is nothing to select by.\n */\nexport function matchFeaturesByExpression(\n features: readonly Feature[],\n source: string,\n options: { zoom?: number; variables?: ExpressionVariable[] } = {},\n): FeatureExpressionMatches {\n const trimmed = source.trim();\n if (!trimmed) return { ok: false, errors: [], ids: [], errorCount: 0 };\n\n const { validation, expression } = compileMapExpression(trimmed, {\n variables: options.variables,\n expectedType: \"boolean\",\n });\n if (!validation.ok || !expression) {\n return { ok: false, errors: validation.errors, ids: [], errorCount: 0 };\n }\n\n const ids: string[] = [];\n let errorCount = 0;\n features.forEach((feature, index) => {\n try {\n // Same rationale as evaluateMapExpression: evaluateWithoutErrorHandling\n // so runtime failures throw instead of feeding console.warn.\n const value = expression.evaluateWithoutErrorHandling({ zoom: options.zoom ?? 0 }, {\n type: feature.geometry?.type ?? \"Unknown\",\n properties: feature.properties ?? {},\n ...(feature.id !== undefined ? { id: feature.id } : {}),\n geometry: feature.geometry,\n } as never);\n if (value === true) ids.push(featureSelectionId(feature, index));\n } catch {\n errorCount += 1;\n }\n });\n return { ok: true, errors: [], ids, errorCount };\n}\n\n/**\n * True when a preview value is a real style-spec `Color` instance (its\n * evaluated color representation: premultiplied r/g/b/a floats). Shared by\n * the preview formatter and the dialog's color-swatch rendering so the two\n * checks cannot drift apart. An instanceof check (not duck typing) so user\n * attribute data that happens to carry numeric r/g/b/a fields (e.g. via\n * `[\"properties\"]`) is never misclassified as a color.\n */\nexport function isStyleSpecColor(\n value: unknown,\n): value is { r: number; g: number; b: number; a: number } {\n return value instanceof Color;\n}\n\n/**\n * Renders a preview value as a short display string: style-spec Color\n * instances become rgba() strings, plain values render as JSON, and\n * `null`/`undefined` render as \"null\".\n */\nexport function formatExpressionPreviewValue(value: unknown): string {\n if (value === null || value === undefined) return \"null\";\n if (isStyleSpecColor(value)) {\n const color = value;\n // Style-spec colors are premultiplied floats in [0, 1]; undo the\n // premultiplication so the displayed rgba matches the authored color.\n const alpha = color.a;\n const channel = (component: number) => Math.round((alpha === 0 ? 0 : component / alpha) * 255);\n return `rgba(${channel(color.r)}, ${channel(color.g)}, ${channel(color.b)}, ${Number(alpha.toFixed(3))})`;\n }\n try {\n return JSON.stringify(value) ?? String(value);\n } catch {\n return String(value);\n }\n}\n\n/** Attribute field types the builder badges fields with. */\nexport type ExpressionFieldType = \"string\" | \"number\" | \"boolean\" | \"mixed\" | \"unknown\";\n\n/**\n * Infers a display type for each named field by scanning feature properties:\n * a single consistent primitive type wins, disagreements become \"mixed\", and\n * fields with no non-null values are \"unknown\".\n */\nexport function inferFieldTypes(\n features: ReadonlyArray<{ properties?: Record<string, unknown> | null }>,\n names: string[],\n): Record<string, ExpressionFieldType> {\n const result: Record<string, ExpressionFieldType> = {};\n for (const name of names) result[name] = \"unknown\";\n\n for (const feature of features) {\n const properties = feature.properties;\n if (!properties) continue;\n for (const name of names) {\n const value = properties[name];\n if (value === null || value === undefined) continue;\n const valueType = typeof value;\n const fieldType: ExpressionFieldType =\n valueType === \"string\" || valueType === \"number\" || valueType === \"boolean\"\n ? valueType\n : \"mixed\";\n if (result[name] === \"unknown\") result[name] = fieldType;\n else if (result[name] !== fieldType) result[name] = \"mixed\";\n }\n }\n return result;\n}\n","import {\n DEFAULT_LAYER_STYLE,\n STROKE_DASH_PRESETS,\n styleValue,\n type LayerStyle,\n type StrokeDashPreset,\n type VectorRule,\n} from \"./types\";\nimport { getVectorColorRamp } from \"./color-ramp\";\nimport { getActiveSemiMajorAxisMeters } from \"./ellipsoids\";\nimport { removeTrailingJsonCommas } from \"./expressions\";\n\n/**\n * A data-driven color value for a vector paint property: either a plain CSS\n * color string, or a MapLibre expression array (e.g. a categorized `match` or\n * graduated `interpolate`). Typed maplibre-agnostically so `@geolibre/core`\n * stays free of a maplibre-gl dependency; consumers cast to the concrete\n * `PropertyValueSpecification<string>` where the MapLibre types are in scope.\n */\nexport type VectorColorValue = string | unknown[];\n\n/** Whether a color value is a data-driven expression rather than a flat color. */\nexport function isVectorColorExpression(value: VectorColorValue): value is unknown[] {\n return Array.isArray(value);\n}\n\n/**\n * Whether a string is a 6-digit hex color (`#rrggbb`), the form the vector\n * renderer treats as valid. Exported so the symbology exporters can apply the\n * same validity check the live `match`/`case` expressions use rather than\n * re-deriving the regex.\n */\nexport function isHexColor(value: unknown): boolean {\n // Guard the type: `stop.color`/`rule.color` are typed `string`, but the data\n // can come from a hand-edited or imported .geolibre.json, so a missing/null\n // value must return false rather than throw on `.trim()`.\n return typeof value === \"string\" && /^#[0-9a-f]{6}$/i.test(value.trim());\n}\n\n/** A 3- or 6-digit hex color, as emitted by the simplestyle spec. */\nfunction isSimpleStyleColor(value: string): boolean {\n return /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.test(value.trim());\n}\n\n/**\n * simplestyle-spec color and numeric property names. Color keys carry CSS hex\n * colors; numeric keys carry plain numbers. See\n * https://github.com/mapbox/simplestyle-spec.\n */\nconst SIMPLE_STYLE_COLOR_KEYS = [\"fill\", \"stroke\", \"marker-color\"] as const;\nconst SIMPLE_STYLE_NUMBER_KEYS = [\n \"fill-opacity\",\n \"stroke-width\",\n \"stroke-opacity\",\n // Non-standard: alpha from a KML IconStyle color, wired into circle-opacity.\n \"marker-opacity\",\n] as const;\n\nfunction isSimpleStyleEnabled(style: LayerStyle): boolean {\n return styleValue(style, \"simpleStyleEnabled\") === true;\n}\n\n/**\n * Wrap a resolved color value so a per-feature simplestyle property takes\n * precedence when {@link LayerStyle.simpleStyleEnabled} is set. Returns the base\n * value unchanged when the feature lacks the property or the mode is off.\n *\n * @param style - The layer style.\n * @param property - The simplestyle property name (e.g. `fill`, `stroke`).\n * @param base - The flat color or expression to fall back to.\n * @returns A `coalesce` expression, or the base value when disabled.\n */\nfunction withSimpleStyleColor(\n style: LayerStyle,\n property: (typeof SIMPLE_STYLE_COLOR_KEYS)[number],\n base: VectorColorValue,\n): VectorColorValue {\n if (!isSimpleStyleEnabled(style)) return base;\n // A zoom-stepped base (per-rule scale ranges) must keep its step outermost:\n // MapLibre only allows [\"zoom\"] as the input of a top-level step/interpolate,\n // so the per-feature coalesce is applied inside each step output instead of\n // around the whole expression.\n return mapZoomStepOutputs(base, (value) => [\n \"coalesce\",\n [\"get\", property],\n value,\n ]) as VectorColorValue;\n}\n\n/**\n * Apply a per-feature transform to a value, descending into the outputs of a\n * top-level `[\"step\", [\"zoom\"], …]` expression (as produced by the rule-based\n * per-rule zoom compilation) so the zoom step stays outermost. MapLibre\n * rejects any expression where `[\"zoom\"]` is not the input of a top-level\n * `step`/`interpolate`, so wrappers (simplestyle coalesce, opacity folds)\n * must be pushed inside the step's outputs rather than wrapped around it.\n * Non-step values are transformed directly.\n */\nexport function mapZoomStepOutputs(\n value: unknown,\n transform: (output: unknown) => unknown,\n): unknown {\n if (\n Array.isArray(value) &&\n value[0] === \"step\" &&\n Array.isArray(value[1]) &&\n value[1][0] === \"zoom\"\n ) {\n const out: unknown[] = [\"step\", value[1], transform(value[2])];\n for (let index = 3; index < value.length; index += 2) {\n out.push(value[index], transform(value[index + 1]));\n }\n return out;\n }\n return transform(value);\n}\n\n/**\n * Resolve a numeric paint value, letting a per-feature simplestyle property\n * override the layer value when {@link LayerStyle.simpleStyleEnabled} is set.\n *\n * The lookup is wrapped in a `coalesce` rather than leaning on `to-number`'s\n * own fallback argument, because that fallback only fires when a value fails\n * to convert — and a *missing* property does not fail, it converts to 0. Since\n * simplestyle is enabled for the whole layer as soon as any feature carries any\n * one of these keys, features that carry only some of them (an ArcGIS KML\n * export writes `fill`/`fill-opacity` but no `marker-opacity`, for instance)\n * would otherwise be painted at opacity 0 or width 0 — present in the layer,\n * invisible on the map (#1552).\n *\n * @param style - The layer style.\n * @param property - The simplestyle property name (e.g. `stroke-width`).\n * @param base - The layer-level fallback value.\n * @returns A `to-number` expression, or `base` when disabled.\n */\nexport function simpleStyleNumberValue(\n style: LayerStyle,\n property: (typeof SIMPLE_STYLE_NUMBER_KEYS)[number],\n base: number,\n): number | unknown[] {\n if (!isSimpleStyleEnabled(style)) return base;\n return [\"to-number\", [\"coalesce\", [\"get\", property], base], base];\n}\n\n/** Whether `node` is `[\"get\", key]` for one of `keys`, returning the key. */\nfunction simpleStyleGet<K extends string>(node: unknown, keys: readonly K[]): K | null {\n if (!Array.isArray(node) || node.length !== 2 || node[0] !== \"get\") return null;\n return (keys as readonly unknown[]).includes(node[1]) ? (node[1] as K) : null;\n}\n\n/**\n * Read back the per-feature color lookup {@link withSimpleStyleColor} writes,\n * `[\"coalesce\", [\"get\", key], base]` with a simplestyle color key, so a style\n * importer recognizes exactly what the exporter emits.\n *\n * @param node - A paint value or a node inside one.\n * @returns The simplestyle key and the layer-level base, or `null` when the\n * node is not that lookup.\n */\nfunction readSimpleStyleColorLookup(\n node: unknown,\n): { property: (typeof SIMPLE_STYLE_COLOR_KEYS)[number]; base: unknown } | null {\n if (!Array.isArray(node) || node.length !== 3 || node[0] !== \"coalesce\") return null;\n const property = simpleStyleGet(node[1], SIMPLE_STYLE_COLOR_KEYS);\n return property ? { property, base: node[2] } : null;\n}\n\n/**\n * Read back the per-feature number lookup {@link simpleStyleNumberValue}\n * writes, `[\"to-number\", [\"coalesce\", [\"get\", key], base], base]` with a\n * simplestyle number key and a numeric base.\n *\n * @param node - A paint value or a node inside one.\n * @returns The simplestyle key and the layer-level base, or `null` when the\n * node is not that lookup.\n */\nexport function readSimpleStyleNumberLookup(\n node: unknown,\n): { property: (typeof SIMPLE_STYLE_NUMBER_KEYS)[number]; base: number } | null {\n if (!Array.isArray(node) || node.length !== 3 || node[0] !== \"to-number\") return null;\n const inner = node[1];\n if (!Array.isArray(inner) || inner.length !== 3 || inner[0] !== \"coalesce\") return null;\n const property = simpleStyleGet(inner[1], SIMPLE_STYLE_NUMBER_KEYS);\n const base = node[2];\n if (!property || typeof base !== \"number\" || !Number.isFinite(base) || inner[2] !== base) {\n return null;\n }\n return { property, base };\n}\n\n/**\n * Whether a paint value reads per-feature simplestyle properties the way the\n * GeoLibre exporter writes them, anywhere inside it (a lookup can sit inside a\n * zoom step, a per-rule `case` or an opacity multiplication).\n *\n * @param value - A paint value.\n * @returns `true` when a color or number lookup is found.\n */\nexport function readsSimpleStyleProperties(value: unknown): boolean {\n if (!Array.isArray(value)) return false;\n if (readSimpleStyleColorLookup(value) || readSimpleStyleNumberLookup(value)) return true;\n return value.some(readsSimpleStyleProperties);\n}\n\n/**\n * Ground resolution (meters per pixel) at MapLibre zoom 0 on the equator, for\n * the Web Mercator projection: earth circumference (2*pi*6378137) over the\n * 512px world at zoom 0. Resolution halves with every zoom level. Exported so\n * the Mapbox-style importer can reverse {@link metersWidthExpression} without a\n * second copy of the magic number.\n */\nexport const MERCATOR_METERS_PER_PIXEL_AT_ZOOM_0 = (2 * Math.PI * 6378137) / 512;\n\n/**\n * Same ground resolution as {@link MERCATOR_METERS_PER_PIXEL_AT_ZOOM_0} but for\n * the project's active body: its circumference over the 512px zoom-0 world. On\n * Earth this equals the constant; on the Moon/Mars it uses that body's radius so\n * a stroke width given in ground meters renders at the correct on-screen size.\n * Exported so the Mapbox-style importer can reverse {@link metersWidthExpression}\n * with the same ellipsoid-aware factor the exporter used (keeping the round trip\n * correct on non-Earth projects), rather than the Earth-only constant.\n */\nexport function mercatorMetersPerPixelAtZoom0(): number {\n return (2 * Math.PI * getActiveSemiMajorAxisMeters()) / 512;\n}\n\n// Largest zoom MapLibre renders; used as the upper interpolation stop.\nconst MAX_MERCATOR_ZOOM = 24;\n\n/**\n * Build a zoom-driven width expression that keeps a stroke proportional to the\n * map scale, so a width given in ground meters renders thicker when zoomed in\n * and thinner when zoomed out (QGIS \"map units\" behavior).\n *\n * In Web Mercator the pixels-per-meter ratio doubles with each zoom level, so\n * an `[\"exponential\", 2]` interpolation between two stops one zoom apart is\n * exact across the whole range. The conversion is referenced to the equator;\n * because Mercator stretches distances toward the poles, the on-screen width at\n * higher latitudes is correspondingly larger, matching how the underlying map\n * is itself stretched.\n *\n * Typed maplibre-agnostically (`unknown[]`); consumers cast to the concrete\n * `PropertyValueSpecification<number>` where the MapLibre types are in scope.\n *\n * @param meters - The stroke width in ground meters.\n * @returns A MapLibre `interpolate` expression array.\n */\nexport function metersWidthExpression(meters: number): unknown[] {\n const widthAtZoom0 = meters / mercatorMetersPerPixelAtZoom0();\n return [\n \"interpolate\",\n [\"exponential\", 2],\n [\"zoom\"],\n 0,\n widthAtZoom0,\n MAX_MERCATOR_ZOOM,\n widthAtZoom0 * 2 ** MAX_MERCATOR_ZOOM,\n ];\n}\n\n/**\n * Resolve the `line-width` paint value for a layer style, honoring the\n * {@link LayerStyle.strokeWidthUnit}:\n *\n * - `\"meters\"`: a zoom-driven {@link metersWidthExpression} from the flat\n * `strokeWidth`, so the stroke scales with the map. A per-feature pixel\n * `stroke-width` override no longer applies in this mode.\n * - `\"pixels\"` (default): the constant pixel width, still honoring any\n * per-feature simplestyle `stroke-width`.\n *\n * Shared by the map style-mapper and the geo-editor plugin so the Sketches\n * store layer and Geoman's interaction display layers render an identical\n * width.\n *\n * @param style - The layer style.\n * @returns A number (constant pixels) or a MapLibre expression array.\n */\nexport function lineWidthValue(style: LayerStyle): number | unknown[] {\n // Proportional (graduated) sizing takes precedence: width is driven by a\n // numeric field, reusing the circle-radius output range as the width range.\n const proportionalWidth = proportionalRadiusExpression(style);\n if (proportionalWidth) {\n // Per-rule widths still override the proportional base for matched\n // features (the property-driven interpolate nests legally inside a\n // case), mirroring circleRadiusValue.\n return vectorStrokeWidthValue(style, proportionalWidth);\n }\n if (styleValue(style, \"strokeWidthUnit\") === \"meters\") {\n // The meters width is itself a zoom-driven interpolation; embedding it\n // inside a per-rule case would nest [\"zoom\"] below the top level, which\n // MapLibre rejects. Like the per-feature simplestyle width, per-rule pixel\n // widths do not apply in meters mode.\n return metersWidthExpression(styleValue(style, \"strokeWidth\"));\n }\n // Per-rule symbol widths (rule-based mode) override the layer width for\n // features a rule matches; unmatched features keep the base value.\n return vectorStrokeWidthValue(\n style,\n simpleStyleNumberValue(style, \"stroke-width\", styleValue(style, \"strokeWidth\")),\n );\n}\n\n/**\n * The layer's stroke opacity, clamped to [0, 1]. A missing or non-finite value\n * (an older project, a hand-edited file) reads as fully opaque.\n *\n * @param style - The layer style.\n * @returns The stroke opacity.\n */\nexport function strokeOpacityValue(style: LayerStyle): number {\n const value = styleValue(style, \"strokeOpacity\");\n return typeof value === \"number\" && Number.isFinite(value) ? Math.min(1, Math.max(0, value)) : 1;\n}\n\n/**\n * A stroke opacity paint value: the layer's {@link LayerStyle.strokeOpacity},\n * which a per-feature simplestyle `stroke-opacity` replaces when\n * {@link LayerStyle.simpleStyleEnabled} is set.\n *\n * @param style - The layer style.\n * @returns A number, or the simplestyle lookup expression.\n */\nexport function strokeOpacityPaintValue(style: LayerStyle): number | unknown[] {\n return simpleStyleNumberValue(style, \"stroke-opacity\", strokeOpacityValue(style));\n}\n\n/**\n * Whether a value is a usable dash pattern: finite, non-negative lengths with\n * at least one dash longer than zero. Exported so the style sanitizer and the\n * importers apply the same rule the renderer does.\n *\n * @param value - The candidate pattern.\n * @returns True when the value can be drawn as a dash pattern.\n */\nexport function isStrokeDashPattern(value: unknown): value is number[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n value.every((length) => typeof length === \"number\" && Number.isFinite(length) && length >= 0) &&\n value.some((length) => length > 0)\n );\n}\n\n/**\n * The `line-dasharray` for the layer, or `null` for a solid line. `null` is\n * what MapLibre's `setPaintProperty` takes to reset a property, so returning it\n * (rather than leaving the key out) clears a dash when the user goes back to\n * solid.\n *\n * @param style - The layer style.\n * @returns The dash pattern, or `null` when the line is solid.\n */\nexport function strokeDashValue(style: LayerStyle): number[] | null {\n const dash = styleValue(style, \"strokeDash\");\n return isStrokeDashPattern(dash) ? [...dash] : null;\n}\n\n/**\n * The {@link STROKE_DASH_PRESETS} name matching a pattern, or `\"custom\"` when\n * none does. An empty or unusable pattern is `\"solid\"`.\n *\n * @param dash - A dash pattern.\n * @returns The preset name, or `\"custom\"`.\n */\nexport function strokeDashPresetName(\n dash: readonly number[] | undefined,\n): StrokeDashPreset | \"custom\" {\n if (!isStrokeDashPattern(dash)) return \"solid\";\n for (const [name, pattern] of Object.entries(STROKE_DASH_PRESETS)) {\n if (\n pattern.length === dash.length &&\n pattern.every((length, index) => length === dash[index])\n ) {\n return name as StrokeDashPreset;\n }\n }\n return \"custom\";\n}\n\n/**\n * The layer's line blur in pixels; a missing, negative or non-finite value is 0.\n *\n * @param style - The layer style.\n * @returns The blur in pixels.\n */\nexport function strokeBlurValue(style: LayerStyle): number {\n const value = styleValue(style, \"strokeBlur\");\n return typeof value === \"number\" && Number.isFinite(value) && value > 0 ? value : 0;\n}\n\n/**\n * Whether a FeatureCollection carries per-feature simplestyle-spec properties\n * worth honoring: at least one feature with a valid hex color in a color key\n * (`fill`/`stroke`/`marker-color`) or a finite number in a numeric key\n * (`fill-opacity`/`stroke-width`/`stroke-opacity`). The scan is capped so very\n * large collections do not pay a full pass.\n *\n * @param geojson - The collection to inspect (may be undefined).\n * @returns `true` when simplestyle rendering should be enabled for the layer.\n */\nexport function hasSimpleStyleProperties(\n geojson: { features?: { properties?: Record<string, unknown> | null }[] } | undefined,\n): boolean {\n const features = geojson?.features;\n if (!features?.length) return false;\n const limit = Math.min(features.length, 1000);\n for (let index = 0; index < limit; index += 1) {\n const properties = features[index]?.properties;\n if (!properties) continue;\n for (const key of SIMPLE_STYLE_COLOR_KEYS) {\n const value = properties[key];\n if (typeof value === \"string\" && isSimpleStyleColor(value)) return true;\n }\n for (const key of SIMPLE_STYLE_NUMBER_KEYS) {\n const value = properties[key];\n if (typeof value === \"number\" && Number.isFinite(value)) return true;\n }\n }\n return false;\n}\n\n/**\n * Parses a user-entered MapLibre expression string into an expression array,\n * tolerating trailing commas. Returns null when the text is empty or not a\n * JSON array.\n */\nexport function parseJsonExpression(expression: string): unknown[] | null {\n const trimmed = expression.trim();\n if (!trimmed) return null;\n\n try {\n const parsed = JSON.parse(removeTrailingJsonCommas(trimmed));\n if (!Array.isArray(parsed)) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\n/**\n * Builds the data-driven color value for a vector layer's current style mode.\n * `single` (or any mode that cannot produce a valid expression) returns the\n * flat fallback color; `categorized` returns a `match` expression, `graduated`\n * a `step` expression, and `expression` the parsed user expression.\n *\n * Graduated stops are class lower bounds (see\n * {@link createGraduatedClassBreaks}), so the classes are discrete: the map\n * paints exactly one color per class, which is what the legend (`≥ value`) and\n * the QML/SLD exporters already describe. Rendering them as a continuous\n * `interpolate` instead blends a shade per feature, so a 4-class layer draws in\n * many more than 4 colors and stops matching its own legend (#1384).\n *\n * @param style - The layer style.\n * @param fallbackColor - The flat color used for `single` mode and as the\n * expression fallback.\n * @returns A flat color string or a MapLibre color expression.\n */\nexport function vectorColorExpression(style: LayerStyle, fallbackColor: string): VectorColorValue {\n const mode = styleValue(style, \"vectorStyleMode\");\n if (mode === \"single\") return fallbackColor;\n\n if (mode === \"expression\") {\n return parseJsonExpression(styleValue(style, \"vectorStyleExpression\")) ?? fallbackColor;\n }\n\n if (mode === \"rule-based\") {\n return ruleBasedColorExpression(style, fallbackColor);\n }\n\n const property = styleValue(style, \"vectorStyleProperty\").trim();\n if (!property) return fallbackColor;\n\n if (mode === \"categorized\") {\n const stops = styleValue(style, \"vectorStyleStops\").filter(\n (stop) => String(stop.value).trim().length > 0 && isHexColor(stop.color),\n );\n if (stops.length === 0) return fallbackColor;\n\n return [\n \"match\",\n [\"to-string\", [\"get\", property]],\n ...stops.flatMap((stop) => [String(stop.value).trim(), stop.color]),\n fallbackColor,\n ];\n }\n\n const stops = graduatedStops(style);\n if (stops.length < 2) return fallbackColor;\n\n // `step` outputs the base color below the first break, then switches at each\n // subsequent break, so every feature lands in exactly one class and features\n // under the lowest break keep the first class's color (matching how the SLD\n // exporter writes its leading `< first` rule).\n return [\n \"step\",\n [\"to-number\", [\"get\", property], stops[0].value],\n stops[0].color,\n ...stops.slice(1).flatMap((stop) => [stop.value, stop.color]),\n ];\n}\n\n/**\n * The graduated stops of a style, cleaned for rendering: non-numeric values and\n * invalid colors dropped, sorted ascending, and de-duplicated. MapLibre rejects\n * a `step` expression whose inputs are not strictly ascending, and stops reach\n * here from hand-edited projects and style imports as well as the Style panel,\n * so repeats are dropped rather than trusted away.\n */\nfunction graduatedStops(style: LayerStyle): { color: string; value: number }[] {\n const sorted = styleValue(style, \"vectorStyleStops\")\n .map((stop) => ({\n color: stop.color,\n value: typeof stop.value === \"number\" ? stop.value : Number.parseFloat(stop.value),\n }))\n .filter((stop) => Number.isFinite(stop.value) && isHexColor(stop.color))\n .sort((a, b) => a.value - b.value);\n return sorted.filter((stop, index) => index === 0 || stop.value > sorted[index - 1].value);\n}\n\n/**\n * A rule-based renderer rule resolved for rendering: the rule tree walked down\n * to a drawable leaf, with the ancestors' filters ANDed in and zoom ranges\n * intersected. Only enabled leaves with a valid\n * color and filter appear; group rules (rules other rules name as\n * {@link VectorRule.parentId}) contribute constraints but are not themselves\n * emitted. Shared by the paint compilers, the legend, and the symbology\n * exporters so all of them agree on which rules actually draw.\n */\nexport interface EffectiveVectorRule {\n id: string;\n /** The leaf rule's label (for the legend / exported rule titles). */\n label: string;\n /** The symbol fill/circle color (valid 6-digit hex). */\n color: string;\n /** The parsed MapLibre filter, ANDed with every ancestor's filter. */\n filter: unknown[];\n /** Intersected lower zoom bound (inclusive), when any rule in the chain has one. */\n minZoom?: number;\n /** Intersected upper zoom bound (exclusive), when any rule in the chain has one. */\n maxZoom?: number;\n strokeColor?: string;\n strokeWidth?: number;\n fillOpacity?: number;\n circleRadius?: number;\n}\n\n/** A finite per-rule zoom bound, or undefined when unset/invalid. */\nfunction ruleZoom(value: number | undefined): number | undefined {\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * Resolve the layer's rule tree into the drawable leaf rules (see\n * {@link EffectiveVectorRule}) plus the catch-all else rule. Disabled rules\n * (and every descendant of a disabled group), rules with an invalid color or\n * filter, subtrees under a group whose non-blank filter does not parse, and\n * rules whose intersected zoom range is empty are all dropped. Cycles in\n * `parentId` are treated as broken chains and dropped rather than looping.\n *\n * @param style - The layer style (reads {@link LayerStyle.vectorRules}).\n * @returns The ordered drawable rules and the enabled else rule, if any.\n */\nexport function effectiveVectorRules(style: LayerStyle): {\n rules: EffectiveVectorRule[];\n elseRule: VectorRule | null;\n} {\n const all = styleValue(style, \"vectorRules\");\n const elseRule = all.find((rule) => rule.isElse && rule.enabled !== false) ?? null;\n const byId = new Map<string, VectorRule>();\n for (const rule of all) {\n if (!rule.isElse) byId.set(rule.id, rule);\n }\n // A rule other rules point at is a group; only leaves draw. A rule naming\n // itself as its parent is treated as top-level (mirroring the editor and\n // the QML exporter), not as its own group.\n const groupIds = new Set<string>();\n for (const rule of byId.values()) {\n if (rule.parentId && rule.parentId !== rule.id && byId.has(rule.parentId)) {\n groupIds.add(rule.parentId);\n }\n }\n\n const rules: EffectiveVectorRule[] = [];\n for (const rule of all) {\n if (rule.isElse || groupIds.has(rule.id)) continue;\n if (rule.enabled === false || !isHexColor(rule.color)) continue;\n const own = parseJsonExpression(rule.filter);\n // A MapLibre filter is an expression that must start with a string operator;\n // skip non-operator arrays (e.g. a bare value) so the compiled `case` never\n // carries a non-boolean condition that MapLibre would reject at runtime.\n if (!own || typeof own[0] !== \"string\") continue;\n\n const filters: unknown[][] = [own];\n let minZoom = ruleZoom(rule.minZoom);\n let maxZoom = ruleZoom(rule.maxZoom);\n let dropped = false;\n const seen = new Set([rule.id]);\n // A self-referencing parentId means \"no parent\" (a normal top-level\n // rule), matching the editor's tree walk and the QML exporter.\n let parent = rule.parentId && rule.parentId !== rule.id ? byId.get(rule.parentId) : undefined;\n while (parent) {\n if (seen.has(parent.id) || parent.enabled === false) {\n dropped = true;\n break;\n }\n seen.add(parent.id);\n if (parent.filter.trim()) {\n const parentFilter = parseJsonExpression(parent.filter);\n // A group whose filter is written but unreadable can match nothing;\n // dropping the subtree mirrors how an invalid leaf filter is skipped.\n if (!parentFilter || typeof parentFilter[0] !== \"string\") {\n dropped = true;\n break;\n }\n filters.unshift(parentFilter);\n }\n const parentMin = ruleZoom(parent.minZoom);\n const parentMax = ruleZoom(parent.maxZoom);\n if (parentMin !== undefined) {\n minZoom = minZoom === undefined ? parentMin : Math.max(minZoom, parentMin);\n }\n if (parentMax !== undefined) {\n maxZoom = maxZoom === undefined ? parentMax : Math.min(maxZoom, parentMax);\n }\n // An ancestor with a self-referencing parentId is a top-level rule (the\n // same convention as the initial parent resolution above); advancing to\n // itself would trip the cycle guard and wrongly drop the whole subtree.\n parent =\n parent.parentId && parent.parentId !== parent.id ? byId.get(parent.parentId) : undefined;\n }\n if (dropped) continue;\n if (minZoom !== undefined && maxZoom !== undefined && minZoom >= maxZoom) {\n continue;\n }\n\n rules.push({\n id: rule.id,\n label: rule.label,\n color: rule.color,\n filter: filters.length === 1 ? filters[0] : [\"all\", ...filters],\n ...(minZoom !== undefined ? { minZoom } : {}),\n ...(maxZoom !== undefined ? { maxZoom } : {}),\n ...(isHexColor(rule.strokeColor) ? { strokeColor: rule.strokeColor } : {}),\n ...(typeof rule.strokeWidth === \"number\" && Number.isFinite(rule.strokeWidth)\n ? { strokeWidth: rule.strokeWidth }\n : {}),\n ...(typeof rule.fillOpacity === \"number\" && Number.isFinite(rule.fillOpacity)\n ? { fillOpacity: rule.fillOpacity }\n : {}),\n ...(typeof rule.circleRadius === \"number\" && Number.isFinite(rule.circleRadius)\n ? { circleRadius: rule.circleRadius }\n : {}),\n });\n }\n return { rules, elseRule };\n}\n\n/**\n * A rule's match condition as a MapLibre filter: its (ancestor-combined)\n * attribute filter, with any scale range folded in as `[\"zoom\"]` comparisons.\n * The zoom window is half-open (`min <= zoom < max`), matching how\n * {@link zoomWrappedRuleValue} activates rules per zoom segment. MapLibre only\n * re-evaluates `[\"zoom\"]` in filters at integer zoom levels, so a scale-ranged\n * rule's features appear/disappear on whole zooms — the paint expressions keep\n * the fractional-zoom precision.\n */\nfunction ruleMatchFilter(rule: EffectiveVectorRule): unknown[] {\n const conditions: unknown[] = [rule.filter];\n if (rule.minZoom !== undefined) {\n conditions.push([\">=\", [\"zoom\"], rule.minZoom]);\n }\n if (rule.maxZoom !== undefined) {\n conditions.push([\"<\", [\"zoom\"], rule.maxZoom]);\n }\n return conditions.length === 1 ? rule.filter : [\"all\", ...conditions];\n}\n\n// The layer sync recomputes the visibility filter for every render sub-layer\n// (fill, line, point, labels, …) on every sync tick, and compiling it re-walks\n// and re-parses the whole rule tree. The store replaces the vectorRules array\n// on every edit, so the array's identity is a correct cache key; memoizing on\n// it makes the repeated per-sub-layer calls O(1), mirroring the WeakMap caches\n// in the map package (e.g. the text-marker scan).\nconst visibilityFilterCache = new WeakMap<VectorRule[], unknown[] | null>();\n\n/**\n * The MapLibre filter that hides features matching no rule, for a rule-based\n * layer whose catch-all else rule has been switched off. Returns `null` — and\n * callers then leave their geometry filters untouched — unless the layer is in\n * `\"rule-based\"` mode AND carries an else record explicitly disabled\n * (`enabled: false`). An absent else record keeps the historical fallback\n * rendering, so projects saved before the toggle existed are unaffected.\n *\n * With the else rule off, the returned `[\"any\", filter1, filter2, …]` keeps\n * only features matched by a drawable rule (see {@link effectiveVectorRules});\n * with no drawable rules it returns the vacuous `[\"any\"]`, which matches\n * nothing, so every feature hides. Sub-layers combine it with their geometry\n * filters, which also drops unmatched features from labels and hit-testing —\n * mirroring QGIS, where features matching no rule are simply not drawn.\n *\n * @param style - The layer style (reads {@link LayerStyle.vectorRules}).\n * @returns The filter to `[\"all\", …]`-combine into each sub-layer, or `null`.\n */\nexport function ruleBasedVisibilityFilter(style: LayerStyle): unknown[] | null {\n if (styleValue(style, \"vectorStyleMode\") !== \"rule-based\") return null;\n const all = styleValue(style, \"vectorRules\");\n const cached = visibilityFilterCache.get(all);\n if (cached !== undefined) return cached;\n const elseRecord = all.find((rule) => rule.isElse);\n const filter =\n !elseRecord || elseRecord.enabled !== false\n ? null\n : [\"any\", ...effectiveVectorRules(style).rules.map(ruleMatchFilter)];\n visibilityFilterCache.set(all, filter);\n return filter;\n}\n\n/**\n * Wrap a per-zoom-segment value builder in a `[\"step\", [\"zoom\"], …]` expression\n * when any rule carries a zoom bound. MapLibre only allows `[\"zoom\"]` as the\n * top-level input of a `step`/`interpolate` in paint properties (not inside a\n * `case` condition), so per-rule scale ranges compile by splitting the zoom\n * axis at every rule bound and rebuilding the `case` expression per segment\n * with only the rules active in that segment. With no zoom bounds the builder\n * runs once and its plain value is returned unchanged, keeping the historical\n * `case` shape (which the Mapbox-style importer recognizes).\n */\nfunction zoomWrappedRuleValue(\n rules: EffectiveVectorRule[],\n build: (active: EffectiveVectorRule[]) => unknown,\n): unknown {\n const bounds = new Set<number>();\n for (const rule of rules) {\n if (rule.minZoom !== undefined) bounds.add(rule.minZoom);\n if (rule.maxZoom !== undefined) bounds.add(rule.maxZoom);\n }\n if (bounds.size === 0) return build(rules);\n const breaks = [...bounds].sort((a, b) => a - b);\n const activeAt = (segmentStart: number) =>\n rules.filter(\n (rule) =>\n (rule.minZoom ?? -Infinity) <= segmentStart && segmentStart < (rule.maxZoom ?? Infinity),\n );\n const expression: unknown[] = [\"step\", [\"zoom\"], build(activeAt(-Infinity))];\n for (const zoom of breaks) {\n expression.push(zoom, build(activeAt(zoom)));\n }\n return expression;\n}\n\n/**\n * Compiles the `\"rule-based\"` renderer's ordered rules into a MapLibre `case`\n * color expression: `[\"case\", filter1, color1, filter2, color2, …, elseColor]`.\n * Rules are evaluated top to bottom; the first matching filter wins. Disabled\n * rules and rules with an invalid filter JSON or a non-hex color are skipped;\n * nested rules AND their ancestors' filters (see {@link effectiveVectorRules}).\n * When any rule carries a zoom range the `case` is rebuilt per zoom segment\n * inside a `[\"step\", [\"zoom\"], …]` wrapper. The catch-all (`isElse`) rule\n * supplies the trailing fallback; when absent or invalid the layer\n * `fallbackColor` is used. With no usable rules the flat fallback color is\n * returned.\n *\n * @param style - The layer style (reads {@link LayerStyle.vectorRules}).\n * @param fallbackColor - The color used when no else rule defines one.\n * @returns A MapLibre expression, or a flat color when no rule applies.\n */\nexport function ruleBasedColorExpression(\n style: LayerStyle,\n fallbackColor: string,\n): VectorColorValue {\n const { rules, elseRule } = effectiveVectorRules(style);\n const elseColor = elseRule && isHexColor(elseRule.color) ? elseRule.color : fallbackColor;\n if (rules.length === 0) return elseColor;\n return zoomWrappedRuleValue(rules, (active) => {\n if (active.length === 0) return elseColor;\n return [\"case\", ...active.flatMap((rule) => [rule.filter, rule.color]), elseColor];\n }) as VectorColorValue;\n}\n\n/** The per-rule override fields a paint channel can read. */\ntype VectorRuleOverrideKey = \"strokeColor\" | \"strokeWidth\" | \"fillOpacity\" | \"circleRadius\";\n\n/**\n * Build a paint value honoring per-rule symbol overrides for one field: a\n * `case` expression (zoom-wrapped when rules carry scale ranges) where each\n * drawable rule's branch yields its override, or the layer fallback when that\n * rule does not override the field. Returns the fallback unchanged when the\n * layer is not in rule-based mode or no rule (including the else rule)\n * overrides the field, so simple styles keep their flat paint values.\n *\n * First-match semantics are preserved: every drawable rule contributes a\n * branch (even without an override) so a feature matched by an earlier rule\n * never picks up a later rule's override.\n */\nfunction ruleOverrideValue(\n style: LayerStyle,\n key: VectorRuleOverrideKey,\n fallback: unknown,\n): unknown {\n if (styleValue(style, \"vectorStyleMode\") !== \"rule-based\") return fallback;\n const { rules, elseRule } = effectiveVectorRules(style);\n const elseOverride = readRuleOverride(elseRule ?? undefined, key);\n if (elseOverride === undefined && !rules.some((rule) => rule[key] !== undefined)) {\n return fallback;\n }\n const elseValue = elseOverride ?? fallback;\n return zoomWrappedRuleValue(rules, (active) => {\n if (active.length === 0) return elseValue;\n return [\"case\", ...active.flatMap((rule) => [rule.filter, rule[key] ?? fallback]), elseValue];\n });\n}\n\n/** A validated override read straight off a (possibly else) {@link VectorRule}. */\nfunction readRuleOverride(\n rule: VectorRule | undefined,\n key: VectorRuleOverrideKey,\n): string | number | undefined {\n const value = rule?.[key];\n if (key === \"strokeColor\") {\n return isHexColor(value) ? (value as string) : undefined;\n }\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\n/**\n * The stroke/outline color for polygon outlines and circle strokes, honoring\n * per-rule {@link VectorRule.strokeColor} overrides in rule-based mode.\n * Falls back to the flat layer stroke color.\n */\nexport function vectorOutlineColorValue(style: LayerStyle): VectorColorValue {\n return ruleOverrideValue(\n style,\n \"strokeColor\",\n styleValue(style, \"strokeColor\"),\n ) as VectorColorValue;\n}\n\n/**\n * A stroke-width paint value honoring per-rule {@link VectorRule.strokeWidth}\n * overrides in rule-based mode; `fallback` is the layer-level width (a number\n * or an already-built expression such as the meters-based interpolation).\n */\nexport function vectorStrokeWidthValue(\n style: LayerStyle,\n fallback: number | unknown[],\n): number | unknown[] {\n return ruleOverrideValue(style, \"strokeWidth\", fallback) as number | unknown[];\n}\n\n/**\n * A fill/circle opacity paint value honoring per-rule\n * {@link VectorRule.fillOpacity} overrides in rule-based mode; `fallback` is\n * the layer-level opacity value (number or expression).\n */\nexport function vectorFillOpacityValue(\n style: LayerStyle,\n fallback: number | unknown[],\n): number | unknown[] {\n return ruleOverrideValue(style, \"fillOpacity\", fallback) as number | unknown[];\n}\n\n/**\n * The color-ramp colors a density heatmap paints with.\n *\n * {@link getVectorColorRamp} falls back to the *first* ramp (\"viridis\") for an\n * unknown name, which is not the heatmap default (\"turbo\"). Both the map paint\n * (`heatmapPaint` in `@geolibre/map`) and the auto-legend gradient resolve the\n * ramp here so a stale or hand-edited `heatmapColorRamp` cannot make the legend\n * and the rendered heatmap disagree about which ramp is in use.\n *\n * @param style - The layer style.\n * @returns The ramp's colors, or the default ramp's colors for an unknown name.\n */\nexport function heatmapRampColors(style: LayerStyle): readonly string[] {\n const value = styleValue(style, \"heatmapColorRamp\");\n const ramp = getVectorColorRamp(value);\n return (ramp.value === value ? ramp : getVectorColorRamp(DEFAULT_LAYER_STYLE.heatmapColorRamp))\n .colors;\n}\n\n/** The validated proportional (graduated) size configuration of a layer. */\nexport interface ProportionalSizeRange {\n property: string;\n minValue: number;\n maxValue: number;\n minRadius: number;\n maxRadius: number;\n}\n\n/**\n * The proportional-size configuration, or `null` when proportional sizing\n * does not apply: disabled, no field chosen, non-finite or degenerate\n * (`maxValue <= minValue`) value range, or non-finite radii. This is the\n * single validation chain for every proportional consumer — circle radius,\n * line width, and marker icon-size (`@geolibre/map`) — so their notion of\n * \"active\" can never drift apart.\n *\n * @param style - The layer style.\n * @returns The validated range, or `null` when proportional sizing is off.\n */\nexport function proportionalSizeRange(style: LayerStyle): ProportionalSizeRange | null {\n if (!styleValue(style, \"proportionalSizeEnabled\")) return null;\n return validatedSizeRange(\n styleValue(style, \"proportionalSizeProperty\"),\n styleValue(style, \"proportionalSizeMinValue\"),\n styleValue(style, \"proportionalSizeMaxValue\"),\n styleValue(style, \"proportionalSizeMinRadius\"),\n styleValue(style, \"proportionalSizeMaxRadius\"),\n );\n}\n\n/**\n * The validation every field-driven size range shares: a chosen field, a\n * finite non-degenerate value span, and finite radii. Kept in one place so a\n * future tightening (rejecting negative radii, say) cannot land on the\n * layer-wide proportional renderer without also landing on the geometry\n * generator's centroids.\n */\nfunction validatedSizeRange(\n rawProperty: string,\n minValue: number,\n maxValue: number,\n minRadius: number,\n maxRadius: number,\n): ProportionalSizeRange | null {\n const property = rawProperty.trim();\n if (!property) return null;\n if (!(Number.isFinite(minValue) && Number.isFinite(maxValue))) return null;\n if (maxValue <= minValue) return null;\n if (!(Number.isFinite(minRadius) && Number.isFinite(maxRadius))) return null;\n return { property, minValue, maxValue, minRadius, maxRadius };\n}\n\n/**\n * The proportional `interpolate` expression mapping\n * `proportionalSizeMinValue..proportionalSizeMaxValue` onto\n * `proportionalSizeMinRadius..proportionalSizeMaxRadius`, or `null` when\n * proportional sizing does not apply (see {@link proportionalSizeRange}).\n *\n * @param style - The layer style.\n * @returns The `interpolate` expression, or `null`.\n */\nexport function proportionalRadiusExpression(style: LayerStyle): unknown[] | null {\n const range = proportionalSizeRange(style);\n return range ? radiusInterpolateExpression(range) : null;\n}\n\n/**\n * The `interpolate` that maps a validated range's value span onto its radius\n * span. Shared by the layer-wide proportional renderer and the geometry\n * generator's centroid sizing so both scale identically (linear, with the\n * value clamped to the range's ends by `interpolate`'s own stop behavior).\n */\nfunction radiusInterpolateExpression(range: ProportionalSizeRange): unknown[] {\n return [\n \"interpolate\",\n [\"linear\"],\n [\"to-number\", [\"get\", range.property], range.minValue],\n range.minValue,\n Math.max(0, range.minRadius),\n range.maxValue,\n Math.max(0, range.maxRadius),\n ];\n}\n\n/**\n * The geometry generator's centroid-size configuration, or `null` when the\n * generated points should all take the flat\n * {@link LayerStyle.geometryGeneratorCircleRadius}: no field chosen, non-finite\n * or degenerate (`maxValue <= minValue`) value range, or non-finite radii.\n * Mirrors {@link proportionalSizeRange} against the generator's own keys.\n *\n * @param style - The layer style.\n * @returns The validated range, or `null` when field-driven sizing is off.\n */\nexport function generatorSizeRange(style: LayerStyle): ProportionalSizeRange | null {\n return validatedSizeRange(\n styleValue(style, \"geometryGeneratorSizeProperty\"),\n styleValue(style, \"geometryGeneratorSizeMinValue\"),\n styleValue(style, \"geometryGeneratorSizeMaxValue\"),\n styleValue(style, \"geometryGeneratorSizeMinRadius\"),\n styleValue(style, \"geometryGeneratorSizeMaxRadius\"),\n );\n}\n\n/**\n * Builds the `circle-radius` paint value for the geometry generator's derived\n * centroid points: an `interpolate` over\n * {@link LayerStyle.geometryGeneratorSizeProperty} when field-driven sizing\n * applies (see {@link generatorSizeRange}), otherwise the flat\n * {@link LayerStyle.geometryGeneratorCircleRadius}.\n *\n * The flat fallback keeps the 1px floor the generator has always applied; the\n * interpolate is left as authored, so a 0px min radius still hides the\n * smallest symbols on purpose.\n *\n * @param style - The layer style.\n * @returns A constant radius (pixels) or a MapLibre `interpolate` expression.\n */\nexport function generatorCircleRadiusValue(style: LayerStyle): number | unknown[] {\n const range = generatorSizeRange(style);\n if (range) return radiusInterpolateExpression(range);\n return Math.max(1, styleValue(style, \"geometryGeneratorCircleRadius\"));\n}\n\n/**\n * Builds the `circle-radius` paint value, honoring proportional (graduated)\n * symbol sizing. When {@link LayerStyle.proportionalSizeEnabled} is set with a\n * chosen numeric field and a valid value range, returns an `interpolate` that\n * maps `proportionalSizeMinValue..proportionalSizeMaxValue` onto\n * `proportionalSizeMinRadius..proportionalSizeMaxRadius`; otherwise the constant\n * {@link LayerStyle.circleRadius}.\n *\n * @param style - The layer style.\n * @returns A constant radius (pixels) or a MapLibre `interpolate` expression.\n */\nexport function circleRadiusValue(style: LayerStyle): number | unknown[] {\n // Per-rule symbol sizes (rule-based mode) override the base radius for\n // features a rule matches; unmatched features keep the base value.\n return ruleOverrideValue(style, \"circleRadius\", baseCircleRadiusValue(style)) as\n | number\n | unknown[];\n}\n\n/** The layer-level circle radius before per-rule overrides. */\nfunction baseCircleRadiusValue(style: LayerStyle): number | unknown[] {\n return proportionalRadiusExpression(style) ?? styleValue(style, \"circleRadius\");\n}\n\n/** Fill color value for a polygon layer (fallback: the layer fill color). */\nexport function vectorFillColorValue(style: LayerStyle): VectorColorValue {\n return withSimpleStyleColor(\n style,\n \"fill\",\n vectorColorExpression(style, styleValue(style, \"fillColor\")),\n );\n}\n\n/**\n * Circle color value for a point layer. Intentionally identical to\n * `vectorFillColorValue`: GeoLibre has no separate point-fill color, so point\n * circles share the polygon fill color (matching `circlePaint` in the map\n * package). Kept as its own function so the per-geometry callers read in\n * parallel and a future dedicated circle color stays a one-line change here.\n */\nexport function vectorCircleColorValue(style: LayerStyle): VectorColorValue {\n return withSimpleStyleColor(\n style,\n \"marker-color\",\n vectorColorExpression(style, styleValue(style, \"fillColor\")),\n );\n}\n\n/**\n * Line color value for line geometry and polygon outlines (fallback: the\n * layer stroke color). For non-`expression` modes the data-driven color is\n * applied to line geometry only, while polygon outlines keep the flat stroke\n * color, matching the polygon-fill-only behavior of categorized/graduated\n * styling.\n */\nexport function vectorLineColorValue(style: LayerStyle): VectorColorValue {\n const strokeColor = styleValue(style, \"strokeColor\");\n if (styleValue(style, \"vectorStyleMode\") === \"rule-based\") {\n return withSimpleStyleColor(style, \"stroke\", ruleBasedLineColorExpression(style));\n }\n const vectorColor = vectorColorExpression(style, strokeColor);\n const resolved =\n vectorColor === strokeColor\n ? strokeColor\n : styleValue(style, \"vectorStyleMode\") === \"expression\"\n ? vectorColor\n : [\"case\", [\"==\", [\"geometry-type\"], \"Polygon\"], strokeColor, vectorColor];\n return withSimpleStyleColor(style, \"stroke\", resolved);\n}\n\n/**\n * The line color for the rule-based renderer: line geometry takes the rule\n * color, polygon outlines take the per-rule stroke color override (falling\n * back to the flat layer stroke). Both channels are built inside one shared\n * zoom wrapper, since composing two independently zoom-stepped expressions in\n * a single `case` would nest `[\"zoom\"]` below the top level, which MapLibre\n * rejects.\n */\nfunction ruleBasedLineColorExpression(style: LayerStyle): VectorColorValue {\n const strokeColor = styleValue(style, \"strokeColor\");\n const { rules, elseRule } = effectiveVectorRules(style);\n const elseColor = elseRule && isHexColor(elseRule.color) ? elseRule.color : strokeColor;\n const elseOutline =\n (readRuleOverride(elseRule ?? undefined, \"strokeColor\") as string | undefined) ?? strokeColor;\n const hasOutlineOverride =\n rules.some((rule) => rule.strokeColor !== undefined) || elseOutline !== strokeColor;\n if (rules.length === 0 && elseColor === strokeColor && !hasOutlineOverride) {\n return strokeColor;\n }\n return zoomWrappedRuleValue(rules, (active) => {\n const colorValue: unknown =\n active.length === 0\n ? elseColor\n : [\"case\", ...active.flatMap((rule) => [rule.filter, rule.color]), elseColor];\n const outlineValue: unknown = !hasOutlineOverride\n ? strokeColor\n : active.length === 0\n ? elseOutline\n : [\n \"case\",\n ...active.flatMap((rule) => [rule.filter, rule.strokeColor ?? strokeColor]),\n elseOutline,\n ];\n if (colorValue === strokeColor && outlineValue === strokeColor) {\n return strokeColor;\n }\n return [\"case\", [\"==\", [\"geometry-type\"], \"Polygon\"], outlineValue, colorValue];\n }) as VectorColorValue;\n}\n\n/**\n * Resolves the 3D-extrusion height (the roof's altitude) for a layer style\n * into a MapLibre value: a plain meters number, or a data-driven expression.\n * With a positive `extrusionFloorHeight` the roof sits that far above the\n * base (`extrusionBaseValue`, clamped to the ground as MapLibre clamps\n * `fill-extrusion-base`), whatever the height settings say. Otherwise, in\n * advanced mode a valid `extrusionHeightExpression` wins; else the height is\n * the chosen property scaled by `extrusionHeightScale` (`[\"*\", [\"to-number\",\n * [\"get\", prop], 0], scale]`), or a flat `0` when no property is set (so the\n * layer renders flat rather than erroring). Shared by the map's\n * fill-extrusion paint and the Add Vector Layer control's extrusion mapping\n * so both extrude identically.\n *\n * @param style - The layer style.\n * @returns The extrusion height as a number or a MapLibre expression array.\n */\nexport function extrusionHeightValue(style: LayerStyle): number | unknown[] {\n const floorHeight = extrusionFloorHeightValue(style);\n if (floorHeight > 0) {\n const base = extrusionBaseValue(style);\n return typeof base === \"number\"\n ? Math.max(0, base) + floorHeight\n : [\"+\", [\"max\", 0, base], floorHeight];\n }\n const advancedExpression = parseJsonExpression(\n styleValue(style, \"extrusionAdvancedStyleEnabled\")\n ? styleValue(style, \"extrusionHeightExpression\")\n : \"\",\n );\n if (advancedExpression) return advancedExpression;\n const property = styleValue(style, \"extrusionHeightProperty\").trim();\n if (!property) return 0;\n const scale = styleValue(style, \"extrusionHeightScale\");\n return [\"*\", [\"to-number\", [\"get\", property], 0], scale];\n}\n\n/**\n * The layer's floor height in metres: a finite `extrusionFloorHeight` above\n * 0, else 0 (the height is then the roof's altitude). Also 0 for a project\n * saved before the field existed.\n *\n * @param style - The layer style.\n * @returns The floor height, or 0 when it is off.\n */\nexport function extrusionFloorHeightValue(style: LayerStyle): number {\n const value = styleValue(style, \"extrusionFloorHeight\");\n return typeof value === \"number\" && Number.isFinite(value) && value > 0 ? value : 0;\n}\n\n/**\n * Resolves the 3D-extrusion base for a layer style into a MapLibre value: a\n * plain meters number, or a data-driven expression. In advanced mode a valid\n * `extrusionBaseExpression` wins; otherwise, with `extrusionBaseProperty` set,\n * the base is `extrusionBase` plus the property scaled by `extrusionBaseScale`\n * (so floors stack from a `level` field), else the flat `extrusionBase`. Like\n * {@link extrusionHeightValue} it is an altitude: MapLibre draws a feature\n * only where its height is above its base.\n *\n * @param style - The layer style.\n * @returns The extrusion base as a number or a MapLibre expression array.\n */\nexport function extrusionBaseValue(style: LayerStyle): number | unknown[] {\n const advancedExpression = parseJsonExpression(\n styleValue(style, \"extrusionAdvancedStyleEnabled\")\n ? styleValue(style, \"extrusionBaseExpression\")\n : \"\",\n );\n if (advancedExpression) return advancedExpression;\n const rawOffset = styleValue(style, \"extrusionBase\");\n const offset = Number.isFinite(rawOffset) ? rawOffset : 0;\n const property = styleValue(style, \"extrusionBaseProperty\").trim();\n if (!property) return offset;\n const scale = styleValue(style, \"extrusionBaseScale\");\n const scaled = [\"*\", [\"to-number\", [\"get\", property], 0], Number.isFinite(scale) ? scale : 1];\n return offset === 0 ? scaled : [\"+\", scaled, offset];\n}\n\n/**\n * Resolves the 3D-extrusion color for a layer style: a data-driven expression\n * when the layer's symbology mode produces one (categorized/graduated/rule/\n * expression) or an advanced `extrusionColorExpression` is set, otherwise the\n * flat `extrusionColor`. Mirrors the fill-color contract so an extruded layer\n * honors the same attribute-driven styling.\n *\n * @param style - The layer style.\n * @returns A flat color string or a MapLibre color expression array.\n */\nexport function extrusionColorValue(style: LayerStyle): VectorColorValue {\n const flat = styleValue(style, \"extrusionColor\");\n const vectorExpression = vectorColorExpression(style, flat);\n if (vectorExpression !== flat) return vectorExpression;\n const advancedExpression = parseJsonExpression(\n styleValue(style, \"extrusionAdvancedStyleEnabled\")\n ? styleValue(style, \"extrusionColorExpression\")\n : \"\",\n );\n return (advancedExpression as VectorColorValue | null) ?? flat;\n}\n","/**\n * Helpers for a layer's user-authored catalog metadata\n * ({@link LayerDescriptiveMetadata}, issue #2858): normalization for the\n * project file, field validation for the Metadata dialog, and the STAC 1.0\n * Item export.\n */\nimport type {\n GeoLibreLayer,\n LayerDescriptiveMetadata,\n LayerMetadataContact,\n LayerMetadataLink,\n LayerMetadataTemporalExtent,\n} from \"./types\";\n\n/** STAC specification version the Item export targets. */\nexport const STAC_VERSION = \"1.0.0\";\n\n/** Schema URL of the STAC processing extension, declared for `processing:lineage`. */\nexport const STAC_PROCESSING_EXTENSION =\n \"https://stac-extensions.github.io/processing/v1.1.0/schema.json\";\n\n/** URL schemes a metadata link (or asset) may use. */\nconst ALLOWED_LINK_PROTOCOLS = new Set([\"http:\", \"https:\", \"ftp:\", \"s3:\", \"gs:\", \"mailto:\"]);\n\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\nconst DATE_ONLY_PATTERN = /^(\\d{4})-(\\d{2})-(\\d{2})$/;\nconst DATE_TIME_PATTERN =\n /^(\\d{4})-(\\d{2})-(\\d{2})[T ](\\d{2}):(\\d{2})(?::(\\d{2})(?:\\.\\d+)?)?(Z|[+-]\\d{2}:?\\d{2})?$/i;\n/** The STAC 1.0 `license` pattern (an SPDX identifier, `various` or `proprietary`). */\nconst STAC_LICENSE_PATTERN = /^[\\w\\-.+]+$/;\n\n/**\n * Trim a value to a non-empty string, or `undefined`.\n *\n * @param value - Any value.\n * @returns The trimmed string, or `undefined` for a non-string or blank value.\n */\nfunction cleanString(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed === \"\" ? undefined : trimmed;\n}\n\n/**\n * Split comma-separated keyword text into a clean keyword list.\n *\n * @param text - Text such as `\"roads, transport, osm\"`.\n * @returns The trimmed, non-empty keywords with case-insensitive duplicates removed.\n */\nexport function parseMetadataKeywords(text: string): string[] {\n return normalizeKeywords(text.split(\",\"));\n}\n\nfunction normalizeKeywords(value: unknown): string[] {\n if (!Array.isArray(value)) return [];\n const seen = new Set<string>();\n const keywords: string[] = [];\n for (const entry of value) {\n const keyword = cleanString(entry);\n if (!keyword) continue;\n const key = keyword.toLowerCase();\n if (seen.has(key)) continue;\n seen.add(key);\n keywords.push(keyword);\n }\n return keywords;\n}\n\nfunction normalizeContact(value: unknown): LayerMetadataContact | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const raw = value as Record<string, unknown>;\n const contact: LayerMetadataContact = {};\n const name = cleanString(raw.name);\n const email = cleanString(raw.email);\n const organization = cleanString(raw.organization);\n if (name) contact.name = name;\n if (email) contact.email = email;\n if (organization) contact.organization = organization;\n return Object.keys(contact).length > 0 ? contact : undefined;\n}\n\nfunction normalizeTemporalExtent(value: unknown): LayerMetadataTemporalExtent | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const raw = value as Record<string, unknown>;\n const extent: LayerMetadataTemporalExtent = {};\n const start = cleanString(raw.start);\n const end = cleanString(raw.end);\n if (start) extent.start = start;\n if (end) extent.end = end;\n return Object.keys(extent).length > 0 ? extent : undefined;\n}\n\nfunction normalizeLinks(value: unknown): LayerMetadataLink[] {\n if (!Array.isArray(value)) return [];\n const links: LayerMetadataLink[] = [];\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) continue;\n const raw = entry as Record<string, unknown>;\n const href = cleanString(raw.href);\n // A link is its address: a row with only a label is an unfinished edit.\n if (!href) continue;\n const link: LayerMetadataLink = { href };\n const rel = cleanString(raw.rel);\n const title = cleanString(raw.title);\n if (rel) link.rel = rel;\n if (title) link.title = title;\n links.push(link);\n }\n return links;\n}\n\n/**\n * Normalize a raw descriptive-metadata value (from a project file, a plugin or\n * the Metadata dialog): keep only string fields, trim them, and drop empty\n * fields, empty keywords and links without an address.\n *\n * Values are not validated here — a malformed email or date in a hand-edited\n * file is kept so the dialog can show and fix it rather than silently lose it.\n *\n * @param value - Any value.\n * @returns The cleaned record, or `undefined` when nothing is left, so an\n * empty record is never written to a project.\n */\nexport function normalizeLayerDescriptiveMetadata(\n value: unknown,\n): LayerDescriptiveMetadata | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const raw = value as Record<string, unknown>;\n const metadata: LayerDescriptiveMetadata = {};\n for (const key of [\"title\", \"abstract\", \"license\", \"attribution\", \"lineage\"] as const) {\n const text = cleanString(raw[key]);\n if (text) metadata[key] = text;\n }\n const keywords = normalizeKeywords(raw.keywords);\n if (keywords.length > 0) metadata.keywords = keywords;\n const contact = normalizeContact(raw.contact);\n if (contact) metadata.contact = contact;\n const temporalExtent = normalizeTemporalExtent(raw.temporalExtent);\n if (temporalExtent) metadata.temporalExtent = temporalExtent;\n const links = normalizeLinks(raw.links);\n if (links.length > 0) metadata.links = links;\n return Object.keys(metadata).length > 0 ? metadata : undefined;\n}\n\n/**\n * Whether a string is a plausible email address (`local@domain.tld`).\n *\n * @param value - The candidate address.\n * @returns `true` when it has the shape of an email address.\n */\nexport function isValidMetadataEmail(value: string): boolean {\n return EMAIL_PATTERN.test(value.trim());\n}\n\n/**\n * Whether a string is an absolute URL with a scheme a metadata link may use\n * (http, https, ftp, s3, gs, mailto). Script and data URLs are rejected.\n *\n * @param value - The candidate URL.\n * @returns `true` when it parses as an allowed absolute URL.\n */\nexport function isValidMetadataUrl(value: string): boolean {\n try {\n const url = new URL(value.trim());\n return ALLOWED_LINK_PROTOCOLS.has(url.protocol);\n } catch {\n return false;\n }\n}\n\n/**\n * Whether the calendar fields name a real day (rejecting e.g. February 30).\n *\n * @param year - Four-digit year.\n * @param month - Month, 1-12.\n * @param day - Day of month.\n * @returns `true` when the date exists.\n */\nfunction isRealDate(year: number, month: number, day: number): boolean {\n // setUTCFullYear, not Date.UTC: the latter maps years 0-99 to 1900-1999.\n const date = new Date(0);\n date.setUTCFullYear(year, month - 1, day);\n return (\n date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day\n );\n}\n\n/**\n * Whether a string is an ISO 8601 date (`YYYY-MM-DD`) or date-time\n * (`YYYY-MM-DDTHH:MM[:SS[.fff]][Z|±HH:MM]`) naming a real instant.\n *\n * @param value - The candidate date.\n * @returns `true` when it is a valid ISO 8601 date or date-time.\n */\nexport function isValidMetadataDate(value: string): boolean {\n const text = value.trim();\n const dateOnly = DATE_ONLY_PATTERN.exec(text);\n if (dateOnly) return isRealDate(Number(dateOnly[1]), Number(dateOnly[2]), Number(dateOnly[3]));\n const dateTime = DATE_TIME_PATTERN.exec(text);\n if (!dateTime) return false;\n if (!isRealDate(Number(dateTime[1]), Number(dateTime[2]), Number(dateTime[3]))) return false;\n const hours = Number(dateTime[4]);\n const minutes = Number(dateTime[5]);\n const seconds = dateTime[6] === undefined ? 0 : Number(dateTime[6]);\n return hours < 24 && minutes < 60 && seconds < 60;\n}\n\n/**\n * Convert a validated metadata date to an RFC 3339 UTC date-time, as STAC\n * requires. A date-only bound covers its whole day: a start becomes the first\n * second of the day, an end the last. A date-time without an offset is read\n * as UTC so the export does not depend on the exporting machine's time zone.\n *\n * @param value - A date accepted by {@link isValidMetadataDate}.\n * @param bound - Whether the value starts or ends an interval.\n * @returns The RFC 3339 date-time, or `null` for an invalid date.\n */\nexport function metadataDateToRfc3339(value: string, bound: \"start\" | \"end\"): string | null {\n const text = value.trim();\n if (!isValidMetadataDate(text)) return null;\n if (DATE_ONLY_PATTERN.test(text)) {\n return `${text}T${bound === \"start\" ? \"00:00:00\" : \"23:59:59\"}Z`;\n }\n // A compact `+HHMM` offset passes validation but is not an ECMAScript\n // date-time format, so spell it `+HH:MM` before parsing. The pattern is\n // case-insensitive, but ECMAScript only promises to read an upper-case `T`\n // and `Z`, so upper-case the text first.\n const normalized = text\n .toUpperCase()\n .replace(\" \", \"T\")\n .replace(/([+-]\\d{2})(\\d{2})$/, \"$1:$2\");\n const hasOffset = /(Z|[+-]\\d{2}:\\d{2})$/.test(normalized);\n const parsed = new Date(hasOffset ? normalized : `${normalized}Z`);\n return Number.isNaN(parsed.getTime()) ? null : parsed.toISOString();\n}\n\n/** Why a metadata field failed {@link validateLayerDescriptiveMetadata}. */\nexport type LayerMetadataIssueCode = \"email\" | \"url\" | \"date\" | \"dateOrder\";\n\n/** One validation failure, addressed by a dotted field path. */\nexport interface LayerMetadataIssue {\n /**\n * The failing field: `contact.email`, `temporalExtent.start`,\n * `temporalExtent.end`, or `links.<index>.href`.\n */\n field: string;\n code: LayerMetadataIssueCode;\n}\n\n/**\n * Validate the fields of a descriptive-metadata record that have a format:\n * the contact email, the temporal-extent dates (and their order), and the\n * link URLs. Free-text fields are not checked.\n *\n * @param metadata - The record to check (normalized or a dialog draft).\n * @returns Every failure found; empty when the record is valid.\n */\nexport function validateLayerDescriptiveMetadata(\n metadata: LayerDescriptiveMetadata | undefined,\n): LayerMetadataIssue[] {\n const issues: LayerMetadataIssue[] = [];\n if (!metadata) return issues;\n const email = cleanString(metadata.contact?.email);\n if (email && !isValidMetadataEmail(email)) issues.push({ field: \"contact.email\", code: \"email\" });\n const start = cleanString(metadata.temporalExtent?.start);\n const end = cleanString(metadata.temporalExtent?.end);\n const startValid = start ? isValidMetadataDate(start) : true;\n const endValid = end ? isValidMetadataDate(end) : true;\n if (!startValid) issues.push({ field: \"temporalExtent.start\", code: \"date\" });\n if (!endValid) issues.push({ field: \"temporalExtent.end\", code: \"date\" });\n if (start && end && startValid && endValid) {\n const startTime = Date.parse(metadataDateToRfc3339(start, \"start\") ?? \"\");\n const endTime = Date.parse(metadataDateToRfc3339(end, \"end\") ?? \"\");\n if (startTime > endTime) issues.push({ field: \"temporalExtent.end\", code: \"dateOrder\" });\n }\n (metadata.links ?? []).forEach((link, index) => {\n const href = cleanString(link?.href);\n if (href && !isValidMetadataUrl(href)) {\n issues.push({ field: `links.${index}.href`, code: \"url\" });\n }\n });\n return issues;\n}\n\n/** A STAC 1.0 link object. */\nexport interface StacLink {\n href: string;\n rel: string;\n type?: string;\n title?: string;\n}\n\n/** A STAC 1.0 asset object. */\nexport interface StacAsset {\n href: string;\n type?: string;\n title?: string;\n roles?: string[];\n}\n\n/** A STAC 1.0 provider object. */\nexport interface StacProvider {\n name: string;\n description?: string;\n roles?: string[];\n url?: string;\n}\n\n/** The STAC 1.0 Item {@link buildLayerStacItem} produces. */\nexport interface StacItem {\n type: \"Feature\";\n stac_version: string;\n stac_extensions: string[];\n id: string;\n geometry: Record<string, unknown> | null;\n bbox?: [number, number, number, number];\n properties: Record<string, unknown> & { datetime: string | null };\n links: StacLink[];\n assets: Record<string, StacAsset>;\n}\n\n/** Options for {@link buildLayerStacItem}. */\nexport interface LayerStacItemOptions {\n /** The layer's extent in EPSG:4326 as `[west, south, east, north]`. */\n bbox?: readonly number[] | null;\n /** Where the layer's data can be fetched from, when it has a source URL. */\n assetHref?: string | null;\n /** Clock used for `datetime` when no temporal extent is recorded. */\n now?: Date;\n}\n\n/**\n * Validate a WGS84 bounding box.\n *\n * @param bbox - Candidate `[west, south, east, north]`.\n * @returns The box, or `null` when it is not four finite in-range numbers.\n */\nfunction validBbox(\n bbox: readonly number[] | null | undefined,\n): [number, number, number, number] | null {\n if (!bbox || bbox.length !== 4) return null;\n if (!bbox.every((value) => typeof value === \"number\" && Number.isFinite(value))) return null;\n const [west, south, east, north] = bbox;\n if (south < -90 || north > 90 || south > north) return null;\n if (west < -180 || west > 180 || east < -180 || east > 180) return null;\n return [west, south, east, north];\n}\n\n/**\n * The GeoJSON geometry covering a bounding box: a Point for a degenerate box,\n * a MultiPolygon split at the antimeridian when `west > east` (the STAC\n * convention for a box crossing it), otherwise a Polygon.\n *\n * @param bbox - A box accepted by {@link validBbox}.\n * @returns The footprint geometry.\n */\nfunction bboxGeometry([west, south, east, north]: [number, number, number, number]): Record<\n string,\n unknown\n> {\n if (west === east && south === north) return { type: \"Point\", coordinates: [west, south] };\n // Degenerate on one axis only (e.g. points sharing one longitude): a line,\n // not a zero-area polygon.\n if (west === east || south === north) {\n return {\n type: \"LineString\",\n coordinates: [\n [west, south],\n [east, north],\n ],\n };\n }\n const ring = (w: number, e: number) => [\n [w, south],\n [e, south],\n [e, north],\n [w, north],\n [w, south],\n ];\n if (west > east) {\n return { type: \"MultiPolygon\", coordinates: [[ring(west, 180)], [ring(-180, east)]] };\n }\n return { type: \"Polygon\", coordinates: [ring(west, east)] };\n}\n\n/** Media types by data-file extension, for the Item's data asset. */\nconst ASSET_MEDIA_TYPES: Record<string, string> = {\n geojson: \"application/geo+json\",\n json: \"application/json\",\n tif: \"image/tiff; application=geotiff\",\n tiff: \"image/tiff; application=geotiff\",\n parquet: \"application/vnd.apache.parquet\",\n geoparquet: \"application/vnd.apache.parquet\",\n fgb: \"application/vnd.flatgeobuf\",\n pmtiles: \"application/vnd.pmtiles\",\n gpkg: \"application/geopackage+sqlite3\",\n kml: \"application/vnd.google-earth.kml+xml\",\n kmz: \"application/vnd.google-earth.kmz\",\n csv: \"text/csv\",\n zip: \"application/zip\",\n las: \"application/vnd.las\",\n laz: \"application/vnd.laszip\",\n zarr: \"application/vnd+zarr\",\n};\n\n/**\n * Guess the media type of a layer's data asset from the URL's extension, or\n * the layer type when the URL has none (a COG is flagged cloud-optimized).\n *\n * @param href - The asset URL.\n * @param layerType - The layer's type.\n * @returns The media type, or `undefined` when it cannot be told.\n */\nexport function stacAssetMediaType(\n href: string,\n layerType: GeoLibreLayer[\"type\"],\n): string | undefined {\n let path = href;\n try {\n path = new URL(href).pathname;\n } catch {\n // Not an absolute URL: read the extension off the raw text.\n }\n const extension = /\\.([a-z0-9]+)$/i.exec(path.replace(/\\/+$/, \"\"))?.[1]?.toLowerCase();\n if (layerType === \"cog\" && (extension === \"tif\" || extension === \"tiff\" || !extension)) {\n return \"image/tiff; application=geotiff; profile=cloud-optimized\";\n }\n if (extension && ASSET_MEDIA_TYPES[extension]) return ASSET_MEDIA_TYPES[extension];\n if (layerType === \"geojson\" && !extension) return \"application/geo+json\";\n return undefined;\n}\n\n/**\n * Build a STAC 1.0 Item describing a layer from its descriptive metadata.\n *\n * Mapping: `id` is the layer id; `geometry`/`bbox` come from the WGS84 extent\n * (a `null` geometry, as STAC allows, when the extent is unknown); `title`\n * falls back to the layer name; `abstract` becomes `description`; the contact\n * becomes a `producer` provider; `lineage` becomes `processing:lineage` (with\n * the processing extension declared); the temporal extent becomes `datetime`\n * or `start_datetime`/`end_datetime` (with `datetime` set to the export time\n * when none is recorded, since STAC requires one); links keep their `rel`\n * (default `related`); and a source URL becomes the `data` asset.\n *\n * A license that is not an SPDX-style identifier (STAC 1.0's pattern) is\n * exported as `proprietary`, with the original text kept in\n * `geolibre:license`; the attribution is kept in `geolibre:attribution`.\n *\n * @param layer - The layer to describe.\n * @param options - Extent, asset URL and clock; see {@link LayerStacItemOptions}.\n * @returns The STAC Item, ready to `JSON.stringify`.\n */\nexport function buildLayerStacItem(\n layer: Pick<GeoLibreLayer, \"id\" | \"name\" | \"type\" | \"descriptiveMetadata\">,\n options: LayerStacItemOptions = {},\n): StacItem {\n const metadata = normalizeLayerDescriptiveMetadata(layer.descriptiveMetadata) ?? {};\n const now = options.now ?? new Date();\n const properties: StacItem[\"properties\"] = { datetime: null };\n properties.title = metadata.title ?? layer.name;\n if (metadata.abstract) properties.description = metadata.abstract;\n if (metadata.keywords?.length) properties.keywords = [...metadata.keywords];\n\n const start = metadata.temporalExtent?.start\n ? metadataDateToRfc3339(metadata.temporalExtent.start, \"start\")\n : null;\n const end = metadata.temporalExtent?.end\n ? metadataDateToRfc3339(metadata.temporalExtent.end, \"end\")\n : null;\n // Compared as instants: the two bounds may be spelled differently\n // (`…T00:00:00Z` from a date, `…T00:00:00.000Z` from a date-time).\n if (start && end && Date.parse(start) !== Date.parse(end)) {\n properties.start_datetime = start;\n properties.end_datetime = end;\n } else {\n // STAC needs a datetime unless both interval bounds are known: an instant\n // or a one-sided interval exports its known bound, and a layer with no\n // temporal extent is stamped with the export time.\n properties.datetime = start ?? end ?? now.toISOString();\n }\n\n if (metadata.license) {\n if (STAC_LICENSE_PATTERN.test(metadata.license)) {\n properties.license = metadata.license;\n } else {\n properties.license = \"proprietary\";\n properties[\"geolibre:license\"] = metadata.license;\n }\n }\n const contact = metadata.contact;\n const providerName = contact?.organization ?? contact?.name ?? contact?.email;\n if (contact && providerName) {\n const person = [\n contact.organization && contact.name ? contact.name : undefined,\n contact.email,\n ].filter(Boolean);\n const provider: StacProvider = { name: providerName, roles: [\"producer\"] };\n if (person.length > 0) provider.description = `Contact: ${person.join(\", \")}`;\n properties.providers = [provider];\n }\n if (metadata.attribution) properties[\"geolibre:attribution\"] = metadata.attribution;\n\n const extensions: string[] = [];\n if (metadata.lineage) {\n properties[\"processing:lineage\"] = metadata.lineage;\n extensions.push(STAC_PROCESSING_EXTENSION);\n }\n\n const links: StacLink[] = (metadata.links ?? [])\n .filter((link) => isValidMetadataUrl(link.href))\n .map((link) => ({\n href: link.href,\n rel: link.rel ?? \"related\",\n ...(link.title ? { title: link.title } : {}),\n }));\n\n const assets: Record<string, StacAsset> = {};\n const assetHref = cleanString(options.assetHref);\n if (assetHref) {\n // A tile template (`…/{z}/{x}/{y}.png`) cannot be fetched as-is, so it\n // is published as a `tiles` asset rather than a dereferenceable `data` one.\n const templated = /\\{[a-z]+\\}/i.test(assetHref);\n const type = templated ? undefined : stacAssetMediaType(assetHref, layer.type);\n assets[templated ? \"tiles\" : \"data\"] = {\n href: assetHref,\n ...(type ? { type } : {}),\n title: properties.title as string,\n roles: [templated ? \"tiles\" : \"data\"],\n };\n }\n\n const bbox = validBbox(options.bbox);\n return {\n type: \"Feature\",\n stac_version: STAC_VERSION,\n stac_extensions: extensions,\n id: layer.id,\n geometry: bbox ? bboxGeometry(bbox) : null,\n ...(bbox ? { bbox } : {}),\n properties,\n links,\n assets,\n };\n}\n","import type { Feature, GeoJsonProperties } from \"geojson\";\nimport {\n compileFeatureExpression,\n formatExpressionPreviewValue,\n isStyleSpecColor,\n} from \"./expressions\";\nimport type { LayerVirtualField } from \"./types\";\n\n/**\n * Virtual fields (QGIS Field Calculator → \"Create virtual field\", issue\n * #1321): expression-backed columns that recompute live instead of being\n * written once as static values.\n *\n * The engine mirrors the persistent-joins engine (`joins.ts`): computed\n * values are materialized into feature properties, so every consumer of\n * attributes — the attribute table, Expression Builder, data-driven styling,\n * labels, selection, export — sees the column with no further wiring.\n * Idempotency uses the same bookkeeping: each applied field records the\n * column it actually added (`addedField`), applying always strips those\n * first, and an existing column always wins a name collision (the virtual\n * field is skipped entirely), so stripping exactly restores the pre-apply\n * properties.\n *\n * Expressions are declarative MapLibre expressions compiled through the\n * style spec — never arbitrary code — so persisting them in `.geolibre.json`\n * and re-evaluating them on project load is safe (unlike the attribute\n * table's Field Calculator, whose JavaScript evaluator must never run\n * persisted expressions). Fields apply in list order and each sees the\n * columns materialized by earlier fields, so a virtual field can build on\n * another. Zoom-dependent expressions evaluate at zoom 0 and `@` variables\n * are not substituted: a virtual field is a property of the data, not of the\n * current view.\n *\n * Known trade-off, shared with the joins engine (see `joins.ts`): when a\n * layer's data is replaced wholesale, the replacement is stripped with the\n * *previous* bookkeeping. That is required because a replacement can also be\n * a write-back of the derived output (the attribute table round-trips\n * `layer.geojson`), which must not freeze a stale computed value into base\n * data — but it means a replacement dataset's own column whose name matches a\n * previously-materialized virtual column is replaced by the expression's\n * value for that column. Renaming or removing the virtual field first keeps\n * such collisions from arising.\n */\n\n/**\n * Remove every column previously added by `fields` (per their `addedField`\n * bookkeeping) from a copy of `features`, restoring the pre-apply properties.\n * Features without any tracked column are returned unchanged (same\n * reference).\n */\nexport function stripVirtualFieldColumns(\n features: Feature[],\n fields: LayerVirtualField[] | undefined,\n): Feature[] {\n const tracked = new Set<string>();\n for (const field of fields ?? []) {\n if (field.addedField) tracked.add(field.addedField);\n }\n if (tracked.size === 0) return features;\n return features.map((feature) => {\n const props = feature.properties;\n if (!props) return feature;\n let hasTracked = false;\n for (const key of tracked) {\n if (key in props) {\n hasTracked = true;\n break;\n }\n }\n if (!hasTracked) return feature;\n const next: GeoJsonProperties = {};\n for (const [key, value] of Object.entries(props)) {\n if (!tracked.has(key)) next[key] = value;\n }\n return { ...feature, properties: next };\n });\n}\n\n/** Result of {@link applyLayerVirtualFields}: features plus refreshed bookkeeping. */\nexport interface ApplyVirtualFieldsResult {\n features: Feature[];\n /** Input fields with `addedField`/`error`/`errorCount` recomputed for this run. */\n fields: LayerVirtualField[];\n}\n\n/**\n * Normalize an evaluated expression value into a property value: `undefined`\n * and non-finite numbers become null (a clean empty cell), style-spec Color\n * instances become their rgba() string (a Color object would neither\n * serialize nor display usefully), everything else passes through.\n */\nfunction normalizeComputedValue(value: unknown): unknown {\n if (value === undefined) return null;\n if (typeof value === \"number\" && !Number.isFinite(value)) return null;\n if (isStyleSpecColor(value)) return formatExpressionPreviewValue(value);\n return value;\n}\n\n/**\n * Apply `fields` in order to base (already-stripped) `features`, returning\n * new feature objects with each computed column merged into their properties.\n *\n * Semantics per field: a disabled field, an empty/colliding output name, or\n * an expression that fails to compile contributes nothing (with the compile\n * error recorded on the field); a runtime evaluation failure nulls that one\n * feature's cell and increments the field's `errorCount` instead of aborting\n * the column. Fields evaluate sequentially, so a later field's expression\n * can `[\"get\", ...]` an earlier field's column.\n */\nexport function applyLayerVirtualFields(\n features: Feature[],\n fields: LayerVirtualField[] | undefined,\n): ApplyVirtualFieldsResult {\n const fieldList = fields ?? [];\n const clearBookkeeping = (field: LayerVirtualField): LayerVirtualField => ({\n ...field,\n addedField: undefined,\n error: undefined,\n errorCount: undefined,\n });\n // Only bail out when no field is active. A zero-feature layer still runs\n // the per-field pass below: compiling validates the expression (so a real\n // compile error is surfaced, not swallowed) and a free name is still\n // recorded as `addedField` — otherwise an empty dataset would be\n // indistinguishable from a name collision in the UI.\n const active = fieldList.filter((field) => field.enabled !== false);\n if (active.length === 0) {\n return { features, fields: fieldList.map(clearBookkeeping) };\n }\n\n // Existing columns always win a name collision: a virtual field never\n // shadows a base column, a joined column, or an earlier virtual field.\n const usedNames = new Set<string>();\n for (const feature of features) {\n for (const key of Object.keys(feature.properties ?? {})) usedNames.add(key);\n }\n\n // One clone up front; per-field application then mutates our own copies.\n const out = features.map((feature) => ({\n ...feature,\n properties: { ...(feature.properties ?? {}) },\n }));\n\n const outFields = fieldList.map((field): LayerVirtualField => {\n if (field.enabled === false) return clearBookkeeping(field);\n const name = field.name.trim();\n if (!name || usedNames.has(name)) return clearBookkeeping(field);\n\n const compiled = compileFeatureExpression(field.expression);\n if (!compiled.ok || !compiled.evaluate) {\n return {\n ...clearBookkeeping(field),\n error: compiled.errors[0] ?? \"Invalid expression\",\n };\n }\n\n usedNames.add(name);\n let errorCount = 0;\n for (const feature of out) {\n let value: unknown = null;\n try {\n value = normalizeComputedValue(compiled.evaluate(feature));\n } catch {\n errorCount += 1;\n }\n feature.properties[name] = value;\n }\n return {\n ...clearBookkeeping(field),\n addedField: name,\n ...(errorCount > 0 ? { errorCount } : {}),\n };\n });\n\n return { features: out, fields: outFields };\n}\n","import type { Feature, FeatureCollection, GeoJsonProperties } from \"geojson\";\nimport type { GeoLibreLayer, LayerJoin, LayerVirtualField } from \"./types\";\nimport { applyLayerVirtualFields, stripVirtualFieldColumns } from \"./virtual-fields\";\n\n/**\n * Persistent attribute joins (QGIS Layer Properties → Joins), issue #1315.\n *\n * A layer's `joins` are live left joins that materialize columns from another\n * layer's attribute table into this layer's feature properties. Materializing\n * (rather than resolving lazily) means every consumer of attributes — the\n * attribute table, Expression Builder, data-driven styling, labels, diagrams,\n * export — sees the joined columns with no further wiring.\n *\n * Idempotency without a duplicate base copy: each applied join records the\n * output column names it added (`addedFields`), and applying joins always\n * strips those first. Base columns win every name collision (a joined column\n * whose output name already exists is skipped entirely), so stripping the\n * added columns exactly restores the pre-join properties.\n *\n * Known trade-off of that bookkeeping: when a layer's data is replaced\n * wholesale (file reload), the replacement is stripped with the *previous*\n * `addedFields`. That is required because a replacement can also be a\n * write-back of the joined output (the attribute table round-trips\n * `layer.geojson`), which must not freeze stale joined values into base data —\n * but it means a reloaded base column whose name collides with a\n * previously-joined output column is replaced by the join's value for that\n * column. A field-name prefix (the QGIS convention the UI suggests) keeps\n * such collisions from arising.\n *\n * The data is already in memory as JS feature objects, so the join is a plain\n * hash join here rather than the DuckDB-WASM SQL statement sketched in the\n * issue — the result is identical for equality keys and the engine stays\n * synchronous and dependency-free. Key semantics deliberately mirror the\n * Processing → Vector attribute join (and the sidecar's `_attribute_join_key`):\n * empty values never match, and non-empty values compare stringified.\n */\n\n/** Canonical JSON for object values (sorted keys), matching the processing engine. */\nfunction stableStringify(value: unknown): string {\n if (Array.isArray(value)) {\n return `[${value.map(stableStringify).join(\",\")}]`;\n }\n if (value !== null && typeof value === \"object\") {\n const entries = Object.entries(value as Record<string, unknown>)\n .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))\n .map(([k, v]) => `${JSON.stringify(k)}:${stableStringify(v)}`);\n return `{${entries.join(\",\")}}`;\n }\n return JSON.stringify(value) ?? \"null\";\n}\n\nfunction valueToString(value: unknown): string {\n if (typeof value === \"boolean\") return value ? \"true\" : \"false\";\n if (value !== null && typeof value === \"object\") return stableStringify(value);\n return String(value);\n}\n\n/**\n * Match key for a persistent join: empty values (null/undefined/NaN/empty\n * string) never match a row, mirroring a SQL/pandas NaN join key. Non-empty\n * values are keyed by their string form, so a numeric `5` and the string `\"5\"`\n * join while a zero-padded code like `\"01001\"` only matches another `\"01001\"`.\n * Kept in sync with the Processing attribute join's `attributeJoinKey`.\n */\nexport function layerJoinKey(value: unknown): string | null {\n const isEmpty =\n value === null ||\n value === undefined ||\n (typeof value === \"number\" && Number.isNaN(value)) ||\n valueToString(value) === \"\";\n return isEmpty ? null : valueToString(value);\n}\n\n/** GeoJSON columns are own properties; inherited object members are not data. */\nfunction fieldValue(properties: GeoJsonProperties, field: string): unknown {\n return properties && Object.hasOwn(properties, field) ? properties[field] : undefined;\n}\n\n/**\n * Remove every column previously added by `joins` (per their `addedFields`\n * bookkeeping) from a copy of `features`, restoring the pre-join properties.\n * Features without any tracked column are returned unchanged (same reference).\n */\nexport function stripJoinFields(features: Feature[], joins: LayerJoin[] | undefined): Feature[] {\n const tracked = new Set<string>();\n for (const join of joins ?? []) {\n for (const field of join.addedFields ?? []) tracked.add(field);\n }\n if (tracked.size === 0) return features;\n return features.map((feature) => {\n const props = feature.properties;\n if (!props) return feature;\n let hasTracked = false;\n for (const key of tracked) {\n if (key in props) {\n hasTracked = true;\n break;\n }\n }\n if (!hasTracked) return feature;\n const next: GeoJsonProperties = {};\n for (const [key, value] of Object.entries(props)) {\n if (!tracked.has(key)) next[key] = value;\n }\n return { ...feature, properties: next };\n });\n}\n\n/** Result of {@link applyLayerJoins}: joined features plus refreshed join records. */\nexport interface ApplyLayerJoinsResult {\n features: Feature[];\n /** Input joins with `addedFields` and `stats` recomputed for this run. */\n joins: LayerJoin[];\n}\n\n/**\n * Apply `joins` in order to base (already-stripped) `features`, returning new\n * feature objects with the joined columns merged into their properties.\n *\n * Semantics per join: left join, first matching join row wins, and joined\n * columns are null-filled on unmatched features so the schema stays\n * consistent. An output name (prefix + field) that collides with an existing\n * column — from the base data or an earlier join — drops that one column;\n * the join's other columns still apply. A disabled join, or one whose source\n * cannot be resolved, contributes nothing and gets empty `addedFields` and no\n * stats.\n *\n * @param features - The layer's base features (strip previous joins first).\n * @param joins - Join definitions in application order.\n * @param resolveSource - Maps a join's `joinLayerId` to that layer's current\n * feature collection, or `undefined` when the layer is gone (or is the\n * target itself; self-joins are refused by the caller).\n */\nexport function applyLayerJoins(\n features: Feature[],\n joins: LayerJoin[] | undefined,\n resolveSource: (joinLayerId: string) => FeatureCollection | undefined,\n): ApplyLayerJoinsResult {\n const joinList = joins ?? [];\n // Resolve each source once, shared between the early-exit check and the\n // per-join application below.\n const resolved = new Map<string, FeatureCollection | undefined>();\n const resolveOnce = (joinLayerId: string): FeatureCollection | undefined => {\n if (!resolved.has(joinLayerId)) {\n resolved.set(joinLayerId, resolveSource(joinLayerId));\n }\n return resolved.get(joinLayerId);\n };\n const active = joinList.filter((join) => join.enabled !== false && resolveOnce(join.joinLayerId));\n if (active.length === 0) {\n return {\n features,\n joins: joinList.map((join) => ({\n ...join,\n addedFields: [],\n stats: undefined,\n })),\n };\n }\n\n // One clone up front; per-join application then mutates our own copies.\n const out = features.map((feature) => ({\n ...feature,\n properties: { ...(feature.properties ?? {}) },\n }));\n\n // Schema-level collision tracking: a joined column never shadows a base\n // column (or an earlier join's column) on any feature.\n const usedNames = new Set<string>();\n for (const feature of features) {\n for (const key of Object.keys(feature.properties ?? {})) usedNames.add(key);\n }\n\n const outJoins = joinList.map((join): LayerJoin => {\n const source = join.enabled === false ? undefined : resolveOnce(join.joinLayerId);\n if (!source) return { ...join, addedFields: [], stats: undefined };\n\n const joinFeatures = (source.features ?? []).filter(Boolean);\n\n // Collect join-table columns in first-seen order for a deterministic schema.\n const joinKeysOrder: string[] = [];\n const joinKeySet = new Set<string>();\n for (const jf of joinFeatures) {\n for (const key of Object.keys(jf.properties ?? {})) {\n if (!joinKeySet.has(key)) {\n joinKeySet.add(key);\n joinKeysOrder.push(key);\n }\n }\n }\n\n // An explicit `fields` array is honored verbatim (an empty subset joins no\n // columns); only an absent array means \"every field except the key\".\n const requested =\n join.fields !== undefined\n ? join.fields.filter((field) => joinKeySet.has(field))\n : joinKeysOrder.filter((key) => key !== join.joinField);\n const prefix = join.prefix ?? \"\";\n const fieldPairs: Array<[source: string, output: string]> = [];\n for (const field of requested) {\n const outputName = prefix + field;\n if (usedNames.has(outputName)) continue;\n usedNames.add(outputName);\n fieldPairs.push([field, outputName]);\n }\n\n // First-match lookup: when several join rows share a key, the first wins.\n const lookup = new Map<string, GeoJsonProperties>();\n for (const jf of joinFeatures) {\n const key = layerJoinKey(fieldValue(jf.properties, join.joinField));\n if (key === null || lookup.has(key)) continue;\n lookup.set(key, jf.properties ?? {});\n }\n\n let matched = 0;\n const targetKeys = new Set<string>();\n for (const feature of out) {\n const props = feature.properties;\n const key = layerJoinKey(fieldValue(props, join.targetField));\n if (key !== null) targetKeys.add(key);\n const row = key === null ? undefined : lookup.get(key);\n if (row) {\n matched += 1;\n for (const [field, outputName] of fieldPairs) {\n props[outputName] = fieldValue(row, field) ?? null;\n }\n } else {\n for (const [, outputName] of fieldPairs) props[outputName] = null;\n }\n }\n\n // Counts rows, not unique keys: duplicate unmatched rows each count, as\n // the stat (and the UI copy) promise.\n let unmatchedJoin = 0;\n for (const jf of joinFeatures) {\n const key = layerJoinKey(fieldValue(jf.properties, join.joinField));\n if (key !== null && !targetKeys.has(key)) unmatchedJoin += 1;\n }\n\n return {\n ...join,\n addedFields: fieldPairs.map(([, outputName]) => outputName),\n stats: {\n matchedCount: matched,\n unmatchedTargetCount: out.length - matched,\n unmatchedJoinCount: unmatchedJoin,\n },\n };\n });\n\n return { features: out, joins: outJoins };\n}\n\n/**\n * Strip-and-reapply this layer's derived columns — persistent joins, then\n * virtual fields (so an expression can read joined columns) — resolving join\n * sources from `allLayers`. Pass `nextJoins`/`nextVirtualFields` to replace\n * the definitions (the layer's current bookkeeping still drives the strip, so\n * removed definitions clean up after themselves); omit them to refresh the\n * existing definitions against current data. Layers without a feature\n * collection are returned unchanged.\n */\nexport function applyJoinsToLayer(\n layer: GeoLibreLayer,\n allLayers: GeoLibreLayer[],\n nextJoins?: LayerJoin[],\n nextVirtualFields?: LayerVirtualField[],\n): GeoLibreLayer {\n const geojson = layer.geojson;\n const joins = nextJoins ?? layer.joins ?? [];\n const virtualFields = nextVirtualFields ?? layer.virtualFields ?? [];\n if (!geojson) {\n return {\n ...layer,\n joins: joins.length > 0 ? joins : undefined,\n virtualFields: virtualFields.length > 0 ? virtualFields : undefined,\n };\n }\n // Virtual columns strip before join columns so a join output name held by a\n // stale virtual column frees up; the reverse order would also work today\n // (both strips are pure column removals), but this matches apply order.\n const base = stripJoinFields(\n stripVirtualFieldColumns(geojson.features ?? [], layer.virtualFields),\n layer.joins,\n );\n const applied = applyLayerJoins(base, joins, (joinLayerId) => {\n if (joinLayerId === layer.id) return undefined;\n return allLayers.find((candidate) => candidate.id === joinLayerId)?.geojson;\n });\n const withVirtual = applyLayerVirtualFields(applied.features, virtualFields);\n const { geoarrow: _staleGeoarrow, ...rest } = layer;\n return {\n ...rest,\n geojson: { ...geojson, features: withVirtual.features },\n joins: applied.joins.length > 0 ? applied.joins : undefined,\n virtualFields: withVirtual.fields.length > 0 ? withVirtual.fields : undefined,\n };\n}\n\n/**\n * Ids of every layer whose data (directly or transitively) feeds `layerId`'s\n * joins, following `joinLayerId` edges through each source's own joins.\n * Disabled joins count too: re-enabling one must not spring a cycle. The Joins\n * UI uses this to keep a layer that already consumes the target — however\n * indirectly — out of the join-source picker, so circular joins cannot be\n * authored (the refresh cascade still breaks them defensively for hand-edited\n * projects).\n */\nexport function collectTransitiveJoinSourceIds(\n layers: GeoLibreLayer[],\n layerId: string,\n): Set<string> {\n const byId = new Map(layers.map((layer) => [layer.id, layer]));\n const visited = new Set<string>();\n const queue = [layerId];\n while (queue.length > 0) {\n const current = byId.get(queue.shift() as string);\n for (const join of current?.joins ?? []) {\n if (visited.has(join.joinLayerId)) continue;\n visited.add(join.joinLayerId);\n queue.push(join.joinLayerId);\n }\n }\n return visited;\n}\n\n/**\n * Order `candidates` (layers with joins) so every layer comes after the\n * candidates its joins consume — Kahn's algorithm over \"join source → join\n * target\" edges within the candidate set. When a cycle blocks progress\n * (possible only in hand-edited projects; the UI refuses circular joins), an\n * actual cycle member is forced through — found by walking unmet-source edges\n * until a node repeats — so consumers *downstream* of the cycle still order\n * after it rather than falling back ahead of it.\n */\nfunction topologicalJoinOrder(candidates: GeoLibreLayer[]): string[] {\n const candidateIds = new Set(candidates.map((layer) => layer.id));\n const indegree = new Map<string, number>();\n const dependents = new Map<string, string[]>();\n const sourcesOf = new Map<string, string[]>();\n for (const layer of candidates) indegree.set(layer.id, 0);\n for (const layer of candidates) {\n for (const join of layer.joins ?? []) {\n if (\n join.enabled === false ||\n join.joinLayerId === layer.id ||\n !candidateIds.has(join.joinLayerId)\n ) {\n continue;\n }\n indegree.set(layer.id, (indegree.get(layer.id) ?? 0) + 1);\n const list = dependents.get(join.joinLayerId) ?? [];\n list.push(layer.id);\n dependents.set(join.joinLayerId, list);\n const sources = sourcesOf.get(layer.id) ?? [];\n sources.push(join.joinLayerId);\n sourcesOf.set(layer.id, sources);\n }\n }\n const order: string[] = [];\n const orderedSet = new Set<string>();\n const queue = candidates.filter((layer) => indegree.get(layer.id) === 0).map((layer) => layer.id);\n while (order.length < candidates.length) {\n if (queue.length === 0) {\n // Every unordered layer has at least one unordered source, so walking\n // unmet-source edges from any unordered layer must revisit a node, and\n // that node sits on a cycle.\n const start = candidates.find((layer) => !orderedSet.has(layer.id));\n if (!start) break;\n let cursor = start.id;\n const walked = new Set<string>();\n while (!walked.has(cursor)) {\n walked.add(cursor);\n const next = (sourcesOf.get(cursor) ?? []).find((source) => !orderedSet.has(source));\n if (next === undefined) break;\n cursor = next;\n }\n queue.push(cursor);\n }\n const id = queue.shift() as string;\n if (orderedSet.has(id)) continue;\n orderedSet.add(id);\n order.push(id);\n for (const dependent of dependents.get(id) ?? []) {\n const remaining = (indegree.get(dependent) ?? 1) - 1;\n indegree.set(dependent, remaining);\n if (remaining === 0 && !orderedSet.has(dependent)) queue.push(dependent);\n }\n }\n return order;\n}\n\n/**\n * Refresh every layer whose joins (directly or transitively) consume the\n * layer identified by `seedId`, after that layer's data changed — or after it\n * was removed (a gone source resolves to nothing, so its frozen columns strip\n * away). The affected layers re-derive in topological dependency order, so a\n * layer joining two other affected layers sees both of them fresh regardless\n * of their positions in the layer panel. The seed itself is not touched.\n */\nexport function cascadeLayerJoinRefresh(layers: GeoLibreLayer[], seedId: string): GeoLibreLayer[] {\n // Collect the transitive dependents of the seed.\n const affected = new Set<string>();\n const queue = [seedId];\n while (queue.length > 0) {\n const sourceId = queue.shift() as string;\n for (const layer of layers) {\n if (layer.id === seedId || affected.has(layer.id)) continue;\n if (layer.joins?.some((join) => join.enabled !== false && join.joinLayerId === sourceId)) {\n affected.add(layer.id);\n queue.push(layer.id);\n }\n }\n }\n if (affected.size === 0) return layers;\n\n let current = layers;\n const order = topologicalJoinOrder(layers.filter((layer) => affected.has(layer.id)));\n for (const id of order) {\n current = current.map((layer) => (layer.id === id ? applyJoinsToLayer(layer, current) : layer));\n }\n return current;\n}\n\n/**\n * Re-resolve every layer's persistent joins against the freshly loaded layer\n * set (project open). Layers re-derive in topological order of their join\n * dependencies, so a chain (`C joins A, A joins B`) resolves A before C; a\n * hand-edited cycle is broken by forcing one of its members through first\n * (see {@link topologicalJoinOrder}), so consumers downstream of the cycle\n * still re-derive after it. Layers with virtual fields but no joins re-derive\n * too (their expressions run against the freshly loaded features). Layer sets\n * without joins or virtual fields pass through by reference.\n */\nexport function reapplyLayerJoins(layers: GeoLibreLayer[]): GeoLibreLayer[] {\n const joined = layers.filter((layer) => layer.joins?.length || layer.virtualFields?.length);\n if (joined.length === 0) return layers;\n\n let current = layers;\n for (const id of topologicalJoinOrder(joined)) {\n current = current.map((layer) => (layer.id === id ? applyJoinsToLayer(layer, current) : layer));\n }\n return current;\n}\n","import type {\n AppCapabilities,\n AppPrivilege,\n AppRole,\n GeoLibreLayer,\n LayerCapabilities,\n} from \"./types\";\n\n/**\n * Default inferred capabilities for a layer based on its type and metadata.\n *\n * Inferred rules:\n * - `query`: true for all valid layers.\n * - `create`, `update`, `delete`: true for in-memory or editable GeoJSON vector layers,\n * false for derived queries (DuckDB/SQL), read-only vector tiles, and raster layers.\n * - `export`: true by default.\n */\nexport function inferLayerCapabilities(layer: GeoLibreLayer): Required<LayerCapabilities> {\n const isVector = layer.type === \"geojson\";\n const isDuckDB =\n layer.metadata?.sourceKind === \"duckdb-query\" ||\n (layer.type === \"geojson\" && typeof layer.metadata?.query === \"string\");\n const isExternalNative = layer.metadata?.externalNativeLayer === true;\n const isSketches = layer.metadata?.sourceKind === \"geoeditor-sketches\";\n const isSqlQuery = layer.metadata?.sourceKind === \"sql-query\";\n\n const isReadOnlyVector = isExternalNative && layer.metadata?.sourceKind !== \"maplibre-gl-vector\";\n const isEditable = isVector && !isDuckDB && !isReadOnlyVector && !isSketches && !isSqlQuery;\n\n return {\n query: true,\n create: isEditable,\n update: isEditable,\n delete: isEditable,\n export: true,\n };\n}\n\n/**\n * Resolves the effective capabilities for a layer by overlaying any explicit\n * capability overrides on top of inferred defaults.\n */\nexport function resolveLayerCapabilities(\n layer: GeoLibreLayer | undefined,\n): Required<LayerCapabilities> {\n if (!layer) {\n return {\n query: false,\n create: false,\n update: false,\n delete: false,\n export: false,\n };\n }\n\n const defaults = inferLayerCapabilities(layer);\n if (!layer.capabilities) {\n return defaults;\n }\n\n return {\n query: layer.capabilities.query ?? defaults.query,\n create: layer.capabilities.create ?? defaults.create,\n update: layer.capabilities.update ?? defaults.update,\n delete: layer.capabilities.delete ?? defaults.delete,\n export: layer.capabilities.export ?? defaults.export,\n };\n}\n\n/**\n * Normalizes an untrusted capabilities value from JSON/project data.\n */\nexport function normalizeLayerCapabilities(raw: unknown): LayerCapabilities | undefined {\n if (!raw || typeof raw !== \"object\" || Array.isArray(raw)) return undefined;\n const obj = raw as Record<string, unknown>;\n const caps: LayerCapabilities = {};\n let hasAny = false;\n\n if (typeof obj.query === \"boolean\") {\n caps.query = obj.query;\n hasAny = true;\n }\n if (typeof obj.create === \"boolean\") {\n caps.create = obj.create;\n hasAny = true;\n }\n if (typeof obj.update === \"boolean\") {\n caps.update = obj.update;\n hasAny = true;\n }\n if (typeof obj.delete === \"boolean\") {\n caps.delete = obj.delete;\n hasAny = true;\n }\n if (typeof obj.export === \"boolean\") {\n caps.export = obj.export;\n hasAny = true;\n }\n\n return hasAny ? caps : undefined;\n}\n\n/**\n * All supported application privilege identifiers in GeoLibre.\n */\nexport const ALL_APP_PRIVILEGES: readonly AppPrivilege[] = [\n \"layers:edit\",\n \"layers:add-remote\",\n \"layers:add-local\",\n \"processing:run\",\n \"processing:sidecar\",\n \"project:save\",\n \"project:share\",\n \"project:share-public\",\n \"plugins:install\",\n \"assistant:use\",\n \"connections:manage\",\n \"export:data\",\n \"export:image\",\n \"settings:manage\",\n] as const;\n\n/**\n * Standard privilege bundles for named application roles.\n */\nexport const ROLE_PRIVILEGES: Record<Exclude<AppRole, \"custom\">, readonly AppPrivilege[]> = {\n viewer: [\"export:image\", \"export:data\"],\n editor: [\n \"export:image\",\n \"export:data\",\n \"layers:edit\",\n \"layers:add-local\",\n \"layers:add-remote\",\n \"processing:run\",\n \"project:save\",\n ],\n publisher: [\n \"export:image\",\n \"export:data\",\n \"layers:edit\",\n \"layers:add-local\",\n \"layers:add-remote\",\n \"processing:run\",\n \"project:save\",\n \"project:share\",\n \"project:share-public\",\n \"processing:sidecar\",\n \"assistant:use\",\n ],\n administrator: ALL_APP_PRIVILEGES,\n};\n\n/**\n * Resolves the effective privilege list for a given role, applying custom overrides if specified.\n */\nexport function resolveRolePrivileges(\n role: AppRole,\n customPrivileges?: readonly AppPrivilege[],\n): AppPrivilege[] {\n if (role === \"custom\") {\n if (!customPrivileges || customPrivileges.length === 0) return [];\n const validSet = new Set<AppPrivilege>(ALL_APP_PRIVILEGES);\n return [...new Set(customPrivileges.filter((p) => validSet.has(p)))];\n }\n return [...ROLE_PRIVILEGES[role]];\n}\n\n/**\n * Intersects multiple sets of privileges to derive the effective permissions when multiple\n * policies (e.g. deployment, organization, and share link) apply simultaneously.\n */\nexport function intersectPrivileges(...privilegeSets: (readonly AppPrivilege[])[]): AppPrivilege[] {\n if (privilegeSets.length === 0) return [];\n if (privilegeSets.length === 1) return [...new Set(privilegeSets[0])];\n let current = new Set<AppPrivilege>(privilegeSets[0]);\n for (let i = 1; i < privilegeSets.length; i++) {\n const nextSet = new Set<AppPrivilege>(privilegeSets[i]);\n current = new Set([...current].filter((p) => nextSet.has(p)));\n }\n return [...current];\n}\n\n/**\n * Evaluates whether an application capability set grants a specific privilege.\n */\nexport function hasAppPrivilege(\n capabilities: AppCapabilities | undefined,\n privilege: AppPrivilege,\n): boolean {\n if (!capabilities) return true;\n return capabilities.privileges.includes(privilege);\n}\n\n/**\n * Resolves why a privilege is withheld: its own reason if it has one, otherwise\n * the reason recorded for the capability set as a whole.\n *\n * @param capabilities - The active capability set, if any.\n * @param privilege - The privilege being explained.\n * @returns The reason, or undefined when none was recorded.\n */\nexport function appPrivilegeReason(\n capabilities: AppCapabilities | undefined,\n privilege: AppPrivilege,\n): string | undefined {\n if (!capabilities) return undefined;\n return capabilities.privilegeReasons?.[privilege] ?? capabilities.reason;\n}\n\n/**\n * Creates the default unconstrained application capabilities (Administrator role).\n */\nexport function createDefaultAppCapabilities(): AppCapabilities {\n return {\n role: \"administrator\",\n privileges: [...ALL_APP_PRIVILEGES],\n };\n}\n\n/**\n * Normalizes an untrusted array of privilege strings.\n */\nexport function normalizeAppPrivileges(raw: unknown): AppPrivilege[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const validSet = new Set<string>(ALL_APP_PRIVILEGES);\n const result: AppPrivilege[] = [];\n for (const item of raw) {\n if (typeof item === \"string\" && validSet.has(item)) {\n result.push(item as AppPrivilege);\n }\n }\n return result.length > 0 ? [...new Set(result)] : [];\n}\n","import type { GeoLibreLayer, LayerGroup } from \"./types\";\n\n/** Opacity a freshly created {@link LayerGroup} starts at (fully opaque). */\nexport const DEFAULT_LAYER_GROUP_OPACITY = 1;\n\n/**\n * One row in the layer panel: either a top-level (ungrouped) layer or a group\n * header together with its child layers.\n */\nexport type LayerTreeItem =\n | { kind: \"layer\"; layer: GeoLibreLayer }\n | { kind: \"group\"; group: LayerGroup; children: GeoLibreLayer[] };\n\n/**\n * One top-level block of the layer panel, in top-of-panel-first order: either a\n * single ungrouped layer or the contiguous run of layers a group owns.\n *\n * A group that owns no layer anywhere beneath it still gets a unit, with an\n * empty `layers` array, so that empty folders have a position in the panel and\n * can be reordered like any other row.\n */\nexport type LayerPanelUnit = {\n /** Group the block belongs to, or `null` for a lone ungrouped layer. */\n groupId: string | null;\n /** Member layers, top-first. Empty for a group with no layers under it. */\n layers: GeoLibreLayer[];\n};\n\n/**\n * The group a group is really nested under: its `parentId`, or `null` when it\n * is top-level or its `parentId` points at a group that no longer exists. Every\n * placement rule reads the parent through here so a dangling reference behaves\n * like a top-level folder rather than falling into a branch of the tree that is\n * not there.\n */\nfunction effectiveParentId(\n group: LayerGroup | undefined,\n groupById: ReadonlyMap<string, LayerGroup>,\n): string | null {\n const parentId = group?.parentId;\n return parentId && groupById.has(parentId) ? parentId : null;\n}\n\n/**\n * Panel index range `[first, last]` each group spans, keyed by group id. A\n * group with no unit of its own (an organizer whose layers all live in child\n * groups) spans the union of its descendants' units, so it is absent only when\n * nothing under it has a position yet.\n */\nfunction groupUnitRanges(\n units: LayerPanelUnit[],\n groupById: ReadonlyMap<string, LayerGroup>,\n): Map<string, [number, number]> {\n const ranges = new Map<string, [number, number]>();\n units.forEach((unit, index) => {\n let id: string | null = unit.groupId;\n const visited = new Set<string>();\n while (id && !visited.has(id)) {\n visited.add(id);\n const range = ranges.get(id);\n if (range) ranges.set(id, [Math.min(range[0], index), Math.max(range[1], index)]);\n else ranges.set(id, [index, index]);\n // A dangling `parentId` ends the walk rather than recording a range for a\n // group that does not exist.\n id = effectiveParentId(groupById.get(id), groupById);\n }\n });\n return ranges;\n}\n\n/**\n * Group indices ordered so that a group's parent always comes first, keeping\n * the `groups` order otherwise. `moveLayerGroupToGroup` reparents in place\n * without touching the array, so a child can sit ahead of its own parent; a\n * parent has to be placed first for the child to have a block to sit under.\n * A `parentId` cycle never resolves, so what is left is emitted in array order\n * rather than dropped.\n */\nfunction parentsFirstOrder(\n groups: LayerGroup[],\n groupById: ReadonlyMap<string, LayerGroup>,\n): number[] {\n const order: number[] = [];\n const placed = new Set<string>();\n let pending = groups.map((_, index) => index);\n while (pending.length > 0) {\n const deferred: number[] = [];\n for (const index of pending) {\n const parentId = groups[index].parentId;\n if (parentId && groupById.has(parentId) && !placed.has(parentId)) deferred.push(index);\n else {\n order.push(index);\n placed.add(groups[index].id);\n }\n }\n if (deferred.length === pending.length) {\n order.push(...deferred);\n break;\n }\n pending = deferred;\n }\n return order;\n}\n\n/**\n * Split the panel into its top-level blocks, **top-of-panel first** (the\n * reverse of the store's render order, where the last array element draws on\n * top).\n *\n * Layers give every populated group a position of its own. A group with no\n * layers under it has none to derive, so it is slotted in next to its\n * **siblings** — the groups sharing its parent — in `groups` order: directly\n * below the nearest sibling above it in that array, else directly above the\n * nearest one below it. Because the neighbours are read from `groups` rather\n * than from the layer array, a folder keeps its place relative to its siblings\n * when one of them gains its first layer (GeoLibre#1739).\n *\n * Only a sibling can place it. An ancestor's range already contains the slot\n * being chosen, and an unrelated group's range covers a different branch of the\n * tree, so anchoring on either drops the folder into someone else's block —\n * which is how a nested folder used to land outside its own parent once a\n * cousin gained a layer (GeoLibre#1739 follow-up). With no sibling positioned\n * yet, a nested folder falls to the end of its parent's block and a top-level\n * one to the top of the panel.\n *\n * An *organizer* — a group with no layers of its own but with some beneath it —\n * gets no block here: the panel draws its header against its top-most\n * descendant layer instead. Only a group whose whole subtree is empty needs one.\n *\n * A `groupId` that does not match any group is treated as ungrouped, so a\n * dangling reference degrades gracefully instead of dropping the layer.\n *\n * @param layers Flat layer list in store (render) order.\n * @param groups Group definitions.\n * @returns Panel blocks in top-to-bottom display order.\n */\nexport function buildLayerPanelUnits(\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n): LayerPanelUnit[] {\n const groupById = new Map(groups.map((g) => [g.id, g]));\n const units: LayerPanelUnit[] = [];\n for (let i = layers.length - 1; i >= 0; i--) {\n const layer = layers[i];\n const groupId = layer.groupId && groupById.has(layer.groupId) ? layer.groupId : null;\n const last = units[units.length - 1];\n if (groupId && last && last.groupId === groupId) last.layers.push(layer);\n else units.push({ groupId, layers: [layer] });\n }\n\n let ranges = groupUnitRanges(units, groupById);\n // Every group a layer reaches — the ones that own a block and the organizers\n // spanning them — already has a position. `ranges` alone cannot stand in for\n // that set once the loop starts: splicing a folder in gives its ancestors an\n // inherited range too, and an ancestor whose subtree holds no layer still\n // needs a block of its own or it renders nowhere at all.\n const positioned = new Set(ranges.keys());\n if (positioned.size === groups.length) return units;\n for (const at of parentsFirstOrder(groups, groupById)) {\n const group = groups[at];\n if (positioned.has(group.id)) continue;\n const parentId = effectiveParentId(group, groupById);\n const isSibling = (other: LayerGroup) => effectiveParentId(other, groupById) === parentId;\n let insertAt: number | undefined;\n for (let i = at - 1; insertAt === undefined && i >= 0; i--) {\n if (!isSibling(groups[i])) continue;\n const range = ranges.get(groups[i].id);\n if (range) insertAt = range[1] + 1;\n }\n for (let i = at + 1; insertAt === undefined && i < groups.length; i++) {\n if (!isSibling(groups[i])) continue;\n const range = ranges.get(groups[i].id);\n if (range) insertAt = range[0];\n }\n // No sibling to sit beside: stay inside the parent's block rather than\n // drifting to the top of the panel past groups this one is nested in.\n if (insertAt === undefined && parentId) insertAt = (ranges.get(parentId)?.[1] ?? -1) + 1;\n units.splice(insertAt ?? 0, 0, { groupId: group.id, layers: [] });\n positioned.add(group.id);\n // Placing this folder shifts the units below it, and makes the folder\n // itself an anchor for the ones still to be placed.\n ranges = groupUnitRanges(units, groupById);\n }\n return units;\n}\n\n/**\n * Where the layer panel should draw every group header, expressed against the\n * layer rows the panel already renders.\n */\nexport type LayerPanelGroupHeaders = {\n /** Headers drawn immediately above the keyed layer's row, in panel order. */\n aboveLayer: Map<string, LayerGroup[]>;\n /** Headers drawn below every layer row, in panel order. */\n bottom: LayerGroup[];\n};\n\n/**\n * Resolve {@link buildLayerPanelUnits} into a draw position for each group\n * header, so a panel that renders row by row from the flat layer list can place\n * them without rebuilding its whole render as a tree.\n *\n * Every header comes from the one walk: the group a layer belongs to, the\n * *organizers* above it whose own layers all live in child groups, and the\n * folders holding no layer at all. Deriving them together is what keeps them in\n * order — each is emitted the first time the walk reaches its subtree, parents\n * ahead of children, so a nested folder can never be drawn above the parent it\n * sits inside (GeoLibre#1739 follow-up).\n *\n * A group is emitted once, at its top-most block, so a group whose members are\n * scattered still gets a single header.\n *\n * @param layers Flat layer list in store (render) order.\n * @param groups Group definitions.\n * @returns The anchor layer (or the panel bottom) each header sits above.\n */\nexport function layerPanelGroupHeaders(\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n): LayerPanelGroupHeaders {\n const groupById = new Map(groups.map((g) => [g.id, g]));\n const units = buildLayerPanelUnits(layers, groups);\n const aboveLayer = new Map<string, LayerGroup[]>();\n const bottom: LayerGroup[] = [];\n const drawn = new Set<string>();\n for (let i = 0; i < units.length; i++) {\n const unit = units[i];\n if (!unit.groupId) continue;\n // Walk up to the root and reverse, so an ancestor's header precedes the\n // headers of everything nested in it. A `parentId` cycle stops the walk\n // instead of hanging.\n const chain: LayerGroup[] = [];\n let id: string | null = unit.groupId;\n const visited = new Set<string>();\n while (id && !visited.has(id) && !drawn.has(id)) {\n visited.add(id);\n const group = groupById.get(id);\n if (!group) break;\n chain.unshift(group);\n id = effectiveParentId(group, groupById);\n }\n if (chain.length === 0) continue;\n // The block's own first layer anchors it; a folder with none borrows the\n // first layer below it, and falls to the panel bottom when there is none.\n let anchorId = unit.layers[0]?.id;\n for (let j = i + 1; anchorId === undefined && j < units.length; j++) {\n anchorId = units[j].layers[0]?.id;\n }\n for (const group of chain) drawn.add(group.id);\n if (anchorId === undefined) {\n bottom.push(...chain);\n continue;\n }\n const at = aboveLayer.get(anchorId);\n if (at) at.push(...chain);\n else aboveLayer.set(anchorId, chain);\n }\n return { aboveLayer, bottom };\n}\n\n/**\n * Derive the layer-panel tree from the flat `layers` array and the group\n * definitions.\n *\n * Items are returned **top-of-panel first** (the reverse of the store's\n * render order, where the last array element draws on top). Within a group the\n * children are likewise ordered top-first. Each group is emitted once, at the\n * panel position of its top-most member; a group's members are normally\n * contiguous in the array, but if they are not, every member is still gathered\n * under a single header. Groups with no members (empty folders) sit where\n * {@link buildLayerPanelUnits} places them.\n *\n * A `groupId` that does not match any group is treated as ungrouped, so a\n * dangling reference degrades gracefully instead of dropping the layer.\n *\n * @param layers Flat layer list in store (render) order.\n * @param groups Group definitions.\n * @returns Panel rows in top-to-bottom display order.\n */\nexport function buildLayerTree(layers: GeoLibreLayer[], groups: LayerGroup[]): LayerTreeItem[] {\n const groupById = new Map(groups.map((g) => [g.id, g]));\n const items: LayerTreeItem[] = [];\n const emittedAt = new Map<string, LayerTreeItem & { kind: \"group\" }>();\n\n for (const unit of buildLayerPanelUnits(layers, groups)) {\n const group = unit.groupId ? groupById.get(unit.groupId) : undefined;\n if (!group) {\n for (const layer of unit.layers) items.push({ kind: \"layer\", layer });\n continue;\n }\n // A group's members are normally one contiguous run, but a scattered group\n // still gets a single header, at its top-most member.\n const existing = emittedAt.get(group.id);\n if (existing) {\n existing.children.push(...unit.layers);\n continue;\n }\n const item = { kind: \"group\" as const, group, children: [...unit.layers] };\n emittedAt.set(group.id, item);\n items.push(item);\n }\n return items;\n}\n\n/**\n * Fold each group's visibility and opacity into its child layers so the map\n * sync can keep treating every layer independently.\n *\n * A child's effective visibility is its own `visible` ANDed with the group's,\n * and its effective opacity is its own multiplied by the group's. Layers\n * without a group (or with a dangling `groupId`) are returned unchanged, and\n * the original object reference is preserved whenever nothing changes so\n * downstream memoization stays cheap.\n *\n * @param layers Flat layer list.\n * @param groups Group definitions.\n * @returns Layers with group effects applied.\n */\nexport function applyGroupEffects(layers: GeoLibreLayer[], groups: LayerGroup[]): GeoLibreLayer[] {\n if (groups.length === 0) return layers;\n const groupById = new Map(groups.map((g) => [g.id, g]));\n return layers.map((layer) => {\n if (!layer.groupId || !groupById.has(layer.groupId)) return layer;\n const { visible, opacity } = foldGroupChain(layer, groupById);\n if (visible === layer.visible && opacity === layer.opacity) return layer;\n return { ...layer, visible, opacity };\n });\n}\n\n/**\n * Walk a layer's group chain upward, ANDing each group's visibility into the\n * layer's own and multiplying each group's opacity into it. The `visited` set\n * makes a corrupted `parentId` cycle terminate instead of hanging.\n */\nfunction foldGroupChain(\n layer: GeoLibreLayer,\n groupById: ReadonlyMap<string, LayerGroup>,\n): { visible: boolean; opacity: number } {\n let visible = layer.visible;\n let opacity = layer.opacity;\n let group = layer.groupId ? groupById.get(layer.groupId) : undefined;\n const visited = new Set<string>();\n while (group && !visited.has(group.id)) {\n visited.add(group.id);\n visible = visible && group.visible;\n opacity *= group.opacity;\n group = group.parentId ? groupById.get(group.parentId) : undefined;\n }\n return { visible, opacity };\n}\n\n/**\n * The single layer form of {@link applyGroupEffects}: the visibility and\n * opacity a layer should actually render at once its (possibly nested) parent\n * groups are folded in.\n *\n * Group state never mutates a child's own `visible`/`opacity` fields, so any\n * renderer that cannot read the folded array — a plugin control that owns its\n * own paint and is driven layer by layer — asks for a single layer here\n * instead.\n *\n * @param layer The layer to fold.\n * @param groups Group definitions, or an already-built id → group map. Callers\n * in a render path that fold many layers against the same groups (the layer\n * panel) should pass a memoized map, since the array form rebuilds one per\n * call.\n * @returns The effective render state (the layer's own values when it is\n * ungrouped or its `groupId` is dangling).\n */\nexport function effectiveLayerRenderState(\n layer: GeoLibreLayer,\n groups: LayerGroup[] | ReadonlyMap<string, LayerGroup>,\n): { visible: boolean; opacity: number } {\n const size = Array.isArray(groups) ? groups.length : groups.size;\n if (size === 0 || !layer.groupId) {\n return { visible: layer.visible, opacity: layer.opacity };\n }\n return foldGroupChain(\n layer,\n Array.isArray(groups) ? new Map(groups.map((g) => [g.id, g])) : groups,\n );\n}\n\n/**\n * Indices, in store order, of every layer that belongs to `groupId`.\n *\n * @param layers Flat layer list.\n * @param groupId Group whose members to locate.\n * @returns Ascending list of member indices (empty when the group has none).\n */\nexport function groupMemberIndices(layers: GeoLibreLayer[], groupId: string): number[] {\n const indices: number[] = [];\n for (let i = 0; i < layers.length; i++) {\n if (layers[i]?.groupId === groupId) indices.push(i);\n }\n return indices;\n}\n\n/**\n * Reorder `layers` so that every group's members form a single contiguous block,\n * preserving the relative order of layers within each group and of the\n * top-level items. The block for a group is anchored at the position of its\n * current bottom-most (first) member — the first member encountered iterating\n * from index 0, which renders at the bottom of the layer panel.\n *\n * Mutating store actions call this after assigning `groupId`s to restore the\n * contiguity invariant the rest of the system relies on.\n *\n * @param layers Flat layer list, possibly with interleaved group members.\n * @returns A new array with grouped layers made contiguous.\n */\nexport function normalizeGroupContiguity(layers: GeoLibreLayer[]): GeoLibreLayer[] {\n const result: GeoLibreLayer[] = [];\n const placed = new Set<string>();\n for (let i = 0; i < layers.length; i++) {\n const layer = layers[i];\n if (placed.has(layer.id)) continue;\n if (!layer.groupId) {\n result.push(layer);\n placed.add(layer.id);\n continue;\n }\n // Pull in every remaining member of this group, in array order, so the\n // block is anchored at the first member encountered.\n for (let j = i; j < layers.length; j++) {\n const candidate = layers[j];\n if (candidate.groupId === layer.groupId && !placed.has(candidate.id)) {\n result.push(candidate);\n placed.add(candidate.id);\n }\n }\n }\n return result;\n}\n\n/**\n * Which of `parentId`'s own blocks a panel unit belongs to, used to move a group\n * only among the blocks it shares a parent with:\n *\n * - the id of the child of `parentId` the unit is nested in, however deep — the\n * sibling block a move steps over, or the moving group's own block;\n * - `parentId` itself when the unit holds that group's own layers: one more\n * block inside the parent, which a child steps over like any other rather\n * than a wall;\n * - `null` for an ungrouped layer at the root of the panel (`parentId` `null`);\n * - `undefined` when the unit sits outside `parentId` altogether — a wall,\n * since a nested group has no position outside the parent holding it.\n *\n * A `parentId` cycle reads as outside rather than looping.\n */\nfunction branchUnder(\n parentId: string | null,\n groupId: string | null,\n groupById: ReadonlyMap<string, LayerGroup>,\n): string | null | undefined {\n if (groupId === null) return parentId === null ? null : undefined;\n if (groupId === parentId) return groupId;\n let id = groupId;\n const visited = new Set([id]);\n for (;;) {\n const next = effectiveParentId(groupById.get(id), groupById);\n if (next === parentId) return id;\n if (next === null || visited.has(next)) return undefined;\n visited.add(next);\n id = next;\n }\n}\n\n/**\n * How deeply a group is nested: 0 for a top-level folder, 1 for a child of one,\n * and so on. Drives the layer panel's indentation and keeps a parent ahead of\n * its children when a reorder re-sorts the group array. A corrupted `parentId`\n * cycle stops the walk instead of hanging.\n *\n * @param group Group to measure.\n * @param groupById Group lookup, so a caller folding many groups against the\n * same set can pass a memoized map rather than rebuild one per call.\n * @returns The nesting depth.\n */\nexport function layerGroupDepth(\n group: LayerGroup,\n groupById: ReadonlyMap<string, LayerGroup>,\n): number {\n let depth = 0;\n let parentId = group.parentId;\n const visited = new Set([group.id]);\n while (parentId && !visited.has(parentId)) {\n visited.add(parentId);\n const parent = groupById.get(parentId);\n if (!parent) break;\n depth += 1;\n parentId = parent.parentId;\n }\n return depth;\n}\n\n/**\n * Move a group — with everything nested inside it — one block up or down among\n * its siblings, the groups sharing its parent.\n *\n * A nested group moves inside its parent only: it steps over a whole sibling\n * block rather than into it, and the parent's boundary is the end of its\n * travel, so `null` comes back rather than a move that would take it out of the\n * folder holding it. Inside that boundary it steps over its parent's *own*\n * layers as readily as over a sibling folder, since those rows are one more\n * block of the parent and ordering a child against them is a real reorder. A\n * folder holding no layer is the exception: it has no way to record a position\n * between two layer rows, so a move that would only cross them changes nothing\n * and reports `null` — the same limit empty folders already have at the top\n * level. A top-level group moves among the other top-level groups and the\n * ungrouped layers.\n *\n * Both arrays carry part of the panel order, so both come back: `layers` holds\n * the order of every populated block, and `groups` holds the order the empty\n * folders are placed against (see {@link buildLayerPanelUnits}). Swapping two\n * folders that own no layers moves nothing in `layers`, and moving a populated\n * group past an empty one moves nothing but the group order, so a caller must\n * apply both.\n *\n * `normalizeGroupContiguity` keeps each *group's* own members together, but not\n * a whole *subtree*: nesting only rewrites `parentId`, so an unrelated block can\n * sit between two sibling child groups. The subtree still travels as one block —\n * \"with everything nested inside it\" — which means such an in-between block is\n * left behind on the other side of it, and the move can shift more than one row.\n * Compacting the subtree is the intended reading of the operation; the in-between\n * block landing on the far side is the accepted cost.\n *\n * @param layers Flat layer list in store (render) order.\n * @param groups Group definitions.\n * @param id Group to move.\n * @param direction `\"up\"` toward the top of the panel, `\"down\"` toward the\n * bottom.\n * @returns The new arrays, or `null` when the group is already at that end of\n * the panel and nothing would change.\n */\nexport function reorderLayerGroupInPanel(\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n id: string,\n direction: \"up\" | \"down\",\n): { layers: GeoLibreLayer[]; groups: LayerGroup[] } | null {\n return moveGroupThroughUnits(buildLayerPanelUnits(layers, groups), layers, groups, id, direction);\n}\n\n/**\n * The body of {@link reorderLayerGroupInPanel}, against panel blocks the caller\n * already has, so asking about every group in turn splits the panel once rather\n * than once per question.\n */\nfunction moveGroupThroughUnits(\n units: LayerPanelUnit[],\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n id: string,\n direction: \"up\" | \"down\",\n): { layers: GeoLibreLayer[]; groups: LayerGroup[] } | null {\n const groupById = new Map(groups.map((g) => [g.id, g]));\n const group = groupById.get(id);\n if (!group) return null;\n // Label every unit with the block it occupies among the moving group's own\n // siblings, so the move steps over a whole neighbouring block and stops at the\n // edge of the parent instead of stepping one unit at a time into it.\n const parentId = effectiveParentId(group, groupById);\n const branches = units.map((unit) => branchUnder(parentId, unit.groupId, groupById));\n const matching = new Set<number>();\n branches.forEach((branch, index) => {\n if (branch === id) matching.add(index);\n });\n if (matching.size === 0) return null;\n const indices = [...matching].sort((a, b) => a - b);\n // Units run top-first, so \"up\" steps over the block above the group's first.\n const neighbor = direction === \"up\" ? indices[0] - 1 : indices[indices.length - 1] + 1;\n if (neighbor < 0 || neighbor >= units.length) return null;\n const neighborBranch = branches[neighbor];\n // Outside the parent: the group is already at that end of its own block.\n if (neighborBranch === undefined) return null;\n // A neighbouring group travels whole, so the move clears everything nested in\n // it; an ungrouped layer is a block of one.\n const neighborUnits =\n neighborBranch === null\n ? [units[neighbor]]\n : units.filter((_, index) => branches[index] === neighborBranch);\n // The moving group's own indices need not be contiguous — sibling child groups\n // can straddle an unrelated block. Gathering them compacts the subtree, which\n // is the point of moving it as a whole; see `reorderLayerGroupInPanel`.\n const block = units.filter((_, index) => matching.has(index));\n const remaining = units.filter((_, index) => !matching.has(index));\n const edge =\n direction === \"up\"\n ? remaining.indexOf(neighborUnits[0])\n : remaining.indexOf(neighborUnits[neighborUnits.length - 1]) + 1;\n const reordered = [...remaining];\n reordered.splice(edge, 0, ...block);\n\n return arraysFromUnits(reordered, layers, groups, groupById);\n}\n\n/**\n * Turn reordered panel blocks back into the two store arrays, or `null` when\n * neither array changed. Shared by every panel reorder so they all agree on how\n * the empty folders follow a move.\n */\nfunction arraysFromUnits(\n reordered: LayerPanelUnit[],\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n groupById: ReadonlyMap<string, LayerGroup>,\n): { layers: GeoLibreLayer[]; groups: LayerGroup[] } | null {\n // Units are top-first and so are the layers inside them; the store keeps the\n // reverse, with the last element drawing on top.\n const nextLayers = reordered.flatMap((unit) => unit.layers).reverse();\n // Re-derive the group order from the new panel so the empty folders, which\n // are placed against it, follow the move. Ties (an organizer and the child\n // block it spans) keep parents first.\n const ranges = groupUnitRanges(reordered, groupById);\n const nextGroups = [...groups].sort((a, b) => {\n const byPosition = (ranges.get(a.id)?.[0] ?? 0) - (ranges.get(b.id)?.[0] ?? 0);\n return byPosition !== 0\n ? byPosition\n : layerGroupDepth(a, groupById) - layerGroupDepth(b, groupById);\n });\n const layersMoved = nextLayers.some((layer, index) => layer.id !== layers[index]?.id);\n const groupsMoved = nextGroups.some((group, index) => group.id !== groups[index]?.id);\n if (!layersMoved && !groupsMoved) return null;\n return { layers: nextLayers, groups: nextGroups };\n}\n\n/** Direction {@link sortLayerGroupInPanel} orders names in, top of panel first. */\nexport type LayerGroupSortOrder = \"asc\" | \"desc\";\n\n/**\n * A numeric-aware, case/accent-insensitive collator, so \"Parcel 2\" sorts before\n * \"Parcel 10\" and \"owner\" sits with \"Owner\", the way a file browser orders.\n * Core has no i18n of its own, so the panel passes the app's display language;\n * without one (or with a tag `Intl` rejects) the runtime default applies.\n */\nfunction nameCollator(locale: string | undefined): Intl.Collator {\n const options: Intl.CollatorOptions = { numeric: true, sensitivity: \"base\" };\n try {\n return new Intl.Collator(locale, options);\n } catch {\n return new Intl.Collator(undefined, options);\n }\n}\n\n/**\n * Sort what sits directly inside a group by name, A to Z (`\"asc\"`) or Z to A\n * (`\"desc\"`) from the top of the panel down (GeoLibre#2599).\n *\n * The group's own layers are sorted among themselves, and its child groups are\n * sorted among themselves by group name, each carrying everything nested inside\n * it. The two are not interleaved: a group's own layers are one contiguous\n * block (see {@link normalizeGroupContiguity}), so that block keeps its slot\n * among the child groups rather than being split between them. Only direct\n * children move; the contents of a child group keep their order.\n *\n * Like {@link reorderLayerGroupInPanel}, both arrays come back, so empty child\n * folders follow the sort too.\n *\n * @param layers Flat layer list in store (render) order.\n * @param groups Group definitions.\n * @param id Group whose children to sort.\n * @param order `\"asc\"` for A to Z, `\"desc\"` for Z to A.\n * @param locale BCP 47 tag whose collation rules to compare names by, normally\n * the app's display language. Defaults to the runtime locale.\n * @returns The new arrays, or `null` when the children are already in that\n * order and nothing would change.\n */\nexport function sortLayerGroupInPanel(\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n id: string,\n order: LayerGroupSortOrder,\n locale?: string,\n): { layers: GeoLibreLayer[]; groups: LayerGroup[] } | null {\n const units = buildLayerPanelUnits(layers, groups);\n return sortGroupThroughUnits(units, layers, groups, id, order, nameCollator(locale));\n}\n\n/**\n * The body of {@link sortLayerGroupInPanel}, against panel blocks the caller\n * already has.\n */\nfunction sortGroupThroughUnits(\n units: LayerPanelUnit[],\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n id: string,\n order: LayerGroupSortOrder,\n collator: Intl.Collator,\n): { layers: GeoLibreLayer[]; groups: LayerGroup[] } | null {\n const groupById = new Map(groups.map((g) => [g.id, g]));\n if (!groupById.has(id)) return null;\n const sign = order === \"asc\" ? 1 : -1;\n const compare = (a: string, b: string) => sign * collator.compare(a, b);\n // Label each unit with the direct child of `id` it sits in (`id` itself for\n // the group's own layers), or `undefined` when it lies outside the group.\n const branches = units.map((unit) => branchUnder(id, unit.groupId, groupById));\n const inside = branches.flatMap((branch, index) => (branch ? [index] : []));\n if (inside.length === 0) return null;\n\n // Blocks in their current top-first order: the group's own layers as one\n // block, each child group as one block carrying its whole subtree.\n const blockUnits = new Map<string, LayerPanelUnit[]>();\n for (const index of inside) {\n const branch = branches[index] as string;\n const list = blockUnits.get(branch);\n if (list) list.push(units[index]);\n else blockUnits.set(branch, [units[index]]);\n }\n const blockIds = [...blockUnits.keys()];\n const childIds = blockIds.filter((branch) => branch !== id);\n const sortedChildIds = [...childIds].sort((a, b) =>\n compare(groupById.get(a)?.name ?? \"\", groupById.get(b)?.name ?? \"\"),\n );\n let nextChild = 0;\n const sortedBlocks = blockIds.map((branch): LayerPanelUnit[] => {\n if (branch !== id) return blockUnits.get(sortedChildIds[nextChild++]) ?? [];\n const own = (blockUnits.get(id) ?? []).flatMap((unit) => unit.layers);\n return [{ groupId: id, layers: [...own].sort((a, b) => compare(a.name, b.name)) }];\n });\n\n // The group's units need not be contiguous (see `reorderLayerGroupInPanel`);\n // gathering them at the first one compacts the subtree, as a move does.\n const insideSet = new Set(inside);\n const reordered = units.filter((_, index) => !insideSet.has(index));\n reordered.splice(inside[0], 0, ...sortedBlocks.flat());\n // Judge \"already sorted\" by what the panel shows, not by `groups` order:\n // a reparent can leave that array out of panel order, and re-deriving it\n // alone would enable a sort that changes nothing visible. This also skips\n // the whole-array rebuild for the common no-op asked by the sort menu.\n const unchanged =\n reordered.length === units.length &&\n reordered.every(\n (unit, index) =>\n unit.groupId === units[index].groupId &&\n unit.layers.length === units[index].layers.length &&\n unit.layers.every((layer, at) => layer.id === units[index].layers[at].id),\n );\n if (unchanged) return null;\n return arraysFromUnits(reordered, layers, groups, groupById);\n}\n\n/**\n * Which directions {@link reorderLayerGroupInPanel} can actually move each\n * group, so the layer panel can disable a menu item that would do nothing.\n *\n * @param layers Flat layer list in store (render) order.\n * @param groups Group definitions.\n * @returns Per-group flags, keyed by group id.\n */\nexport function layerGroupMoveability(\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n): Map<string, { up: boolean; down: boolean }> {\n const units = buildLayerPanelUnits(layers, groups);\n const result = new Map<string, { up: boolean; down: boolean }>();\n for (const group of groups) {\n result.set(group.id, {\n up: moveGroupThroughUnits(units, layers, groups, group.id, \"up\") !== null,\n down: moveGroupThroughUnits(units, layers, groups, group.id, \"down\") !== null,\n });\n }\n return result;\n}\n\n/**\n * Which orders {@link sortLayerGroupInPanel} would actually change for each\n * group, so the layer panel can disable a sort that would do nothing.\n *\n * @param layers Flat layer list in store (render) order.\n * @param groups Group definitions.\n * @param locale Collation locale, as for {@link sortLayerGroupInPanel}.\n * @returns Per-group flags, keyed by group id.\n */\nexport function layerGroupSortability(\n layers: GeoLibreLayer[],\n groups: LayerGroup[],\n locale?: string,\n): Map<string, Record<LayerGroupSortOrder, boolean>> {\n const units = buildLayerPanelUnits(layers, groups);\n const collator = nameCollator(locale);\n const result = new Map<string, Record<LayerGroupSortOrder, boolean>>();\n for (const group of groups) {\n result.set(group.id, {\n asc: sortGroupThroughUnits(units, layers, groups, group.id, \"asc\", collator) !== null,\n desc: sortGroupThroughUnits(units, layers, groups, group.id, \"desc\", collator) !== null,\n });\n }\n return result;\n}\n","import type { DashboardWidget, LegendConfig, ProjectComment } from \"./types\";\n\n/**\n * Normalize the removed-layer-ids argument into a Set for uniform lookup.\n */\nexport function removedLayerIdSet(layerIds: string | Iterable<string>): Set<string> {\n if (typeof layerIds === \"string\") return new Set([layerIds]);\n if (layerIds instanceof Set) return layerIds as Set<string>;\n return new Set(layerIds);\n}\n\n/**\n * Filter out dashboard widgets whose `layerId` references a removed layer.\n * Returns the same array reference when nothing changes.\n */\nexport function scrubWidgetsForRemovedLayers(\n widgets: DashboardWidget[],\n layerIds: string | Iterable<string>,\n): DashboardWidget[] {\n const removed = removedLayerIdSet(layerIds);\n if (removed.size === 0 || widgets.length === 0) return widgets;\n const filtered = widgets.filter((w) => !removed.has(w.layerId));\n return filtered.length === widgets.length ? widgets : filtered;\n}\n\n/**\n * Drop comments whose anchor is a feature on a removed layer.\n * Point-anchored comments are always kept.\n * Returns the same array reference when nothing changes.\n */\nexport function scrubCommentsForRemovedLayers(\n comments: ProjectComment[],\n layerIds: string | Iterable<string>,\n): ProjectComment[] {\n const removed = removedLayerIdSet(layerIds);\n if (removed.size === 0 || comments.length === 0) return comments;\n const filtered = comments.filter(\n (c) => !(c.anchor.type === \"feature\" && removed.has(c.anchor.layerId)),\n );\n return filtered.length === comments.length ? comments : filtered;\n}\n\n/**\n * Scrub legend config of references to removed layers:\n * - `order`: filter out removed layer ids\n * - `overrides`: drop keys that equal a removed id OR start with `${id}::`\n * - `customEntries`: drop keys that equal a removed layer id but KEEP keys\n * starting with `custom:`\n *\n * Returns the same reference when nothing changes.\n */\nexport function scrubLegendForRemovedLayers(\n legend: LegendConfig,\n layerIds: string | Iterable<string>,\n): LegendConfig {\n const removed = removedLayerIdSet(layerIds);\n if (removed.size === 0) return legend;\n\n let changed = false;\n\n // --- order ---\n const order = legend.order.filter((id) => !removed.has(id));\n if (order.length !== legend.order.length) changed = true;\n\n // --- overrides ---\n const overrides: Record<string, (typeof legend.overrides)[string]> = {};\n for (const [key, value] of Object.entries(legend.overrides)) {\n const base = key.includes(\"::\") ? key.slice(0, key.indexOf(\"::\")) : key;\n if (removed.has(base)) {\n changed = true;\n } else {\n overrides[key] = value;\n }\n }\n\n // --- customEntries ---\n let customEntries = legend.customEntries;\n if (customEntries) {\n let customEntriesChanged = false;\n const nextCustom: Record<string, (typeof customEntries)[string]> = {};\n for (const [key, value] of Object.entries(customEntries)) {\n if (key.startsWith(\"custom:\")) {\n nextCustom[key] = value;\n } else if (removed.has(key)) {\n customEntriesChanged = true;\n } else {\n nextCustom[key] = value;\n }\n }\n if (customEntriesChanged) {\n customEntries = Object.keys(nextCustom).length > 0 ? nextCustom : undefined;\n changed = true;\n }\n }\n\n if (!changed) return legend;\n return { ...legend, order, overrides, customEntries };\n}\n","/**\n * Print Layout composer settings that belong to a project.\n *\n * The Print Layout dialog composes a printable page out of the current map:\n * a title block, page size and orientation, the cartographic furniture\n * (legend, scale bar, north arrow), optional data blocks, and an atlas /\n * map-series definition. All of that describes the *project's* map document,\n * so it round-trips through `.geolibre.json` (GeoLibre discussion #1992 —\n * before this, reopening a project lost the title, page format and\n * orientation, and the dialog kept showing the previous project's settings).\n *\n * Deliberately **not** in here: everything that is per-session rather than\n * part of the document — the captured map image, export progress, error and\n * clipboard notices, the current atlas page, and the dialog's own chrome\n * (panel widths, dialog size).\n *\n * The unions below are the storage contract. Their app-side counterparts\n * (`PaperSizeId`, `Orientation`, `BodyCorner`, ... in\n * `apps/geolibre-desktop/src/lib/`) are checked against these by ordinary\n * assignment in the dialog's snapshot/restore paths, so adding a paper size or\n * a corner on one side without the other fails the build.\n */\n\nimport { removedLayerIdSet } from \"./layer-ref-scrub\";\n\n/** A page corner an overlay block can be pinned to. */\nexport type PrintLayoutCorner = \"top-left\" | \"top-right\" | \"bottom-left\" | \"bottom-right\";\n\n/** Which features a data block covers when the atlas paginates the map. */\nexport type PrintLayoutPageFilter = \"all\" | \"contained\" | \"intersecting\";\n\n/** One hand-authored swatch in the composer's user-defined legend. */\nexport interface PrintLayoutLegendEntry {\n id: string;\n label: string;\n color: string;\n}\n\nexport interface PrintLayoutConfig {\n // Title block\n title: string;\n subtitle: string;\n titlePlacement: \"outside\" | \"inside\";\n titleAlign: \"left\" | \"center\" | \"right\";\n\n // Page\n paperSize:\n | \"a4\"\n | \"a3\"\n | \"letter\"\n | \"legal\"\n | \"tabloid\"\n | \"fullhd\"\n | \"hd\"\n | \"uhd4k\"\n | \"square\"\n | \"custom\";\n orientation: \"portrait\" | \"landscape\";\n customWidth: number;\n customHeight: number;\n customUnit: \"mm\" | \"px\";\n pageMargin: \"normal\" | \"narrow\" | \"none\";\n /** Colour of the page around the map: title block, footer row and margins. */\n pageBackground: string;\n /** Look of the panels over the map: white or dark. */\n panelTheme: \"light\" | \"dark\";\n showPageBorder: boolean;\n pageBorderColor: string;\n pageBorderWidth: number;\n\n // Map frame\n mapBorderColor: string;\n mapBorderWidth: number;\n mapBackground: string;\n\n // Map elements\n showTitle: boolean;\n showSubtitle: boolean;\n showLegend: boolean;\n showScaleBar: boolean;\n showNorthArrow: boolean;\n navigationGrouped: boolean;\n showFooter: boolean;\n footerText: string;\n showDate: boolean;\n /** Blank means \"today\", resolved when the page is drawn. */\n dateText: string;\n showAttribution: boolean;\n\n // Colorbar\n showColorbar: boolean;\n colorbarRamp: string;\n /** Kept as typed text so a half-entered value survives a round trip. */\n colorbarMin: string;\n colorbarMax: string;\n colorbarLabel: string;\n colorbarOrientation: \"vertical\" | \"horizontal\";\n colorbarLength: number;\n colorbarPosition: PrintLayoutCorner;\n\n // User-defined legend\n showCustomLegend: boolean;\n customLegendTitle: string;\n customLegendEntries: PrintLayoutLegendEntry[];\n customLegendPosition: PrintLayoutCorner;\n\n // Attribute-table block\n showDataTable: boolean;\n tableLayerId: string;\n tableTitle: string;\n tableColumns: string[];\n tableSortField: string;\n tableSortDesc: boolean;\n tableMaxRows: number;\n tableFitRows: boolean;\n tablePosition: PrintLayoutCorner;\n tablePageFilter: PrintLayoutPageFilter;\n tableFilterToAtlasFeature: boolean;\n\n // Chart block\n showDataChart: boolean;\n chartLayerId: string;\n chartTitle: string;\n chartType: \"bar\" | \"pie\" | \"line\";\n chartCategoryField: string;\n chartAggregation: \"count\" | \"sum\" | \"mean\";\n chartValueField: string;\n chartPosition: PrintLayoutCorner;\n chartPageFilter: PrintLayoutPageFilter;\n\n // Info block (\"title block\" / cartouche)\n showInfoBlock: boolean;\n author: string;\n projectNumber: string;\n crs: string;\n revision: string;\n\n // What the page captures\n captureMode: \"viewport\" | \"extent\";\n /** Drawn print extent as `[west, south, east, north]`; null when unset. */\n extentBbox: [number, number, number, number] | null;\n\n // Atlas / map series\n atlasEnabled: boolean;\n atlasLayerId: string;\n atlasCoverage: \"features\" | \"line\";\n atlasSegmentKm: string;\n atlasNameField: string;\n atlasExtentMode: \"margin\" | \"scale\";\n atlasMarginPct: number;\n atlasMaskEnabled: boolean;\n atlasScale: string;\n atlasSortField: string;\n atlasSortDescending: boolean;\n atlasFilter: string;\n atlasFilenamePattern: string;\n}\n\nconst CORNERS = [\"top-left\", \"top-right\", \"bottom-left\", \"bottom-right\"] as const;\nconst PAGE_FILTERS = [\"all\", \"contained\", \"intersecting\"] as const;\n\n/**\n * Settings a project starts with, and the shape every partial or hand-edited\n * `printLayout` is filled out against. Also the dialog's initial state, so\n * \"new project\" and \"a project saved before this feature\" compose identically.\n */\nexport const DEFAULT_PRINT_LAYOUT: PrintLayoutConfig = {\n title: \"\",\n subtitle: \"\",\n titlePlacement: \"outside\",\n titleAlign: \"center\",\n\n paperSize: \"a4\",\n orientation: \"landscape\",\n customWidth: 1280,\n customHeight: 720,\n customUnit: \"px\",\n pageMargin: \"normal\",\n pageBackground: \"#ffffff\",\n panelTheme: \"light\",\n showPageBorder: false,\n pageBorderColor: \"#111827\",\n pageBorderWidth: 2,\n\n mapBorderColor: \"#9ca3af\",\n mapBorderWidth: 1,\n mapBackground: \"#e5e7eb\",\n\n showTitle: true,\n showSubtitle: true,\n showLegend: true,\n showScaleBar: true,\n showNorthArrow: true,\n navigationGrouped: true,\n showFooter: false,\n footerText: \"\",\n showDate: true,\n dateText: \"\",\n showAttribution: true,\n\n showColorbar: false,\n colorbarRamp: \"viridis\",\n colorbarMin: \"0\",\n colorbarMax: \"100\",\n colorbarLabel: \"\",\n colorbarOrientation: \"vertical\",\n colorbarLength: 34,\n colorbarPosition: \"top-right\",\n\n showCustomLegend: false,\n customLegendTitle: \"Legend\",\n customLegendEntries: [\n { id: \"cl-1\", label: \"Class 1\", color: \"#2563eb\" },\n { id: \"cl-2\", label: \"Class 2\", color: \"#16a34a\" },\n ],\n customLegendPosition: \"top-left\",\n\n showDataTable: false,\n tableLayerId: \"\",\n tableTitle: \"\",\n tableColumns: [],\n tableSortField: \"\",\n tableSortDesc: false,\n tableMaxRows: 10,\n tableFitRows: false,\n tablePosition: \"bottom-left\",\n tablePageFilter: \"contained\",\n tableFilterToAtlasFeature: false,\n\n showDataChart: false,\n chartLayerId: \"\",\n chartTitle: \"\",\n chartType: \"bar\",\n chartCategoryField: \"\",\n chartAggregation: \"count\",\n chartValueField: \"\",\n chartPosition: \"top-right\",\n chartPageFilter: \"contained\",\n\n showInfoBlock: false,\n author: \"\",\n projectNumber: \"\",\n crs: \"\",\n revision: \"\",\n\n captureMode: \"viewport\",\n extentBbox: null,\n\n atlasEnabled: false,\n atlasLayerId: \"\",\n atlasCoverage: \"features\",\n atlasSegmentKm: \"20\",\n atlasNameField: \"\",\n atlasExtentMode: \"margin\",\n atlasMarginPct: 10,\n atlasMaskEnabled: false,\n atlasScale: \"50000\",\n atlasSortField: \"\",\n atlasSortDescending: false,\n atlasFilter: \"\",\n atlasFilenamePattern: \"{atlas.pagenumber}-{atlas.name}\",\n};\n\n/** A fresh copy of the defaults, safe to hand to a store or a component. */\nexport function createDefaultPrintLayout(): PrintLayoutConfig {\n return {\n ...DEFAULT_PRINT_LAYOUT,\n tableColumns: [],\n customLegendEntries: DEFAULT_PRINT_LAYOUT.customLegendEntries.map((entry) => ({ ...entry })),\n };\n}\n\nfunction str(value: unknown, fallback: string): string {\n return typeof value === \"string\" ? value : fallback;\n}\n\n/** A `#RGB` / `#RRGGBB` colour, or `fallback` for anything else. */\nfunction hexColor(value: unknown, fallback: string): string {\n return typeof value === \"string\" && /^#(?:[0-9a-f]{3}|[0-9a-f]{6})$/i.test(value.trim())\n ? value.trim()\n : fallback;\n}\n\nfunction bool(value: unknown, fallback: boolean): boolean {\n return typeof value === \"boolean\" ? value : fallback;\n}\n\nfunction num(value: unknown, fallback: number, min: number, max: number): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) return fallback;\n return Math.min(max, Math.max(min, value));\n}\n\nfunction oneOf<T extends string>(value: unknown, allowed: readonly T[], fallback: T): T {\n return typeof value === \"string\" && (allowed as readonly string[]).includes(value)\n ? (value as T)\n : fallback;\n}\n\nfunction stringList(value: unknown, fallback: string[]): string[] {\n if (!Array.isArray(value)) return fallback;\n return value.filter((item): item is string => typeof item === \"string\");\n}\n\nfunction legendEntries(\n value: unknown,\n fallback: PrintLayoutLegendEntry[],\n): PrintLayoutLegendEntry[] {\n if (!Array.isArray(value)) return fallback.map((entry) => ({ ...entry }));\n const entries: PrintLayoutLegendEntry[] = [];\n // The editor keys its swatch rows by id, so two entries sharing one would\n // make an edit to either apply to both. A hand-edited file can omit an id or\n // repeat one, so ids are made unique here rather than trusted.\n const used = new Set<string>();\n for (const [index, item] of value.entries()) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) continue;\n const entry = item as Partial<PrintLayoutLegendEntry>;\n const given = typeof entry.id === \"string\" ? entry.id.trim() : \"\";\n let id = given;\n if (!id || used.has(id)) {\n // Synthesize past whatever is already taken, including ids claimed later\n // in the array, so the fallback cannot collide with an explicit one.\n let candidate = index + 1;\n while (used.has(`cl-${candidate}`) || claimsId(value, index, `cl-${candidate}`)) {\n candidate += 1;\n }\n id = `cl-${candidate}`;\n }\n used.add(id);\n entries.push({\n id,\n label: str(entry.label, \"\"),\n color: str(entry.color, \"#2563eb\"),\n });\n }\n return entries;\n}\n\n/** Whether any entry after `index` explicitly carries `id`. */\nfunction claimsId(value: unknown[], index: number, id: string): boolean {\n for (let i = index + 1; i < value.length; i += 1) {\n const item = value[i];\n if (!item || typeof item !== \"object\" || Array.isArray(item)) continue;\n const candidate = (item as Partial<PrintLayoutLegendEntry>).id;\n if (typeof candidate === \"string\" && candidate.trim() === id) return true;\n }\n return false;\n}\n\nfunction extent(value: unknown): [number, number, number, number] | null {\n if (!Array.isArray(value) || value.length !== 4) return null;\n if (!value.every((n) => typeof n === \"number\" && Number.isFinite(n))) return null;\n const [west, south, east, north] = value as number[];\n // The draw tool orders its corners, so an inverted or zero-area box only\n // reaches here from a hand-edited file; capturing it would produce an empty\n // image, so treat it as unset (mirrors `normalizeBounds` in project.ts).\n if (west >= east || south >= north) return null;\n return [west, south, east, north];\n}\n\n/**\n * Coerce an untrusted (possibly hand-edited, possibly partial) `printLayout`\n * into a complete {@link PrintLayoutConfig}, filling every missing or\n * malformed field from {@link DEFAULT_PRINT_LAYOUT}.\n *\n * @param value - The raw `printLayout` value read from a project file.\n * @returns The normalized config, or null when the project carries none, so\n * callers can tell \"absent\" from \"explicitly default\".\n */\nexport function normalizePrintLayoutConfig(value: unknown): PrintLayoutConfig | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return null;\n const raw = value as Record<string, unknown>;\n const d = DEFAULT_PRINT_LAYOUT;\n return {\n title: str(raw.title, d.title),\n subtitle: str(raw.subtitle, d.subtitle),\n titlePlacement: oneOf(raw.titlePlacement, [\"outside\", \"inside\"] as const, d.titlePlacement),\n titleAlign: oneOf(raw.titleAlign, [\"left\", \"center\", \"right\"] as const, d.titleAlign),\n\n paperSize: oneOf(\n raw.paperSize,\n [\"a4\", \"a3\", \"letter\", \"legal\", \"tabloid\", \"fullhd\", \"hd\", \"uhd4k\", \"square\", \"custom\"],\n d.paperSize,\n ),\n orientation: oneOf(raw.orientation, [\"portrait\", \"landscape\"] as const, d.orientation),\n customWidth: num(raw.customWidth, d.customWidth, 1, 100000),\n customHeight: num(raw.customHeight, d.customHeight, 1, 100000),\n customUnit: oneOf(raw.customUnit, [\"mm\", \"px\"] as const, d.customUnit),\n pageMargin: oneOf(raw.pageMargin, [\"normal\", \"narrow\", \"none\"] as const, d.pageMargin),\n // Drawn behind the page and read for the text contrast, so only a hex\n // colour is kept: a hand-edited name or typo falls back to white.\n pageBackground: hexColor(raw.pageBackground, d.pageBackground),\n panelTheme: oneOf(raw.panelTheme, [\"light\", \"dark\"] as const, d.panelTheme),\n showPageBorder: bool(raw.showPageBorder, d.showPageBorder),\n pageBorderColor: str(raw.pageBorderColor, d.pageBorderColor),\n // A page border is drawn only when `showPageBorder` is on, so a zero width\n // would be an invisible \"visible\" border; the editor's own floor is 1.\n pageBorderWidth: num(raw.pageBorderWidth, d.pageBorderWidth, 1, 20),\n\n mapBorderColor: str(raw.mapBorderColor, d.mapBorderColor),\n mapBorderWidth: num(raw.mapBorderWidth, d.mapBorderWidth, 0, 10),\n mapBackground: str(raw.mapBackground, d.mapBackground),\n\n showTitle: bool(raw.showTitle, d.showTitle),\n showSubtitle: bool(raw.showSubtitle, d.showSubtitle),\n showLegend: bool(raw.showLegend, d.showLegend),\n showScaleBar: bool(raw.showScaleBar, d.showScaleBar),\n showNorthArrow: bool(raw.showNorthArrow, d.showNorthArrow),\n navigationGrouped: bool(raw.navigationGrouped, d.navigationGrouped),\n showFooter: bool(raw.showFooter, d.showFooter),\n footerText: str(raw.footerText, d.footerText),\n showDate: bool(raw.showDate, d.showDate),\n dateText: str(raw.dateText, d.dateText),\n showAttribution: bool(raw.showAttribution, d.showAttribution),\n\n showColorbar: bool(raw.showColorbar, d.showColorbar),\n colorbarRamp: str(raw.colorbarRamp, d.colorbarRamp),\n colorbarMin: str(raw.colorbarMin, d.colorbarMin),\n colorbarMax: str(raw.colorbarMax, d.colorbarMax),\n colorbarLabel: str(raw.colorbarLabel, d.colorbarLabel),\n colorbarOrientation: oneOf(\n raw.colorbarOrientation,\n [\"vertical\", \"horizontal\"] as const,\n d.colorbarOrientation,\n ),\n colorbarLength: num(raw.colorbarLength, d.colorbarLength, 1, 100),\n colorbarPosition: oneOf(raw.colorbarPosition, CORNERS, d.colorbarPosition),\n\n showCustomLegend: bool(raw.showCustomLegend, d.showCustomLegend),\n customLegendTitle: str(raw.customLegendTitle, d.customLegendTitle),\n customLegendEntries: legendEntries(raw.customLegendEntries, d.customLegendEntries),\n customLegendPosition: oneOf(raw.customLegendPosition, CORNERS, d.customLegendPosition),\n\n showDataTable: bool(raw.showDataTable, d.showDataTable),\n tableLayerId: str(raw.tableLayerId, d.tableLayerId),\n tableTitle: str(raw.tableTitle, d.tableTitle),\n tableColumns: stringList(raw.tableColumns, d.tableColumns),\n tableSortField: str(raw.tableSortField, d.tableSortField),\n tableSortDesc: bool(raw.tableSortDesc, d.tableSortDesc),\n tableMaxRows: num(raw.tableMaxRows, d.tableMaxRows, 1, 10000),\n tableFitRows: bool(raw.tableFitRows, d.tableFitRows),\n tablePosition: oneOf(raw.tablePosition, CORNERS, d.tablePosition),\n tablePageFilter: oneOf(raw.tablePageFilter, PAGE_FILTERS, d.tablePageFilter),\n tableFilterToAtlasFeature: bool(raw.tableFilterToAtlasFeature, d.tableFilterToAtlasFeature),\n\n showDataChart: bool(raw.showDataChart, d.showDataChart),\n chartLayerId: str(raw.chartLayerId, d.chartLayerId),\n chartTitle: str(raw.chartTitle, d.chartTitle),\n chartType: oneOf(raw.chartType, [\"bar\", \"pie\", \"line\"] as const, d.chartType),\n chartCategoryField: str(raw.chartCategoryField, d.chartCategoryField),\n chartAggregation: oneOf(\n raw.chartAggregation,\n [\"count\", \"sum\", \"mean\"] as const,\n d.chartAggregation,\n ),\n chartValueField: str(raw.chartValueField, d.chartValueField),\n chartPosition: oneOf(raw.chartPosition, CORNERS, d.chartPosition),\n chartPageFilter: oneOf(raw.chartPageFilter, PAGE_FILTERS, d.chartPageFilter),\n\n showInfoBlock: bool(raw.showInfoBlock, d.showInfoBlock),\n author: str(raw.author, d.author),\n projectNumber: str(raw.projectNumber, d.projectNumber),\n crs: str(raw.crs, d.crs),\n revision: str(raw.revision, d.revision),\n\n captureMode: oneOf(raw.captureMode, [\"viewport\", \"extent\"] as const, d.captureMode),\n extentBbox: extent(raw.extentBbox),\n\n atlasEnabled: bool(raw.atlasEnabled, d.atlasEnabled),\n atlasLayerId: str(raw.atlasLayerId, d.atlasLayerId),\n atlasCoverage: oneOf(raw.atlasCoverage, [\"features\", \"line\"] as const, d.atlasCoverage),\n atlasSegmentKm: str(raw.atlasSegmentKm, d.atlasSegmentKm),\n atlasNameField: str(raw.atlasNameField, d.atlasNameField),\n atlasExtentMode: oneOf(raw.atlasExtentMode, [\"margin\", \"scale\"] as const, d.atlasExtentMode),\n atlasMarginPct: num(raw.atlasMarginPct, d.atlasMarginPct, 0, 100),\n atlasMaskEnabled: bool(raw.atlasMaskEnabled, d.atlasMaskEnabled),\n atlasScale: str(raw.atlasScale, d.atlasScale),\n atlasSortField: str(raw.atlasSortField, d.atlasSortField),\n atlasSortDescending: bool(raw.atlasSortDescending, d.atlasSortDescending),\n atlasFilter: str(raw.atlasFilter, d.atlasFilter),\n atlasFilenamePattern: str(raw.atlasFilenamePattern, d.atlasFilenamePattern),\n };\n}\n\n/**\n * Whether a config is still the untouched default, so a project that never\n * opened the composer serializes without a `printLayout` key and stays\n * byte-identical to one saved before this feature.\n */\nexport function isDefaultPrintLayout(config: PrintLayoutConfig): boolean {\n return printLayoutConfigsEqual(config, DEFAULT_PRINT_LAYOUT);\n}\n\n/**\n * Structural equality for two configs. Used to skip no-op store writes, so\n * merely opening the Print Layout dialog never marks a project dirty.\n */\nexport function printLayoutConfigsEqual(a: PrintLayoutConfig, b: PrintLayoutConfig): boolean {\n if (a === b) return true;\n for (const key of Object.keys(DEFAULT_PRINT_LAYOUT) as (keyof PrintLayoutConfig)[]) {\n const left = a[key];\n const right = b[key];\n if (Array.isArray(left) || Array.isArray(right)) {\n if (JSON.stringify(left) !== JSON.stringify(right)) return false;\n } else if (left !== right) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Clear each data/atlas block whose layer `missing` rejects. Shared by the two\n * entry points below, which differ only in how they decide what is gone.\n *\n * @returns The same object when every block's layer survives.\n */\nfunction clearBlocksForMissingLayers(\n config: PrintLayoutConfig,\n missing: (layerId: string) => boolean,\n): PrintLayoutConfig {\n const gone = (id: string) => id !== \"\" && missing(id);\n if (!gone(config.tableLayerId) && !gone(config.chartLayerId) && !gone(config.atlasLayerId)) {\n return config;\n }\n return {\n ...config,\n ...(gone(config.tableLayerId) ? { tableLayerId: \"\", showDataTable: false } : {}),\n ...(gone(config.chartLayerId) ? { chartLayerId: \"\", showDataChart: false } : {}),\n ...(gone(config.atlasLayerId) ? { atlasLayerId: \"\", atlasEnabled: false } : {}),\n };\n}\n\n/**\n * Drop references to layers the project no longer carries, so a composer that\n * pointed at a since-deleted layer opens with the block cleared instead of\n * rendering nothing from a dangling id. Mirrors the widget/comment/legend\n * scrubbing in `applyProjectToStore`.\n *\n * @param config - The config to scrub.\n * @param existingLayerIds - Ids of the layers present in the project.\n * @returns The same object when nothing referenced a missing layer.\n */\nexport function scrubPrintLayoutForLayers(\n config: PrintLayoutConfig,\n existingLayerIds: ReadonlySet<string>,\n): PrintLayoutConfig {\n return clearBlocksForMissingLayers(config, (id) => !existingLayerIds.has(id));\n}\n\n/**\n * The delete-time counterpart, matching `scrubLegendForRemovedLayers` and its\n * siblings: called when layers are removed from the open project so the saved\n * file never carries a block pointing at a layer that is already gone.\n *\n * @param config - The config to scrub.\n * @param layerIds - The removed layer id, or ids.\n * @returns The same object when no block referenced a removed layer.\n */\nexport function scrubPrintLayoutForRemovedLayers(\n config: PrintLayoutConfig,\n layerIds: string | Iterable<string>,\n): PrintLayoutConfig {\n const removed = removedLayerIdSet(layerIds);\n if (removed.size === 0) return config;\n return clearBlocksForMissingLayers(config, (id) => removed.has(id));\n}\n","// The Style Manager's data layer (issue #1294): subset extraction, untrusted\n// input normalization, the shareable JSON bundle format, and the built-in\n// presets. UI-free so both the desktop app and tests consume it directly.\n\nimport {\n DEFAULT_LAYER_STYLE,\n type LabelStyle,\n type LayerStyle,\n type LayerType,\n type StyleLibraryEntry,\n type StyleLibraryEntryKind,\n} from \"./types\";\nimport { isStrokeDashPattern } from \"./vector-color\";\n\n/**\n * Layer types whose symbology is driven by the vector {@link LayerStyle}\n * fields and can therefore receive a Style Manager entry (or a style-file\n * import). The single source of truth for every entry point — the Style\n * Manager dialog, the Style panel header button, and the layer context menu —\n * so a future vector-stylable type cannot be added to one gate and missed in\n * another, leaving a dead-end entry point.\n */\nexport function isStyleLibraryTargetLayer(type: LayerType): boolean {\n return type === \"geojson\" || type === \"vector-tiles\";\n}\n\n/** `type` discriminator of an exported Style Manager bundle file. */\nexport const STYLE_LIBRARY_BUNDLE_TYPE = \"geolibre-style-library\";\n\n/** Current bundle format version, for forward-compatible readers. */\nexport const STYLE_LIBRARY_BUNDLE_VERSION = 1;\n\nconst STYLE_LIBRARY_ENTRY_KINDS: readonly StyleLibraryEntryKind[] = [\n \"style\",\n \"symbol\",\n \"labels\",\n \"ramp\",\n];\n\n/**\n * The {@link LayerStyle} keys a `\"symbol\"` entry captures: fill, stroke,\n * point/circle, marker, and fill-pattern symbology. Deliberately excludes the\n * renderer configuration (`vector*`), labels, zoom range, and raster\n * adjustments so a symbol preset restyles a layer without dismantling its\n * classification or labeling.\n */\nexport const SYMBOL_STYLE_KEYS = [\n \"fillColor\",\n \"strokeColor\",\n \"strokeWidth\",\n \"strokeWidthUnit\",\n \"strokeOpacity\",\n \"strokeDash\",\n \"strokeBlur\",\n \"fillOpacity\",\n \"circleRadius\",\n \"fillPattern\",\n \"fillPatternColor\",\n \"fillPatternSvg\",\n \"markerEnabled\",\n \"markerShape\",\n \"markerColor\",\n \"markerSize\",\n \"markerSvg\",\n] as const satisfies readonly (keyof LayerStyle)[];\n\n/**\n * The {@link LayerStyle} keys a `\"ramp\"` entry captures. The classified\n * attribute (`vectorStyleProperty`) and the concrete stops are deliberately\n * excluded: they are data-specific, so a ramp preset stays reusable across\n * layers with different attributes and value ranges.\n */\nexport const RAMP_STYLE_KEYS = [\n \"vectorStyleColorRamp\",\n \"vectorStyleClassCount\",\n \"vectorStyleClassificationScheme\",\n] as const satisfies readonly (keyof LayerStyle)[];\n\n/**\n * Extract the {@link LayerStyle} subset a Style Manager entry of `kind`\n * stores, deep-cloned so later edits to the live layer cannot mutate the\n * saved entry.\n *\n * @param style - The source layer's full style.\n * @param kind - Which subset to capture.\n * @returns The cloned subset to store in a {@link StyleLibraryEntry}.\n */\nexport function extractStyleLibraryStyle(\n style: LayerStyle,\n kind: StyleLibraryEntryKind,\n): Partial<LayerStyle> {\n if (kind === \"labels\") {\n return structuredClone({ labels: style.labels ?? DEFAULT_LAYER_STYLE.labels });\n }\n if (kind === \"symbol\" || kind === \"ramp\") {\n const keys: readonly (keyof LayerStyle)[] =\n kind === \"symbol\" ? SYMBOL_STYLE_KEYS : RAMP_STYLE_KEYS;\n const subset: Partial<LayerStyle> = {};\n for (const key of keys) {\n const value = style[key] ?? DEFAULT_LAYER_STYLE[key];\n (subset as Record<string, unknown>)[key] = value;\n }\n return structuredClone(subset);\n }\n return structuredClone({ ...DEFAULT_LAYER_STYLE, ...style });\n}\n\n/**\n * Allowed values for the enum-typed {@link LayerStyle} fields, so a\n * hand-edited file cannot smuggle an unknown mode/shape/unit string through\n * the primitive `typeof` check and into the renderer. Fields absent here are\n * free-form strings (colors, expressions, property names, SVG markup).\n */\nconst STYLE_ENUM_VALUES: Partial<Record<keyof LayerStyle, readonly string[]>> = {\n strokeWidthUnit: [\"pixels\", \"meters\"],\n vectorStyleMode: [\"single\", \"graduated\", \"categorized\", \"rule-based\", \"expression\"],\n fillPattern: [\"none\", \"hatch\", \"cross-hatch\", \"horizontal\", \"vertical\", \"dots\", \"svg\"],\n markerShape: [\"circle\", \"square\", \"triangle\", \"diamond\", \"star\", \"cross\", \"pin\", \"custom\"],\n pointRenderer: [\"single\", \"heatmap\", \"cluster\"],\n // Union of the graduated and categorized schemes the Style panel offers.\n vectorStyleClassificationScheme: [\n \"equal-interval\",\n \"quantile\",\n \"natural-breaks\",\n \"standard-deviation\",\n \"geometric-interval\",\n \"manual\",\n \"top-values\",\n \"alphabetical\",\n \"first-values\",\n ],\n};\n\n/** Allowed values for the enum-typed {@link LabelStyle} fields. */\nconst LABEL_ENUM_VALUES: Partial<Record<keyof LabelStyle, readonly string[]>> = {\n placement: [\"point\", \"line\"],\n anchor: [\n \"center\",\n \"left\",\n \"right\",\n \"top\",\n \"bottom\",\n \"top-left\",\n \"top-right\",\n \"bottom-left\",\n \"bottom-right\",\n ],\n transform: [\"none\", \"uppercase\", \"lowercase\"],\n dedupe: [\"off\", \"unique\", \"concatenate\"],\n};\n\n/**\n * Coerce an untrusted style patch (from a hand-edited project file or an\n * imported bundle) into a safe {@link LayerStyle} subset: unknown keys are\n * dropped, values whose primitive type disagrees with the default (or that\n * are non-finite numbers, or strings outside an enum field's allowed set) are\n * dropped, and a present `labels` object is completed against the default\n * label style so applying it never leaves the renderer reading undefined\n * label fields.\n *\n * @param value - The raw `style` value from JSON.\n * @returns The sanitized subset (empty when nothing usable survives).\n */\nexport function sanitizeLayerStylePatch(value: unknown): Partial<LayerStyle> {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return {};\n const source = value as Record<string, unknown>;\n const out: Record<string, unknown> = {};\n for (const key of Object.keys(DEFAULT_LAYER_STYLE) as (keyof LayerStyle)[]) {\n if (!(key in source)) continue;\n const given = source[key];\n const fallback = DEFAULT_LAYER_STYLE[key];\n if (key === \"labels\") {\n if (given && typeof given === \"object\" && !Array.isArray(given)) {\n const givenLabels = given as Record<string, unknown>;\n const labels: Record<string, unknown> = {};\n for (const labelKey of Object.keys(DEFAULT_LAYER_STYLE.labels) as (keyof LabelStyle)[]) {\n const labelFallback = DEFAULT_LAYER_STYLE.labels[labelKey];\n const labelGiven = givenLabels[labelKey];\n const labelAllowed = LABEL_ENUM_VALUES[labelKey];\n const labelValid =\n typeof labelGiven === typeof labelFallback &&\n (typeof labelGiven !== \"number\" || Number.isFinite(labelGiven)) &&\n (!labelAllowed || labelAllowed.includes(labelGiven as string));\n labels[labelKey] = labelValid ? labelGiven : labelFallback;\n }\n out.labels = labels as unknown as LabelStyle;\n }\n continue;\n }\n if (Array.isArray(fallback)) {\n if (!Array.isArray(given)) continue;\n // Validate array elements too, not just \"is an array\": malformed stop\n // or rule objects would otherwise survive to the renderer. Rebuild each\n // element from its known fields so extra keys are stripped along the\n // way; an invalid optional label is dropped rather than the whole stop.\n if (key === \"vectorStyleStops\") {\n out[key] = given.flatMap((item) => {\n if (!item || typeof item !== \"object\") return [];\n const stop = item as Record<string, unknown>;\n if (\n (typeof stop.value !== \"string\" &&\n !(typeof stop.value === \"number\" && Number.isFinite(stop.value))) ||\n typeof stop.color !== \"string\"\n ) {\n return [];\n }\n return [\n {\n value: stop.value,\n color: stop.color,\n ...(typeof stop.label === \"string\" ? { label: stop.label } : {}),\n },\n ];\n });\n } else if (key === \"vectorRules\") {\n out[key] = given.flatMap((item) => {\n if (!item || typeof item !== \"object\") return [];\n const rule = item as Record<string, unknown>;\n if (\n typeof rule.id !== \"string\" ||\n typeof rule.label !== \"string\" ||\n typeof rule.filter !== \"string\" ||\n typeof rule.color !== \"string\" ||\n typeof rule.isElse !== \"boolean\"\n ) {\n return [];\n }\n // Optional per-rule fields (enabled toggle, zoom range, nesting,\n // symbol overrides) carry through only when well-typed; a malformed\n // optional field is dropped rather than the whole rule.\n const optionalNumber = (value: unknown) =>\n typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n // Out-of-domain numbers are dropped (the rule inherits the layer\n // value) rather than clamped, so a nonsense hand-edited value never\n // silently becomes a different-but-valid override.\n const optionalInRange = (value: unknown, min: number, max: number) => {\n const parsed = optionalNumber(value);\n return parsed !== undefined && parsed >= min && parsed <= max ? parsed : undefined;\n };\n const optionalString = (value: unknown) =>\n typeof value === \"string\" ? value : undefined;\n const minZoom = optionalInRange(rule.minZoom, 0, 24);\n const maxZoom = optionalInRange(rule.maxZoom, 0, 24);\n const parentId = optionalString(rule.parentId);\n const strokeColor = optionalString(rule.strokeColor);\n const strokeWidth = optionalInRange(rule.strokeWidth, 0, Number.POSITIVE_INFINITY);\n const fillOpacity = optionalInRange(rule.fillOpacity, 0, 1);\n const circleRadius = optionalInRange(rule.circleRadius, 0, Number.POSITIVE_INFINITY);\n return [\n {\n id: rule.id,\n label: rule.label,\n filter: rule.filter,\n color: rule.color,\n isElse: rule.isElse,\n ...(rule.enabled === false ? { enabled: false } : {}),\n ...(minZoom !== undefined ? { minZoom } : {}),\n ...(maxZoom !== undefined ? { maxZoom } : {}),\n ...(parentId !== undefined ? { parentId } : {}),\n ...(strokeColor !== undefined ? { strokeColor } : {}),\n ...(strokeWidth !== undefined ? { strokeWidth } : {}),\n ...(fillOpacity !== undefined ? { fillOpacity } : {}),\n ...(circleRadius !== undefined ? { circleRadius } : {}),\n },\n ];\n });\n } else if (key === \"strokeDash\") {\n // An empty pattern is solid; anything else must be drawable.\n if (given.length === 0 || isStrokeDashPattern(given)) out[key] = given;\n } else {\n out[key] = given;\n }\n continue;\n }\n if (typeof given !== typeof fallback) continue;\n if (typeof given === \"number\" && !Number.isFinite(given)) continue;\n const allowed = STYLE_ENUM_VALUES[key];\n if (allowed && !allowed.includes(given as string)) continue;\n out[key] = given;\n }\n return structuredClone(out) as Partial<LayerStyle>;\n}\n\n/**\n * Restrict a sanitized style patch to the key set its entry `kind` declares,\n * mirroring what {@link extractStyleLibraryStyle} captures on save. Without\n * this, a hand-edited project file or imported bundle could declare\n * `kind: \"symbol\"` with extra full-style fields in the payload, and applying\n * that \"Symbol only\" entry (a partial merge) would silently change labels or\n * renderer settings on the target layer.\n */\nfunction restrictStylePatchToKind(\n style: Partial<LayerStyle>,\n kind: StyleLibraryEntryKind,\n): Partial<LayerStyle> {\n if (kind === \"style\") return style;\n const allowed: readonly (keyof LayerStyle)[] =\n kind === \"symbol\" ? SYMBOL_STYLE_KEYS : kind === \"ramp\" ? RAMP_STYLE_KEYS : [\"labels\"];\n const out: Partial<LayerStyle> = {};\n for (const key of allowed) {\n if (key in style) {\n (out as Record<string, unknown>)[key] = style[key];\n }\n }\n return out;\n}\n\n/**\n * Coerce an untrusted (hand-edited project file or imported bundle) entries\n * array into valid {@link StyleLibraryEntry} records. Drops entries without a\n * usable id or name, de-duplicates by id, coerces an unknown kind to\n * `\"style\"`, and sanitizes each style payload (restricted to the declared\n * kind's key set); entries whose payload sanitizes to nothing are dropped.\n *\n * @param value - The raw `styleLibrary` / bundle `entries` value.\n * @returns Normalized, de-duplicated entries (empty when none survive).\n */\nexport function normalizeStyleLibraryEntries(value: unknown): StyleLibraryEntry[] {\n if (!Array.isArray(value)) return [];\n const entries: StyleLibraryEntry[] = [];\n const seen = new Set<string>();\n for (const raw of value) {\n if (!raw || typeof raw !== \"object\") continue;\n const candidate = raw as Partial<StyleLibraryEntry>;\n const id = typeof candidate.id === \"string\" ? candidate.id.trim() : \"\";\n const name = typeof candidate.name === \"string\" ? candidate.name.trim() : \"\";\n if (!id || !name || seen.has(id)) continue;\n const kind = STYLE_LIBRARY_ENTRY_KINDS.includes(candidate.kind as StyleLibraryEntryKind)\n ? (candidate.kind as StyleLibraryEntryKind)\n : \"style\";\n const style = restrictStylePatchToKind(sanitizeLayerStylePatch(candidate.style), kind);\n if (Object.keys(style).length === 0) continue;\n seen.add(id);\n const tags = Array.isArray(candidate.tags)\n ? [\n ...new Set(\n candidate.tags\n .filter((tag): tag is string => typeof tag === \"string\")\n .map((tag) => tag.trim())\n .filter((tag) => tag !== \"\"),\n ),\n ]\n : [];\n entries.push({\n id,\n name,\n kind,\n tags,\n style,\n updatedAt: typeof candidate.updatedAt === \"string\" ? candidate.updatedAt : \"\",\n });\n }\n return entries;\n}\n\n/**\n * Serialize Style Manager entries into the shareable bundle JSON written by\n * the Export action and read back by {@link parseStyleLibrary}.\n *\n * @param entries - The entries to export.\n * @returns Pretty-printed bundle JSON.\n */\nexport function serializeStyleLibrary(entries: StyleLibraryEntry[]): string {\n return JSON.stringify(\n {\n type: STYLE_LIBRARY_BUNDLE_TYPE,\n version: STYLE_LIBRARY_BUNDLE_VERSION,\n entries,\n },\n null,\n 2,\n );\n}\n\n/**\n * Parse a Style Manager bundle produced by {@link serializeStyleLibrary} (a\n * bare entries array is also accepted, so hand-authored files work). Entries\n * are normalized through {@link normalizeStyleLibraryEntries}.\n *\n * @param json - The bundle file content.\n * @returns The normalized entries.\n * @throws Error when the JSON is not a style-library bundle or holds no\n * usable entries.\n */\nexport function parseStyleLibrary(json: string): StyleLibraryEntry[] {\n let parsed: unknown;\n try {\n parsed = JSON.parse(json);\n } catch {\n throw new Error(\"Not a valid style library file (invalid JSON).\");\n }\n let rawEntries: unknown = null;\n if (Array.isArray(parsed)) {\n rawEntries = parsed;\n } else if (\n parsed &&\n typeof parsed === \"object\" &&\n (parsed as { type?: unknown }).type === STYLE_LIBRARY_BUNDLE_TYPE\n ) {\n // Refuse bundles from a newer format rather than misreading them with v1\n // semantics (e.g. coercing kinds this version does not know to \"style\").\n // Bare arrays stay accepted for hand-authored files.\n if ((parsed as { version?: unknown }).version !== STYLE_LIBRARY_BUNDLE_VERSION) {\n throw new Error(\"Unsupported style library version.\");\n }\n rawEntries = (parsed as { entries?: unknown }).entries;\n }\n if (rawEntries === null) {\n throw new Error(\"Not a valid style library file.\");\n }\n const entries = normalizeStyleLibraryEntries(rawEntries);\n if (entries.length === 0) {\n throw new Error(\"The style library file holds no usable entries.\");\n }\n return entries;\n}\n\n/**\n * Create a fresh unique id for a new library entry.\n *\n * @returns A random id (UUID when available).\n */\nexport function createStyleLibraryEntryId(): string {\n if (typeof crypto !== \"undefined\" && \"randomUUID\" in crypto) {\n return crypto.randomUUID();\n }\n return `style-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 10)}`;\n}\n\n/**\n * Built-in presets shown in the Style Manager so the library is not empty on\n * first open (issue #1294). Read-only: they are not persisted, cannot be\n * deleted, and their ids are namespaced with `preset-` so user entries never\n * collide. Names are plain English by the same convention as the ramp labels\n * in `color-ramp.ts`.\n */\nexport const BUILT_IN_STYLE_PRESETS: readonly StyleLibraryEntry[] = [\n {\n id: \"preset-boundary-outline\",\n name: \"Boundary outline\",\n kind: \"symbol\",\n tags: [\"boundary\", \"basemap\"],\n style: {\n fillColor: \"#4b5563\",\n fillOpacity: 0,\n strokeColor: \"#4b5563\",\n strokeWidth: 1.5,\n strokeWidthUnit: \"pixels\",\n fillPattern: \"none\",\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-boundary-bold\",\n name: \"Boundary outline (bold)\",\n kind: \"symbol\",\n tags: [\"boundary\", \"basemap\"],\n style: {\n fillColor: \"#111827\",\n fillOpacity: 0,\n strokeColor: \"#111827\",\n strokeWidth: 2.5,\n strokeWidthUnit: \"pixels\",\n fillPattern: \"none\",\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-muted-polygon\",\n name: \"Muted polygon\",\n kind: \"symbol\",\n tags: [\"basemap\", \"polygon\"],\n style: {\n fillColor: \"#e5e7eb\",\n fillOpacity: 0.35,\n strokeColor: \"#9ca3af\",\n strokeWidth: 1,\n fillPattern: \"none\",\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-highlight\",\n name: \"Highlight\",\n kind: \"symbol\",\n tags: [\"polygon\", \"selection\"],\n style: {\n fillColor: \"#facc15\",\n fillOpacity: 0.45,\n strokeColor: \"#d97706\",\n strokeWidth: 2,\n fillPattern: \"none\",\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-water\",\n name: \"Water\",\n kind: \"symbol\",\n tags: [\"polygon\", \"hydrology\"],\n style: {\n fillColor: \"#60a5fa\",\n fillOpacity: 0.5,\n strokeColor: \"#2563eb\",\n strokeWidth: 1,\n fillPattern: \"none\",\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-marker-pin\",\n name: \"Pin marker\",\n kind: \"symbol\",\n tags: [\"point\", \"marker\"],\n style: {\n markerEnabled: true,\n markerShape: \"pin\",\n markerColor: \"#dc2626\",\n markerSize: 22,\n circleRadius: 6,\n fillColor: \"#dc2626\",\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-labels-dark\",\n name: \"Labels (dark on light halo)\",\n kind: \"labels\",\n tags: [\"labels\"],\n style: {\n labels: {\n ...DEFAULT_LAYER_STYLE.labels,\n enabled: true,\n size: 12,\n color: \"#111827\",\n haloColor: \"#ffffff\",\n haloWidth: 1.5,\n },\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-ramp-viridis-5\",\n name: \"Viridis (5 classes, quantile)\",\n kind: \"ramp\",\n tags: [\"ramp\"],\n style: {\n vectorStyleColorRamp: \"viridis\",\n vectorStyleClassCount: 5,\n vectorStyleClassificationScheme: \"quantile\",\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-ramp-blues-5\",\n name: \"Blues (5 classes, equal interval)\",\n kind: \"ramp\",\n tags: [\"ramp\"],\n style: {\n vectorStyleColorRamp: \"blues\",\n vectorStyleClassCount: 5,\n vectorStyleClassificationScheme: \"equal-interval\",\n },\n updatedAt: \"\",\n },\n {\n id: \"preset-ramp-spectral-7\",\n name: \"Spectral (7 classes, quantile)\",\n kind: \"ramp\",\n tags: [\"ramp\"],\n style: {\n vectorStyleColorRamp: \"spectral\",\n vectorStyleClassCount: 7,\n vectorStyleClassificationScheme: \"quantile\",\n },\n updatedAt: \"\",\n },\n];\n","import type { GeoLibreLayer } from \"./types\";\n\n/**\n * Older projects and manually added Wayback templates may omit the native\n * zoom limit. Cap their requests at level 19 so renderers overzoom available\n * imagery instead of requesting missing higher-level tiles (issue #3116).\n * Explicit numeric limits remain authoritative, including lower regional limits.\n * Renderers ignore string limits, so only numeric minzoom can constrain this default.\n */\nexport function withRasterSourceDefaults(layer: GeoLibreLayer): GeoLibreLayer {\n if (\n ![\"raster\", \"xyz\", \"wmts\"].includes(layer.type) ||\n !layer.source ||\n (typeof layer.source.maxzoom === \"number\" && Number.isFinite(layer.source.maxzoom)) ||\n (typeof layer.source.minzoom === \"number\" && layer.source.minzoom > 19)\n ) {\n return layer;\n }\n const tiles =\n Array.isArray(layer.source.tiles) && layer.source.tiles.length\n ? layer.source.tiles\n : [layer.source.url];\n if (!tiles.every(isWaybackTileUrl)) return layer;\n return { ...layer, source: { ...layer.source, maxzoom: 19 } };\n}\n\nfunction isWaybackTileUrl(value: unknown): boolean {\n if (typeof value !== \"string\") return false;\n try {\n const url = new URL(value);\n return (\n (url.protocol === \"https:\" || url.protocol === \"http:\") &&\n /^wayback(?:-[a-z])?\\.maptiles\\.arcgis\\.com$/i.test(url.hostname) &&\n /^\\/arcgis\\/rest\\/services\\/World_Imagery\\/(?:WMTS\\/1\\.0\\.0\\/[^/]+\\/)?MapServer\\/tile\\/\\d+\\//i.test(\n url.pathname,\n )\n );\n } catch {\n return false;\n }\n}\n","// Binary GeoArrow / flat columnar vector representation for GeoLibre.\n//\n// Above feature-count thresholds, GeoJSON serialization, deep structured cloning,\n// and millions of individual JS heap objects create significant memory pressure,\n// GC churn, and latency.\n//\n// This module defines a zero-copy / contiguous typed-array representation compatible\n// with the Apache Arrow / GeoArrow specification:\n// - Coordinates are stored in a single contiguous Float64Array ([x0, y0, (z0), x1, y1, (z1), ...])\n// - Offsets (Int32Array) define boundaries for parts, lines, and polygon rings\n// - Properties can be passed as a columnar dictionary or row records\n// - Typed arrays can be transferred or cloned as buffers, but row properties and IDs\n// still require structured cloning. The tiler currently reconstructs GeoJSON.\n\nimport type {\n Feature,\n FeatureCollection,\n Geometry,\n LineString,\n MultiLineString,\n MultiPoint,\n MultiPolygon,\n Point,\n Polygon,\n Position,\n} from \"geojson\";\n\n/** Supported GeoArrow geometry types. */\nexport type GeoArrowGeometryType =\n | \"Point\"\n | \"MultiPoint\"\n | \"LineString\"\n | \"MultiLineString\"\n | \"Polygon\"\n | \"MultiPolygon\";\n\n/**\n * A contiguous columnar vector batch compatible with GeoArrow layout.\n */\nexport interface GeoArrowBatch {\n /** The primary geometry type of the features in this batch. */\n readonly geometryType: GeoArrowGeometryType;\n /**\n * Contiguous coordinates array: [x0, y0, (z0), x1, y1, (z1), ...].\n * Length is coordinateCount * coordDimensions.\n */\n readonly coordinates: Float64Array;\n /** Coordinate dimensions: 2 for XY (default), 3 for XYZ. */\n readonly coordDimensions: 2 | 3;\n /**\n * For Point: omitted (each feature has 1 position).\n * For LineString / MultiPoint: offset into `coordinates` (in vertex units, not ordinate units)\n * for each feature. Length = featureCount + 1.\n * For Polygon: offset into `ringOffsets` for each polygon. Length = featureCount + 1.\n */\n readonly geomOffsets?: Int32Array;\n /**\n * For Polygon / MultiLineString: offset into `coordinates` (in vertex units) for each ring or line.\n * Length = totalRingsCount + 1.\n */\n readonly ringOffsets?: Int32Array;\n /**\n * For MultiPolygon: offset into `geomOffsets` (or ringOffsets) for each feature.\n * Length = featureCount + 1.\n */\n readonly polygonOffsets?: Int32Array;\n /** Total number of features in this batch. */\n readonly featureCount: number;\n /** Feature properties as an array of row objects or column dictionary. */\n readonly properties?: ReadonlyArray<Record<string, unknown>>;\n /** Optional feature identifiers. */\n readonly featureIds?: ReadonlyArray<string | number | undefined>;\n /** Optional precomputed 2D bounding box [minX, minY, maxX, maxY]. */\n readonly bbox?: [number, number, number, number];\n}\n\n/**\n * Type guard for {@link GeoArrowBatch}.\n * Validates structure and basic buffer invariants without deep scanning.\n */\nexport function isGeoArrowBatch(value: unknown): value is GeoArrowBatch {\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as Partial<GeoArrowBatch>;\n if (\n ![\"Point\", \"MultiPoint\", \"LineString\", \"MultiLineString\", \"Polygon\", \"MultiPolygon\"].includes(\n candidate.geometryType ?? \"\",\n )\n )\n return false;\n if (!(candidate.coordinates instanceof Float64Array)) return false;\n if (candidate.coordDimensions !== 2 && candidate.coordDimensions !== 3) return false;\n if (!Number.isSafeInteger(candidate.featureCount) || candidate.featureCount! < 0) return false;\n if (candidate.coordinates.length % candidate.coordDimensions !== 0) return false;\n\n if (candidate.geomOffsets !== undefined && !(candidate.geomOffsets instanceof Int32Array)) {\n return false;\n }\n if (candidate.ringOffsets !== undefined && !(candidate.ringOffsets instanceof Int32Array)) {\n return false;\n }\n if (candidate.polygonOffsets !== undefined && !(candidate.polygonOffsets instanceof Int32Array)) {\n return false;\n }\n\n const vertexCount = candidate.coordinates.length / candidate.coordDimensions;\n const validOffsets = (offsets: Int32Array | undefined, length: number, end: number) => {\n if (!offsets || offsets.length !== length || offsets[0] !== 0) return false;\n for (let i = 1; i < offsets.length; i++) {\n if (offsets[i] < offsets[i - 1] || offsets[i] > end) return false;\n }\n return offsets[offsets.length - 1] === end;\n };\n const count = candidate.featureCount!;\n if (candidate.geometryType === \"Point\") {\n if (vertexCount !== count) return false;\n } else if (candidate.geometryType === \"LineString\" || candidate.geometryType === \"MultiPoint\") {\n if (!validOffsets(candidate.geomOffsets, count + 1, vertexCount)) return false;\n } else {\n const rings = (candidate.ringOffsets?.length ?? 0) - 1;\n if (!validOffsets(candidate.ringOffsets, rings + 1, vertexCount)) return false;\n if (candidate.geometryType === \"MultiPolygon\") {\n const polygons = (candidate.geomOffsets?.length ?? 0) - 1;\n if (!validOffsets(candidate.geomOffsets, polygons + 1, rings)) return false;\n if (!validOffsets(candidate.polygonOffsets, count + 1, polygons)) return false;\n } else if (!validOffsets(candidate.geomOffsets, count + 1, rings)) return false;\n }\n if (\n candidate.properties !== undefined &&\n (!Array.isArray(candidate.properties) || candidate.properties.length !== count)\n )\n return false;\n if (\n candidate.featureIds !== undefined &&\n (!Array.isArray(candidate.featureIds) || candidate.featureIds.length !== count)\n )\n return false;\n\n return true;\n}\n\n/**\n * Computes a 2D bounding box [minX, minY, maxX, maxY] for a {@link GeoArrowBatch}\n * in a single fast, vectorized pass over the flat coordinates buffer.\n * Returns null if the batch contains no finite coordinates.\n */\nexport function computeGeoArrowBBox(batch: GeoArrowBatch): [number, number, number, number] | null {\n if (batch.bbox) return [...batch.bbox];\n const { coordinates, coordDimensions } = batch;\n if (coordinates.length === 0) return null;\n\n let minX = Infinity;\n let minY = Infinity;\n let maxX = -Infinity;\n let maxY = -Infinity;\n\n const step = coordDimensions;\n for (let i = 0; i < coordinates.length; i += step) {\n const x = coordinates[i];\n const y = coordinates[i + 1];\n if (Number.isFinite(x) && Number.isFinite(y)) {\n if (x < minX) minX = x;\n if (y < minY) minY = y;\n if (x > maxX) maxX = x;\n if (y > maxY) maxY = y;\n }\n }\n\n return minX === Infinity ? null : [minX, minY, maxX, maxY];\n}\n\n/**\n * Convert a {@link GeoArrowBatch} to a standard GeoJSON {@link FeatureCollection}.\n * Lossless round-trip converter for renderers or components (Cesium, Attribute Table, Turf)\n * that expect standard GeoJSON objects.\n */\nexport function geoArrowToFeatureCollection(\n batch: GeoArrowBatch,\n): FeatureCollection<Geometry | null> {\n const {\n geometryType,\n coordinates,\n coordDimensions: dim,\n geomOffsets,\n ringOffsets,\n polygonOffsets,\n featureCount,\n properties,\n featureIds,\n } = batch;\n\n const features: Feature<Geometry | null>[] = new Array(featureCount);\n\n function readPosition(vertexIdx: number): Position {\n const o = vertexIdx * dim;\n return dim === 3\n ? [coordinates[o], coordinates[o + 1], coordinates[o + 2]]\n : [coordinates[o], coordinates[o + 1]];\n }\n\n function readLine(startVertex: number, endVertex: number): Position[] {\n const count = endVertex - startVertex;\n const line: Position[] = new Array(count);\n for (let i = 0; i < count; i++) {\n line[i] = readPosition(startVertex + i);\n }\n return line;\n }\n\n for (let i = 0; i < featureCount; i++) {\n const props = properties ? { ...properties[i] } : {};\n const id = featureIds ? featureIds[i] : undefined;\n\n let geometry: Geometry | null = null;\n\n switch (geometryType) {\n case \"Point\": {\n const o = i * dim;\n if (\n o + 1 < coordinates.length &&\n Number.isFinite(coordinates[o]) &&\n Number.isFinite(coordinates[o + 1])\n ) {\n geometry = {\n type: \"Point\",\n coordinates: readPosition(i),\n } as Point;\n }\n break;\n }\n case \"MultiPoint\": {\n if (geomOffsets) {\n const start = geomOffsets[i];\n const end = geomOffsets[i + 1];\n if (end > start) {\n geometry = {\n type: \"MultiPoint\",\n coordinates: readLine(start, end),\n } as MultiPoint;\n }\n }\n break;\n }\n case \"LineString\": {\n if (geomOffsets) {\n const start = geomOffsets[i];\n const end = geomOffsets[i + 1];\n if (end > start) {\n geometry = {\n type: \"LineString\",\n coordinates: readLine(start, end),\n } as LineString;\n }\n }\n break;\n }\n case \"MultiLineString\": {\n if (geomOffsets && ringOffsets) {\n const startLine = geomOffsets[i];\n const endLine = geomOffsets[i + 1];\n const lines: Position[][] = [];\n for (let l = startLine; l < endLine; l++) {\n const vStart = ringOffsets[l];\n const vEnd = ringOffsets[l + 1];\n if (vEnd > vStart) {\n lines.push(readLine(vStart, vEnd));\n }\n }\n if (lines.length > 0) {\n geometry = {\n type: \"MultiLineString\",\n coordinates: lines,\n } as MultiLineString;\n }\n }\n break;\n }\n case \"Polygon\": {\n if (geomOffsets && ringOffsets) {\n const startRing = geomOffsets[i];\n const endRing = geomOffsets[i + 1];\n const rings: Position[][] = [];\n for (let r = startRing; r < endRing; r++) {\n const vStart = ringOffsets[r];\n const vEnd = ringOffsets[r + 1];\n if (vEnd > vStart) {\n rings.push(readLine(vStart, vEnd));\n }\n }\n if (rings.length > 0) {\n geometry = {\n type: \"Polygon\",\n coordinates: rings,\n } as Polygon;\n }\n }\n break;\n }\n case \"MultiPolygon\": {\n if (polygonOffsets && geomOffsets && ringOffsets) {\n const startPoly = polygonOffsets[i];\n const endPoly = polygonOffsets[i + 1];\n const polys: Position[][][] = [];\n for (let p = startPoly; p < endPoly; p++) {\n const startRing = geomOffsets[p];\n const endRing = geomOffsets[p + 1];\n const rings: Position[][] = [];\n for (let r = startRing; r < endRing; r++) {\n const vStart = ringOffsets[r];\n const vEnd = ringOffsets[r + 1];\n if (vEnd > vStart) {\n rings.push(readLine(vStart, vEnd));\n }\n }\n if (rings.length > 0) polys.push(rings);\n }\n if (polys.length > 0) {\n geometry = {\n type: \"MultiPolygon\",\n coordinates: polys,\n } as MultiPolygon;\n }\n }\n break;\n }\n }\n\n features[i] = {\n type: \"Feature\",\n id,\n properties: props,\n geometry,\n };\n }\n\n return {\n type: \"FeatureCollection\",\n features,\n };\n}\n\nfunction extractVerticesForLineOrMultiPoint(\n geometry: Geometry | null | undefined,\n primaryType: \"LineString\" | \"MultiPoint\",\n): ReadonlyArray<Position> | null {\n if (!geometry) return null;\n if (primaryType === \"LineString\") {\n if (geometry.type === \"LineString\" && Array.isArray(geometry.coordinates)) {\n return geometry.coordinates;\n }\n return null;\n }\n if (primaryType === \"MultiPoint\") {\n if (geometry.type === \"MultiPoint\" && Array.isArray(geometry.coordinates)) {\n return geometry.coordinates;\n }\n if (geometry.type === \"Point\" && Array.isArray(geometry.coordinates)) {\n // Point promoted to MultiPoint: exactly one vertex\n return [geometry.coordinates];\n }\n return null;\n }\n return null;\n}\n\n/**\n * Flatten supported Point, MultiPoint, LineString and Polygon collections.\n * Empty collections produce an empty Point batch; unsupported geometries return null.\n */\nexport function featureCollectionToGeoArrow(\n collection: FeatureCollection<Geometry | null>,\n): GeoArrowBatch | null {\n const { features } = collection;\n if (!features || features.length === 0) {\n return {\n geometryType: \"Point\",\n coordinates: new Float64Array(0),\n coordDimensions: 2,\n featureCount: 0,\n properties: [],\n };\n }\n\n // Detect dominant geometry type\n let primaryType: GeoArrowGeometryType | null = null;\n let hasZ = false;\n\n for (const f of features) {\n const g = f.geometry;\n if (!g) continue;\n if (primaryType === null) {\n if (\n g.type === \"Point\" ||\n g.type === \"MultiPoint\" ||\n g.type === \"LineString\" ||\n g.type === \"Polygon\"\n ) {\n primaryType = g.type;\n } else {\n return null; // GeometryCollection or unsupported\n }\n } else if (g.type !== primaryType) {\n // Mixed geometry collection: promote Point -> MultiPoint, Polygon -> MultiPolygon, etc., or bail\n if (\n (primaryType === \"Point\" && g.type === \"MultiPoint\") ||\n (primaryType === \"MultiPoint\" && g.type === \"Point\")\n ) {\n primaryType = \"MultiPoint\";\n } else {\n return null; // Incompatible mixed types\n }\n }\n\n // Inspect every vertex: Z may first appear later in a line or ring.\n if (!hasZ) {\n const positionHasZ = (position: Position) =>\n position.length > 2 && Number.isFinite(position[2]);\n if (g.type === \"Point\") hasZ = positionHasZ(g.coordinates);\n else if (g.type === \"Polygon\") {\n hasZ = g.coordinates.some((ring) => ring.some(positionHasZ));\n } else if (g.type === \"LineString\" || g.type === \"MultiPoint\") {\n hasZ = g.coordinates.some(positionHasZ);\n }\n }\n }\n\n if (!primaryType) primaryType = \"Point\";\n const dim: 2 | 3 = hasZ ? 3 : 2;\n\n // Extract feature IDs and properties\n const featureCount = features.length;\n const properties: Record<string, unknown>[] = new Array(featureCount);\n const featureIds: (string | number | undefined)[] = new Array(featureCount);\n\n for (let i = 0; i < featureCount; i++) {\n properties[i] = features[i].properties ?? {};\n featureIds[i] = features[i].id;\n }\n\n // Handle Point specialization (no offsets needed)\n if (primaryType === \"Point\") {\n const coordinates = new Float64Array(featureCount * dim);\n for (let i = 0; i < featureCount; i++) {\n const g = features[i].geometry;\n const o = i * dim;\n if (g && g.type === \"Point\" && Array.isArray(g.coordinates)) {\n coordinates[o] = Number(g.coordinates[0] ?? NaN);\n coordinates[o + 1] = Number(g.coordinates[1] ?? NaN);\n if (dim === 3) coordinates[o + 2] = Number(g.coordinates[2] ?? 0);\n } else {\n coordinates[o] = NaN;\n coordinates[o + 1] = NaN;\n if (dim === 3) coordinates[o + 2] = NaN;\n }\n }\n const batch: GeoArrowBatch = {\n geometryType: \"Point\",\n coordinates,\n coordDimensions: dim,\n featureCount,\n properties,\n featureIds,\n };\n const bbox = computeGeoArrowBBox(batch);\n return bbox ? { ...batch, bbox } : batch;\n }\n\n // Handle LineString / MultiPoint (1 level of offsets)\n if (primaryType === \"LineString\" || primaryType === \"MultiPoint\") {\n const geomOffsets = new Int32Array(featureCount + 1);\n let totalVertices = 0;\n\n for (let i = 0; i < featureCount; i++) {\n geomOffsets[i] = totalVertices;\n const vertices = extractVerticesForLineOrMultiPoint(features[i].geometry, primaryType);\n if (vertices) {\n totalVertices += vertices.length;\n }\n }\n geomOffsets[featureCount] = totalVertices;\n\n const coordinates = new Float64Array(totalVertices * dim);\n let vIdx = 0;\n\n for (let i = 0; i < featureCount; i++) {\n const vertices = extractVerticesForLineOrMultiPoint(features[i].geometry, primaryType);\n if (vertices) {\n for (const coord of vertices) {\n const o = vIdx * dim;\n coordinates[o] = Number(coord[0] ?? NaN);\n coordinates[o + 1] = Number(coord[1] ?? NaN);\n if (dim === 3) coordinates[o + 2] = Number(coord[2] ?? 0);\n vIdx++;\n }\n }\n }\n\n const batch: GeoArrowBatch = {\n geometryType: primaryType,\n coordinates,\n coordDimensions: dim,\n geomOffsets,\n featureCount,\n properties,\n featureIds,\n };\n const bbox = computeGeoArrowBBox(batch);\n return bbox ? { ...batch, bbox } : batch;\n }\n\n // Handle Polygon (2 levels of offsets: geomOffsets -> ringOffsets -> coordinates)\n if (primaryType === \"Polygon\") {\n const geomOffsets = new Int32Array(featureCount + 1);\n let totalRings = 0;\n let totalVertices = 0;\n\n for (let i = 0; i < featureCount; i++) {\n geomOffsets[i] = totalRings;\n const g = features[i].geometry;\n if (g && g.type === \"Polygon\" && Array.isArray(g.coordinates)) {\n totalRings += g.coordinates.length;\n for (const ring of g.coordinates) {\n totalVertices += ring.length;\n }\n }\n }\n geomOffsets[featureCount] = totalRings;\n\n const ringOffsets = new Int32Array(totalRings + 1);\n const coordinates = new Float64Array(totalVertices * dim);\n let rIdx = 0;\n let vIdx = 0;\n\n for (let i = 0; i < featureCount; i++) {\n const g = features[i].geometry;\n if (g && g.type === \"Polygon\" && Array.isArray(g.coordinates)) {\n for (const ring of g.coordinates) {\n ringOffsets[rIdx++] = vIdx;\n for (const coord of ring) {\n const o = vIdx * dim;\n coordinates[o] = Number(coord[0] ?? NaN);\n coordinates[o + 1] = Number(coord[1] ?? NaN);\n if (dim === 3) coordinates[o + 2] = Number(coord[2] ?? 0);\n vIdx++;\n }\n }\n }\n }\n ringOffsets[totalRings] = vIdx;\n\n const batch: GeoArrowBatch = {\n geometryType: \"Polygon\",\n coordinates,\n coordDimensions: dim,\n geomOffsets,\n ringOffsets,\n featureCount,\n properties,\n featureIds,\n };\n const bbox = computeGeoArrowBBox(batch);\n return bbox ? { ...batch, bbox } : batch;\n }\n\n return null;\n}\n","import { DEFAULT_LAYER_STYLE, type GeoLibreLayer } from \"./types\";\n\n/**\n * CZML (Cesium Language) domain models and layer authoring (issue #2290).\n *\n * CZML is a JSON-based format for describing dynamic, time-varying 3D geospatial\n * scenes on the Cesium globe (trajectories, satellites, vehicles, sensor\n * networks, moving entities with orientations and paths). The globe loads CZML\n * natively through `CzmlDataSource`; the 2D map has no equivalent, so CZML\n * layers are badged \"3D only\" there.\n */\n\n/** `metadata.sourceKind` of a layer that carries or references CZML data. */\nexport const CZML_SOURCE_KIND = \"czml\";\n\n/** One CZML packet in a document stream. */\nexport type CzmlPacket = Record<string, unknown>;\n\n/** A CZML document represented as an array of packets. */\nexport type CzmlDocument = CzmlPacket[];\n\n/** Minimal Point sample in CZML. */\nexport const CZML_SAMPLE_POINT: CzmlPacket[] = [\n {\n id: \"document\",\n name: \"CZML Point Sample\",\n version: \"1.0\",\n },\n {\n id: \"point 1\",\n name: \"Extruded Point\",\n position: {\n cartographicDegrees: [-75.59777, 40.03883, 1000],\n },\n point: {\n color: {\n rgba: [255, 128, 0, 255],\n },\n pixelSize: 14,\n outlineColor: {\n rgba: [255, 255, 255, 255],\n },\n outlineWidth: 2,\n },\n },\n];\n\n/** Time-dynamic trajectory sample with an orbit track and clock. */\nexport const CZML_SAMPLE_DYNAMIC: CzmlPacket[] = [\n {\n id: \"document\",\n name: \"CZML Trajectory Sample\",\n version: \"1.0\",\n clock: {\n interval: \"2026-09-09T00:00:00Z/2026-09-09T02:00:00Z\",\n currentTime: \"2026-09-09T00:00:00Z\",\n multiplier: 60,\n range: \"LOOP_STOP\",\n },\n },\n {\n id: \"satellite\",\n name: \"Satellite Track\",\n availability: \"2026-09-09T00:00:00Z/2026-09-09T02:00:00Z\",\n path: {\n material: {\n solidColor: {\n color: {\n rgba: [0, 200, 255, 255],\n },\n },\n },\n width: 2,\n leadTime: 1800,\n trailTime: 1800,\n },\n position: {\n epoch: \"2026-09-09T00:00:00Z\",\n cartographicDegrees: [\n 0, -75, 40, 250000, 1800, -30, 20, 250000, 3600, 20, 0, 250000, 5400, 70, -20, 250000, 7200,\n 120, -40, 250000,\n ],\n },\n point: {\n color: {\n rgba: [255, 255, 255, 255],\n },\n pixelSize: 10,\n },\n },\n];\n\n/** One-click CZML samples offered in the Add Data dialog. */\nexport const CZML_QUICK_PICKS: ReadonlyArray<{\n name: string;\n data: CzmlPacket[];\n}> = [\n { name: \"CZML Point\", data: CZML_SAMPLE_POINT },\n { name: \"CZML Dynamic Trajectory\", data: CZML_SAMPLE_DYNAMIC },\n];\n\n/**\n * Parse and validate a CZML payload from a JSON string or raw object.\n * Returns an array of CZML packets or null when the input is malformed.\n */\nexport function parseCzml(value: unknown): CzmlPacket[] | null {\n if (!value) return null;\n let parsed = value;\n if (typeof value === \"string\") {\n const trimmed = value.trim();\n if (!trimmed.startsWith(\"[\") && !trimmed.startsWith(\"{\")) return null;\n try {\n parsed = JSON.parse(trimmed);\n } catch {\n return null;\n }\n }\n if (Array.isArray(parsed)) {\n return parsed.length > 0 && parsed.every((item) => item && typeof item === \"object\")\n ? (parsed as CzmlPacket[])\n : null;\n }\n if (parsed && typeof parsed === \"object\") {\n return [parsed as CzmlPacket];\n }\n return null;\n}\n\n/**\n * Whether `layer` represents a CZML dynamic 3D scene.\n */\nexport function isCzmlLayer(layer: Pick<GeoLibreLayer, \"source\" | \"metadata\">): boolean {\n return layer.metadata?.sourceKind === CZML_SOURCE_KIND;\n}\n\n/** Extracted CZML source definition for a layer. */\nexport interface CzmlSource {\n url?: string;\n data?: CzmlPacket[] | string;\n}\n\n/**\n * Extract the CZML content and/or URL from a layer, or null if not a CZML layer.\n */\nexport function czmlSource(layer: Pick<GeoLibreLayer, \"source\" | \"metadata\">): CzmlSource | null {\n if (!isCzmlLayer(layer)) return null;\n const raw = layer.source?.czmlData as CzmlPacket[] | CzmlPacket | string | undefined;\n // An empty packet array is a document with nothing in it, not a document; a\n // bare packet (the Python API accepts one) is wrapped the way `parseCzml`\n // does, since Cesium indexes a document as an array.\n const data = Array.isArray(raw)\n ? raw.length > 0\n ? raw\n : undefined\n : raw && typeof raw === \"object\"\n ? [raw]\n : raw || undefined;\n const rawUrl = layer.source?.url;\n const url = typeof rawUrl === \"string\" && rawUrl.trim() ? rawUrl.trim() : undefined;\n if (!data && !url) return null;\n return { url, data };\n}\n\n/** Generate a unique identifier for a newly created layer. */\nfunction newLayerId(): string {\n return typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\n/**\n * Options for constructing a CZML layer.\n */\nexport interface CzmlLayerOptions {\n /** Optional layer id; defaults to a new UUID. */\n id?: string;\n /** Display name of the layer in the Layers panel. */\n name: string;\n /** Inlined CZML packet array or serialized JSON string. */\n data?: CzmlPacket[] | string;\n /** Remote URL pointing to a .czml document. */\n url?: string;\n /** Local path when loaded from disk. */\n sourcePath?: string;\n /** Credit shown in the globe's attribution control while the layer is present. */\n attribution?: string;\n}\n\n/**\n * Build a typed GeoLibreLayer representing a CZML dynamic scene on the Cesium globe.\n */\nexport function createCzmlLayer(options: CzmlLayerOptions): GeoLibreLayer {\n const id = options.id ?? newLayerId();\n const data = options.data;\n const url = options.url?.trim() || undefined;\n const sourcePath = options.sourcePath?.trim() || undefined;\n\n return {\n id,\n name: options.name,\n type: \"3d-tiles\",\n ...(sourcePath ? { sourcePath } : {}),\n source: {\n type: \"3d-tiles\",\n sourceId: id,\n ...(data !== undefined ? { czmlData: data } : {}),\n ...(url ? { url } : {}),\n ...(sourcePath ? { sourcePath } : {}),\n ...(options.attribution?.trim() ? { attribution: options.attribution.trim() } : {}),\n },\n visible: true,\n opacity: 1,\n style: { ...DEFAULT_LAYER_STYLE },\n metadata: {\n sourceKind: CZML_SOURCE_KIND,\n externalNativeLayer: true,\n // Cesium builds real entities from the document and\n // `CesiumLayerSync.resolveFeature` answers for them, so a click can read a\n // packet's name and custom `properties` (issue #2504). Positions are\n // time-dynamic properties rather than stored geometry, so the answer\n // carries no geometry.\n identifiable: true,\n sourceId: id,\n nativeLayerIds: [id],\n },\n };\n}\n","import { DEFAULT_LAYER_STYLE, type GeoLibreLayer } from \"./types\";\n\n/** Native KML/KMZ documents retain their own styling on the globe. */\nexport const CESIUM_KML_SOURCE_KIND = \"cesium-kml\";\n\nexport function isCesiumKmlLayer(layer: Pick<GeoLibreLayer, \"metadata\">): boolean {\n return layer.metadata?.sourceKind === CESIUM_KML_SOURCE_KIND;\n}\n\nexport interface CesiumKmlLayerOptions {\n id?: string;\n name: string;\n /** HTTP(S) KML or KMZ URL. Relative resources resolve against this URL. */\n url?: string;\n /** Inline XML, or a KMZ data URL so local archives survive project saves. */\n data?: string;\n sourcePath?: string;\n}\n\nexport function cesiumKmlSource(layer: Pick<GeoLibreLayer, \"source\" | \"metadata\">): string | null {\n if (!isCesiumKmlLayer(layer)) return null;\n for (const value of [layer.source.kmlData, layer.source.url]) {\n if (typeof value === \"string\" && value.trim()) return value.trim();\n }\n return null;\n}\n\nexport function createCesiumKmlLayer(options: CesiumKmlLayerOptions): GeoLibreLayer {\n const data = options.data?.trim();\n const url = options.url?.trim();\n if (!data && !url) throw new Error(\"Provide a KML/KMZ document or URL.\");\n const id = options.id ?? crypto.randomUUID();\n return {\n id,\n name: options.name,\n type: \"3d-tiles\",\n ...(options.sourcePath ? { sourcePath: options.sourcePath } : {}),\n source: { type: \"3d-tiles\", sourceId: id, ...(data ? { kmlData: data } : { url }) },\n visible: true,\n opacity: 1,\n style: { ...DEFAULT_LAYER_STYLE },\n metadata: {\n sourceKind: CESIUM_KML_SOURCE_KIND,\n externalNativeLayer: true,\n identifiable: false,\n },\n };\n}\n","import { isCzmlLayer } from \"./czml\";\nimport { isCesiumKmlLayer } from \"./cesium-kml\";\nimport { DEFAULT_LAYER_STYLE, type GeoLibreLayer } from \"./types\";\n\n// Cesium Ion assets (issue #2290): 3D Tiles and imagery referenced by Ion\n// asset id rather than by URL. The globe loads them natively through the Ion\n// token the app already holds for World Terrain; the 2D map has no equivalent,\n// so these layers are flagged \"3D only\" there and never painted.\n\n/** `metadata.sourceKind` of a layer that references a Cesium Ion asset. */\nexport const CESIUM_ION_SOURCE_KIND = \"cesium-ion\";\n\n/** What an Ion asset id resolves to on the globe. */\nexport type CesiumIonAssetKind = \"3d-tiles\" | \"imagery\";\n\n/** Cesium OSM Buildings, the one-click 3D Tiles asset every Ion account can use. */\nexport const CESIUM_OSM_BUILDINGS_ASSET_ID = 96188;\n\n/** Bing Maps Aerial through Ion, the imagery asset every Ion account can use. */\nexport const CESIUM_BING_AERIAL_ASSET_ID = 2;\n\n/**\n * Google Photorealistic 3D Tiles served through Ion. Unlike the two above it\n * is not free on every plan: Ion accounts without it get a 403 the globe\n * surfaces as a layer error.\n */\nexport const CESIUM_GOOGLE_PHOTOREALISTIC_ASSET_ID = 2275207;\n\n/**\n * Which tier of the Ion catalog a quick pick comes from. `global` assets ship\n * with every Ion account; `depot` assets are Asset Depot samples the account\n * has to add once at ion.cesium.com before they stream (until then the\n * endpoint 404s and the globe reports a layer error).\n */\nexport type CesiumIonQuickPickGroup = \"global\" | \"depot\";\n\nexport interface CesiumIonQuickPick {\n assetId: number;\n kind: CesiumIonAssetKind;\n name: string;\n group: CesiumIonQuickPickGroup;\n}\n\n/**\n * Ion assets offered as one-click picks in the Add Data dialog. Asset ids are\n * the dropdown's option values, so they have to stay unique; the ids below are\n * the ones Cesium's own samples use.\n */\nexport const CESIUM_ION_QUICK_PICKS: ReadonlyArray<CesiumIonQuickPick> = [\n {\n assetId: CESIUM_OSM_BUILDINGS_ASSET_ID,\n kind: \"3d-tiles\",\n name: \"Cesium OSM Buildings\",\n group: \"global\",\n },\n {\n assetId: CESIUM_BING_AERIAL_ASSET_ID,\n kind: \"imagery\",\n name: \"Bing Maps Aerial (Ion)\",\n group: \"global\",\n },\n {\n assetId: CESIUM_GOOGLE_PHOTOREALISTIC_ASSET_ID,\n kind: \"3d-tiles\",\n name: \"Google Photorealistic 3D Tiles\",\n group: \"global\",\n },\n { assetId: 2602291, kind: \"3d-tiles\", name: \"Japan 3D Building Data\", group: \"depot\" },\n { assetId: 69380, kind: \"3d-tiles\", name: \"Melbourne Photogrammetry\", group: \"depot\" },\n { assetId: 43978, kind: \"3d-tiles\", name: \"Melbourne Point Cloud\", group: \"depot\" },\n { assetId: 28945, kind: \"3d-tiles\", name: \"Montreal Point Cloud\", group: \"depot\" },\n { assetId: 75343, kind: \"3d-tiles\", name: \"New York City 3D Buildings\", group: \"depot\" },\n { assetId: 3827, kind: \"imagery\", name: \"Washington DC 2017\", group: \"depot\" },\n];\n\n/** Parse an Ion asset id the way the Add Data form and the project file carry it. */\nexport function parseCesiumIonAssetId(value: unknown): number | null {\n const id = typeof value === \"string\" ? Number(value.trim()) : value;\n return typeof id === \"number\" && Number.isSafeInteger(id) && id > 0 ? id : null;\n}\n\n/**\n * The Ion asset id a layer references, or null when the layer is not an Ion\n * asset layer (or carries no usable id).\n */\nexport function cesiumIonAssetId(layer: Pick<GeoLibreLayer, \"source\" | \"metadata\">): number | null {\n if (layer.metadata?.sourceKind !== CESIUM_ION_SOURCE_KIND) return null;\n return parseCesiumIonAssetId(layer.source?.ionAssetId);\n}\n\n/** Whether `layer` references a Cesium Ion asset. */\nexport function isCesiumIonLayer(layer: Pick<GeoLibreLayer, \"source\" | \"metadata\">): boolean {\n return cesiumIonAssetId(layer) !== null;\n}\n\n/** What the Ion asset draws as: a tileset primitive or an imagery layer. */\nexport function cesiumIonAssetKind(layer: Pick<GeoLibreLayer, \"type\">): CesiumIonAssetKind {\n return layer.type === \"3d-tiles\" ? \"3d-tiles\" : \"imagery\";\n}\n\n/**\n * Whether only the 3D globe can render `layer`: the mirror of the globe's\n * `isCesiumSupportedLayerType`, for the Layers panel to badge on the 2D map.\n */\nexport function isCesiumOnlyLayer(layer: Pick<GeoLibreLayer, \"source\" | \"metadata\">): boolean {\n return isCesiumIonLayer(layer) || isCzmlLayer(layer) || isCesiumKmlLayer(layer);\n}\n\nfunction newLayerId(): string {\n return typeof crypto !== \"undefined\" && \"randomUUID\" in crypto\n ? crypto.randomUUID()\n : `${Date.now()}-${Math.random().toString(36).slice(2)}`;\n}\n\nexport interface CesiumIonLayerOptions {\n id?: string;\n name: string;\n assetId: number;\n kind: CesiumIonAssetKind;\n /** Metres to shift a tileset vertically; ignored for imagery. */\n altitudeOffset?: number;\n}\n\n/**\n * Build the store layer for a Cesium Ion asset. A tileset is a `3d-tiles`\n * layer whose `sourceKind` keeps it out of the 2D deck.gl control; imagery is\n * a `raster` layer with no tiles, marked external so the 2D sync leaves it\n * alone. Both persist only the asset id: the token is runtime configuration.\n */\nexport function createCesiumIonLayer(options: CesiumIonLayerOptions): GeoLibreLayer {\n const id = options.id ?? newLayerId();\n const isTileset = options.kind === \"3d-tiles\";\n const altitudeOffset = Number.isFinite(options.altitudeOffset) ? options.altitudeOffset! : 0;\n return {\n id,\n name: options.name,\n type: isTileset ? \"3d-tiles\" : \"raster\",\n source: {\n type: isTileset ? \"3d-tiles\" : \"raster\",\n ionAssetId: options.assetId,\n sourceId: id,\n ...(isTileset ? { altitudeOffset } : {}),\n },\n visible: true,\n opacity: 1,\n style: { ...DEFAULT_LAYER_STYLE },\n metadata: {\n sourceKind: CESIUM_ION_SOURCE_KIND,\n externalNativeLayer: true,\n identifiable: false,\n sourceId: id,\n nativeLayerIds: [id],\n ...(isTileset ? { customLayerType: \"3d-tiles\", altitudeOffset } : {}),\n },\n };\n}\n","/**\n * `${NAME}` references in request header values.\n *\n * A header such as `Authorization: Bearer ${TILES_TOKEN}` is saved as typed\n * and resolved from the project's Environment Variables when a request is\n * made, so the secret itself never sits on the layer. This module is pure (no\n * store import): `credentials.ts` depends on it, and the store depends on\n * `credentials.ts`.\n */\n\n/** `${NAME}` where NAME follows the environment-variable name rule. */\nconst HEADER_REFERENCE = /\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g;\nconst HAS_HEADER_REFERENCE = /\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}/;\n/**\n * Optional auth scheme word, whitespace, one reference, nothing else. Kept in\n * lockstep with `_HEADER_REFERENCE_ONLY` in `python/src/geolibre/project.py`.\n */\nconst REFERENCE_ONLY = /^(?:[A-Za-z][A-Za-z0-9._-]*\\s+)?\\$\\{[A-Za-z_][A-Za-z0-9_]*\\}$/;\n\nconst warnedMissing = new Set<string>();\n\nexport function hasHeaderReferences(headers: Record<string, string> | undefined): boolean {\n if (!headers) return false;\n return Object.values(headers).some(\n (value) => typeof value === \"string\" && HAS_HEADER_REFERENCE.test(value),\n );\n}\n\n/**\n * Whether `value` carries no secret of its own: at most a scheme word such as\n * `Bearer` followed by a single `${NAME}`. Redaction keeps such values.\n */\nexport function isHeaderReferenceOnly(value: string): boolean {\n return REFERENCE_ONLY.test(value.trim());\n}\n\n/**\n * Whether credential-bearing request headers may be sent to `url`: HTTPS, or\n * plain HTTP to loopback so a local dev server still works. The scheme is read\n * off a parsed URL, so an unusually cased `HTTPS://` is not misread as\n * plaintext; a relative or unparseable URL is refused.\n */\nexport function allowsCredentialHeaders(url: string): boolean {\n try {\n const { protocol, hostname } = new URL(url);\n if (protocol === \"https:\") return true;\n return (\n protocol === \"http:\" &&\n (hostname === \"localhost\" || hostname === \"127.0.0.1\" || hostname === \"[::1]\")\n );\n } catch {\n return false;\n }\n}\n\n/**\n * `headers` with every `${NAME}` replaced by `values[NAME]`. A header that\n * references an unset (or empty) variable is omitted rather than sent with a\n * hole in it. Returns the input unchanged when nothing references a variable,\n * and `undefined` when no header remains.\n */\nexport function resolveHeaderReferences(\n headers: Record<string, string> | undefined,\n values: Readonly<Record<string, string>>,\n): Record<string, string> | undefined {\n if (!headers) return undefined;\n if (!Object.values(headers).some((value) => typeof value === \"string\" && value.includes(\"${\"))) {\n return headers;\n }\n const resolved: Record<string, string> = {};\n for (const [name, value] of Object.entries(headers)) {\n if (typeof value !== \"string\") continue;\n let missing: string | null = null;\n const next = value.replace(HEADER_REFERENCE, (_match, variable: string) => {\n const replacement = Object.hasOwn(values, variable) ? values[variable] : \"\";\n if (!replacement) missing ??= variable;\n return replacement;\n });\n if (missing !== null) {\n const warnKey = `${name}:${missing}`;\n if (!warnedMissing.has(warnKey)) {\n warnedMissing.add(warnKey);\n console.warn(\n `[GeoLibre] Request header \"${name}\" references unset environment variable ${missing}; the header is not sent.`,\n );\n }\n continue;\n }\n resolved[name] = next;\n }\n return Object.keys(resolved).length > 0 ? resolved : undefined;\n}\n","import { isHeaderReferenceOnly } from \"./header-references\";\nimport type { GeoLibreProject, LayerConnection } from \"./types\";\n\n/**\n * Project fields whose values are credentials. Keep this registry as the\n * schema-level inventory. Keep the explicit preference/plugin handling in\n * redactProjectCredentials in sync when adding entries outside layer config.\n */\nexport const PROJECT_CREDENTIAL_FIELDS = {\n preferences: [\"environmentVariables\", \"geocoding.apiKeys\"],\n layerConfiguration: [\n \"requestHeaders\",\n \"headers\",\n \"authorization\",\n \"apiKey\",\n \"apiKeys\",\n \"accessToken\",\n \"token\",\n \"password\",\n \"clientSecret\",\n \"connectionString\",\n \"secret\",\n \"bearer\",\n \"auth\",\n \"authKey\",\n \"sasToken\",\n \"subscriptionKey\",\n \"signature\",\n \"pwd\",\n ],\n pluginState: [\"plugins.settings\"],\n} as const;\n\n/**\n * Plugin settings that survive redaction: plugin id to the sub-keys kept from\n * its blob, or `null` to keep the whole blob.\n *\n * A plugin's settings are free-form and an external plugin can persist a\n * credential there, so the default is to drop all of it. What is listed here is\n * map-control *composition* — the swipe split, legend entries and colors, the\n * colorbar's range and ramp — which is what a shared or exported project needs\n * in order to render the map it was built as, and is structured rather than\n * free text. Dropping it silently stripped every legend, colorbar, and swipe\n * from an export.\n *\n * The components plugin's `html` sub-key is deliberately absent: it holds a\n * custom HTML panel the user authored by hand (`ComponentHtmlGuiEntryState`),\n * so it can carry anything, including a URL with a token in it. It is dropped\n * like any unknown blob. Mirrored by `PUBLISHABLE_PLUGIN_SETTINGS` in\n * `python/src/geolibre/project.py`; the two must agree.\n */\nexport const PUBLISHABLE_PLUGIN_SETTINGS: Readonly<Record<string, readonly string[] | null>> = {\n \"maplibre-gl-swipe\": null,\n \"maplibre-gl-components\": [\"legend\", \"colorbar\"],\n // This is the source of truth for dock-owned temporal layers. Dropping it\n // leaves only their external-native store mirrors in a shared project; those\n // mirrors contain a MapLibre source id, not enough information to recreate\n // the COG/mosaic/tiles for a recipient. The retained blob is still passed\n // through redactConfigurationValue below, including every source URL.\n \"maplibre-gl-time-slider\": null,\n // Feed toggles (one boolean per feed) plus a numeric clock speed — no URLs,\n // no keys, nothing user-authored. Listed as a whole blob rather than by key\n // because the feed set grows with every new feed; an enumerated list would\n // silently start counting each new toggle as a credential, which is the bug\n // this entry fixes. Still swept by redactConfigurationValue below.\n \"gods-eye-view\": null,\n // Point class edits keyed by (node key, index): compressed numbers, no\n // user text. Source URLs are values, so the sweep below still scrubs a\n // credentialed one.\n \"geolibre-point-cloud-annotation\": null,\n // The server URL, the map's publish key and a layer-id-to-dataset-key map.\n // The sub-keys are listed rather than the blob, so a key a later change stores\n // here is dropped instead of published. Publish keys are identifiers, not\n // credentials.\n \"maplibre-gl-geolens\": [\"serverUrl\", \"mapKey\", \"layerKeys\"],\n};\n\nexport interface CredentialRedactionResult {\n project: GeoLibreProject;\n /** Stable project paths removed or rewritten by the redaction pass. */\n redactedPaths: string[];\n /**\n * Opaque `path=hash` fingerprints identifying *which* secret sat at each\n * redacted path. A caller that remembers an explicit \"keep credentials\"\n * decision compares these between saves, so a different credential, such as\n * a swapped layer or rotated token, is confirmed again instead of being\n * written to disk under the earlier answer. Paths alone cannot carry that:\n * they are positional, so a replacement layer inherits the path of the one it\n * replaced.\n */\n redactedFingerprints: string[];\n /**\n * Whether any redacted value could not be serialized, and so has no entry in\n * {@link redactedFingerprints}. Such a value cannot be compared with what the\n * user accepted, so a caller reusing a remembered decision must ask again\n * rather than assume the unfingerprintable credential is unchanged.\n */\n hasUnfingerprintableCredential: boolean;\n /** Number of individual credential-bearing fields removed or rewritten. */\n redactedCount: number;\n}\n\n/** Collector threaded through the redaction pass. */\ninterface RedactionAccumulator {\n paths: string[];\n fingerprints: string[];\n count: number;\n /** Whether some redacted value could not be serialized, and so not fingerprinted. */\n unfingerprintable: boolean;\n}\n\n/**\n * Hash a credential value to a short, stable 64-bit token: two independent\n * FNV-1a-style lanes, the first with the canonical FNV prime and the second\n * with a MurmurHash3 mixing constant so the lanes do not move together.\n *\n * The digest never leaves memory and is only ever compared for equality, so it\n * needs to be stable and cheap rather than cryptographic. Hashing rather than\n * retaining the value keeps the secret itself out of the remembered choices.\n *\n * A collision costs a *missed* confirmation, not a spurious one: a caller\n * comparing fingerprints would treat a different secret as one the user already\n * accepted. That is why this is 64 bits rather than 32, and why a value that\n * cannot be serialized returns null rather than hashing a lossy `String(value)`\n * that would collapse every such value onto `[object Object]`.\n *\n * @returns The digest, or null when the value cannot be serialized.\n */\nfunction fingerprintValue(value: unknown): string | null {\n let serialized: string;\n try {\n serialized = JSON.stringify(value) ?? String(value);\n } catch {\n return null;\n }\n let low = 0x811c9dc5;\n let high = 0x27220a95;\n for (let index = 0; index < serialized.length; index += 1) {\n const code = serialized.charCodeAt(index);\n low = Math.imul(low ^ code, 0x01000193);\n high = Math.imul(high ^ code, 0x85ebca6b);\n }\n return `${(low >>> 0).toString(36)}.${(high >>> 0).toString(36)}`;\n}\n\n/** Record one redaction, with the fingerprint of the value being removed. */\nfunction recordRedaction(\n accumulator: RedactionAccumulator,\n path: string,\n value: unknown,\n count = 1,\n): void {\n const digest = fingerprintValue(value);\n accumulator.paths.push(path);\n // A value that cannot be serialized fails closed: it is reported through\n // `hasUnfingerprintableCredential` rather than fingerprinted, so a caller\n // asks again instead of reusing an answer it cannot verify still applies.\n if (digest === null) accumulator.unfingerprintable = true;\n else accumulator.fingerprints.push(`${path}=${digest}`);\n accumulator.count += count;\n}\n\n/**\n * Fold the spellings of one credential name together, so `apiKey`, `api_key`,\n * `api-key`, and `APIKEY` are a single registry entry on both the object-key\n * and the URL-parameter side.\n */\nfunction normalizeCredentialName(name: string): string {\n return name.toLowerCase().replace(/[-_]/g, \"\");\n}\n\nconst SENSITIVE_KEYS = new Set(\n PROJECT_CREDENTIAL_FIELDS.layerConfiguration.map(normalizeCredentialName),\n);\n/**\n * Credential names in query strings. This is the wider of the two registries by\n * design: everything here is also an object-key credential *except* `key` and\n * the Azure SAS positional parameters (`sv`/`sr`/`st`/`se`/`sp`/`sig`/`skoid`),\n * which are only credentials because of where they appear. As configuration\n * field names they collide with ordinary state — `sr` is a spatial reference on\n * an ArcGIS source, `key` is a generic identifier — so matching them on object\n * keys would silently drop layer configuration rather than a secret.\n */\nconst URL_CREDENTIAL_PARAMS = new Set(\n [\n ...PROJECT_CREDENTIAL_FIELDS.layerConfiguration,\n \"key\",\n \"sig\",\n \"se\",\n \"sp\",\n \"sv\",\n \"sr\",\n \"st\",\n \"skoid\",\n ].map(normalizeCredentialName),\n);\n/**\n * Depth at which the redaction pass stops descending and fails closed.\n * Exported so a caller that predicts what redaction will remove (the Share\n * dialog's readiness check) scans exactly as deep as this pass does, instead of\n * keeping a second, shallower cap that would silently disagree.\n */\nexport const MAX_REDACT_DEPTH = 12;\n\n/** Whether an object key in layer/plugin configuration holds a credential. */\nexport function isCredentialFieldName(name: string): boolean {\n return SENSITIVE_KEYS.has(normalizeCredentialName(name));\n}\n\nfunction isCredentialParam(name: string): boolean {\n let decoded = name.replace(/\\+/g, \" \");\n try {\n decoded = decodeURIComponent(decoded);\n } catch {\n // Malformed names cannot match the registry, but must not break export.\n }\n const lowered = decoded.toLowerCase();\n return (\n URL_CREDENTIAL_PARAMS.has(normalizeCredentialName(lowered)) || lowered.startsWith(\"x-amz-\")\n );\n}\n\n/**\n * Whether a URL query-parameter name carries a credential. This is the wider of\n * the two registries (see `URL_CREDENTIAL_PARAMS`). Exported so a caller that\n * must leave the parameter in place — the native diagnostics log rewrites its\n * value to `[redacted]` rather than dropping the pair — shares this registry\n * instead of keeping a second, narrower list that can drift from it.\n */\nexport function isCredentialUrlParam(name: string): boolean {\n return isCredentialParam(name);\n}\n\nfunction redactParameterString(value: string): string {\n return value\n .split(\"&\")\n .filter((pair) => pair !== \"\" && !isCredentialParam(pair.split(\"=\", 1)[0]))\n .join(\"&\");\n}\n\n/**\n * Remove credentials from URL-shaped values without encoding tile-template\n * placeholders such as `{z}/{x}/{y}`.\n */\nexport function redactUrlCredentials(value: string): string {\n const hashIndex = value.indexOf(\"#\");\n const beforeHash = hashIndex === -1 ? value : value.slice(0, hashIndex);\n const fragment = hashIndex === -1 ? undefined : value.slice(hashIndex + 1);\n const queryIndex = beforeHash.indexOf(\"?\");\n let base = queryIndex === -1 ? beforeHash : beforeHash.slice(0, queryIndex);\n const query = queryIndex === -1 ? undefined : beforeHash.slice(queryIndex + 1);\n\n const schemeIndex = base.indexOf(\"://\");\n if (schemeIndex !== -1) {\n const authorityStart = schemeIndex + 3;\n const authorityEnd = base.indexOf(\"/\", authorityStart);\n const authority = base.slice(authorityStart, authorityEnd === -1 ? undefined : authorityEnd);\n const at = authority.lastIndexOf(\"@\");\n if (at !== -1) {\n base =\n base.slice(0, authorityStart) +\n authority.slice(at + 1) +\n (authorityEnd === -1 ? \"\" : base.slice(authorityEnd));\n }\n }\n\n const keptQuery = query === undefined ? undefined : redactParameterString(query);\n const keptFragment =\n fragment === undefined || !fragment.includes(\"=\") ? fragment : redactParameterString(fragment);\n return base + (keptQuery ? `?${keptQuery}` : \"\") + (keptFragment ? `#${keptFragment}` : \"\");\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\n\nfunction isGeoJsonPayload(value: Record<string, unknown>): boolean {\n return (\n value.type === \"FeatureCollection\" ||\n value.type === \"Feature\" ||\n value.type === \"GeometryCollection\"\n );\n}\n\nfunction redactConfigurationValue(\n value: unknown,\n path: string,\n accumulator: RedactionAccumulator,\n depth = 0,\n): unknown {\n if (depth >= MAX_REDACT_DEPTH) {\n // Fail closed. A deeply nested configuration shape is not needed to render\n // any built-in layer, and returning it unchanged would let a credential\n // bypass the invariant merely by exceeding the traversal cap.\n recordRedaction(accumulator, path, value);\n return undefined;\n }\n if (typeof value === \"string\") {\n const redacted = redactUrlCredentials(value);\n if (redacted !== value) {\n recordRedaction(accumulator, path, value);\n }\n return redacted;\n }\n if (Array.isArray(value)) {\n return value.map((item, index) =>\n redactConfigurationValue(item, `${path}[${index}]`, accumulator, depth + 1),\n );\n }\n if (!isPlainObject(value)) return value;\n if (isGeoJsonPayload(value)) return structuredClone(value);\n\n const result: Record<string, unknown> = {};\n for (const [key, nested] of Object.entries(value)) {\n const nestedPath = path ? `${path}.${key}` : key;\n const normalizedKey = normalizeCredentialName(key);\n if (\n (normalizedKey === \"requestheaders\" || normalizedKey === \"headers\") &&\n isPlainObject(nested)\n ) {\n // `Bearer ${TOKEN}` names a variable instead of carrying a secret, so it\n // survives; any other header value is dropped. Mirrors `_redact_config`\n // in python/src/geolibre/project.py.\n const kept: Record<string, string> = {};\n const removed: Record<string, unknown> = {};\n for (const [name, headerValue] of Object.entries(nested)) {\n if (typeof headerValue === \"string\" && isHeaderReferenceOnly(headerValue)) {\n kept[name] = headerValue;\n } else {\n removed[name] = headerValue;\n }\n }\n if (Object.keys(removed).length > 0) recordRedaction(accumulator, nestedPath, removed);\n if (Object.keys(kept).length > 0) result[key] = kept;\n continue;\n }\n if (isCredentialFieldName(key)) {\n recordRedaction(accumulator, nestedPath, nested);\n continue;\n }\n result[key] = redactConfigurationValue(nested, nestedPath, accumulator, depth + 1);\n }\n return result;\n}\n\nfunction countLeafValues(value: unknown): number {\n if (Array.isArray(value)) return value.reduce((count, item) => count + countLeafValues(item), 0);\n if (isPlainObject(value)) {\n return Object.values(value).reduce(\n (count: number, nested) => count + countLeafValues(nested),\n 0,\n );\n }\n return 1;\n}\n\n/**\n * Settings keys that registry-listed external plugins declared publishable,\n * keyed by plugin id. `null` keeps the whole blob. Populated at runtime by the\n * host from the curated plugin registry, never from the project file itself,\n * so a project cannot vouch for its own state. The Python mirror in\n * `python/src/geolibre/project.py` only knows the static built-in list, so a\n * project redacted there still drops these plugins' state.\n */\nconst registryPublishableSettings = new Map<string, readonly string[] | null>();\n\n/**\n * Replace the registry-declared publishable plugin settings.\n *\n * Built-in ids in {@link PUBLISHABLE_PLUGIN_SETTINGS} always win, so a registry\n * entry cannot widen a first-party plugin's allowlist.\n *\n * @param entries Plugin ids mapped to the settings keys to keep, or `null` to\n * keep the whole blob. Ids that declare nothing should be omitted.\n */\nexport function setRegistryPublishableSettings(\n entries: Iterable<readonly [string, readonly string[] | null]>,\n): void {\n registryPublishableSettings.clear();\n for (const [id, keys] of entries) {\n if (Object.prototype.hasOwnProperty.call(PUBLISHABLE_PLUGIN_SETTINGS, id)) continue;\n registryPublishableSettings.set(id, keys);\n }\n}\n\n/**\n * Look up the publishable settings for a plugin id.\n *\n * @param id Plugin id.\n * @returns `undefined` when nothing is publishable, `null` to keep the whole\n * blob, otherwise the list of keys to keep.\n */\nfunction publishableSettingsFor(id: string): readonly string[] | null | undefined {\n if (Object.prototype.hasOwnProperty.call(PUBLISHABLE_PLUGIN_SETTINGS, id)) {\n return PUBLISHABLE_PLUGIN_SETTINGS[id];\n }\n return registryPublishableSettings.get(id);\n}\n\n/**\n * Return a detached project safe for any external egress.\n *\n * Environment variables and geocoder keys are always removed. Layer\n * configuration is recursively scrubbed for credential fields and credential\n * URL parameters. Plugin settings are omitted wholesale because external\n * plugins can persist arbitrary shapes; retaining unknown state cannot provide\n * a no-secret guarantee. The first-party map controls in\n * {@link PUBLISHABLE_PLUGIN_SETTINGS} are the exception: their state is known\n * and carries no credentials, and dropping it stripped the legend, colorbar,\n * and swipe from the exported map. Manifest URLs, activation, and control\n * positions stay intact so recipients can still load and configure the plugin\n * themselves. A registry-listed external plugin can opt in through the\n * `publishableSettings` field of its registry entry (see\n * {@link setRegistryPublishableSettings}); what it keeps is still swept below.\n */\nexport function redactProjectCredentials(project: GeoLibreProject): CredentialRedactionResult {\n const accumulator: RedactionAccumulator = {\n paths: [],\n fingerprints: [],\n count: 0,\n unfingerprintable: false,\n };\n const basemapStyleUrl =\n typeof project.basemapStyleUrl === \"string\"\n ? redactUrlCredentials(project.basemapStyleUrl)\n : project.basemapStyleUrl;\n if (basemapStyleUrl !== project.basemapStyleUrl) {\n recordRedaction(accumulator, \"basemapStyleUrl\", project.basemapStyleUrl);\n }\n const geocoding = project.preferences?.geocoding\n ? { ...project.preferences.geocoding, apiKeys: {} }\n : project.preferences?.geocoding;\n if (geocoding) {\n for (const field of [\"forwardEndpoint\", \"reverseEndpoint\"] as const) {\n const endpoint = geocoding[field];\n if (typeof endpoint !== \"string\") continue;\n const redacted = redactUrlCredentials(endpoint);\n if (redacted !== endpoint) {\n geocoding[field] = redacted;\n recordRedaction(accumulator, `preferences.geocoding.${field}`, endpoint);\n }\n }\n }\n // The Mapbox-only style is a URL like the shared basemap and can carry an\n // access token; sweep it the same way so the save prompt counts it and a\n // \"strip\" choice actually removes it.\n const mapboxStyleUrl = project.preferences?.map?.mapboxStyleUrl;\n const redactedMapboxStyleUrl =\n typeof mapboxStyleUrl === \"string\" ? redactUrlCredentials(mapboxStyleUrl) : mapboxStyleUrl;\n if (redactedMapboxStyleUrl !== mapboxStyleUrl) {\n recordRedaction(accumulator, \"preferences.map.mapboxStyleUrl\", mapboxStyleUrl);\n }\n const preferences = project.preferences\n ? {\n ...project.preferences,\n // Only rows explicitly marked non-secret travel; everything else is\n // treated as a credential.\n environmentVariables: (project.preferences.environmentVariables ?? []).filter(\n (variable) => variable.secret === false,\n ),\n geocoding,\n ...(redactedMapboxStyleUrl !== mapboxStyleUrl\n ? { map: { ...project.preferences.map, mapboxStyleUrl: redactedMapboxStyleUrl } }\n : {}),\n }\n : project.preferences;\n // A secret row with an empty value carries nothing (desktop keeps the value\n // in the keychain), so it must not count toward the save prompt. A populated\n // row with no name still needs an explicit keep/strip choice.\n const populatedEnvironmentVariables =\n project.preferences?.environmentVariables?.filter(\n (variable) => variable.secret !== false && variable.value !== \"\",\n ) ?? [];\n if (populatedEnvironmentVariables.length > 0) {\n recordRedaction(\n accumulator,\n \"preferences.environmentVariables\",\n populatedEnvironmentVariables,\n populatedEnvironmentVariables.length,\n );\n }\n const geocodingApiKeys = project.preferences?.geocoding?.apiKeys ?? {};\n if (Object.keys(geocodingApiKeys).length > 0) {\n recordRedaction(\n accumulator,\n \"preferences.geocoding.apiKeys\",\n geocodingApiKeys,\n Object.keys(geocodingApiKeys).length,\n );\n }\n\n const layers = (project.layers ?? []).map((layer, index) => {\n // `export: false` strips the feature data this project *carries* — the\n // inline `geojson` and the `embeddedGeoJSON` snapshot. It deliberately\n // leaves `source`/`connection` alone, so a layer that fetches live (WFS,\n // ArcGIS FeatureServer, a remote GeoJSON or tile URL) still travels with a\n // reachable URL and re-fetches the same data when the shared project is\n // opened. Stripping those would ship a layer that cannot render at all;\n // whether that is the right trade is tracked in the docs' caveat.\n const isNoExport = layer.capabilities?.export === false;\n const cleanMetadata = redactConfigurationValue(\n layer.metadata,\n `layers[${index}].metadata`,\n accumulator,\n ) as Record<string, unknown>;\n if (isNoExport && \"embeddedGeoJSON\" in cleanMetadata) {\n delete cleanMetadata.embeddedGeoJSON;\n }\n return {\n ...layer,\n ...(isNoExport ? { geojson: undefined } : {}),\n source: redactConfigurationValue(\n layer.source,\n `layers[${index}].source`,\n accumulator,\n ) as Record<string, unknown>,\n metadata: cleanMetadata,\n ...(typeof layer.sourcePath === \"string\"\n ? {\n sourcePath: redactConfigurationValue(\n layer.sourcePath,\n `layers[${index}].sourcePath`,\n accumulator,\n ) as string,\n }\n : {}),\n // `connection.lastError` is free-form text taken from a caught error, and a\n // refresh path that words it as `Failed to fetch ${url}` would carry the\n // request's credential parameters. Sweeping it costs nothing and keeps the\n // no-secret guarantee from depending on how an error message is phrased.\n ...(layer.connection\n ? {\n connection: redactConfigurationValue(\n layer.connection,\n `layers[${index}].connection`,\n accumulator,\n ) as LayerConnection,\n }\n : {}),\n };\n });\n\n let plugins = project.plugins;\n if (plugins) {\n const manifestUrls = plugins.manifestUrls.map((url, index) => {\n const redacted = redactUrlCredentials(url);\n if (redacted !== url) {\n recordRedaction(accumulator, `plugins.manifestUrls[${index}]`, url);\n }\n return redacted;\n });\n const settings = plugins.settings ?? {};\n const kept: Record<string, unknown> = {};\n const dropped: Record<string, unknown> = {};\n for (const [id, value] of Object.entries(settings)) {\n const allowed = publishableSettingsFor(id);\n if (allowed === undefined) {\n dropped[id] = value;\n continue;\n }\n if (allowed === null) {\n kept[id] = value;\n continue;\n }\n // An unexpected shape is dropped rather than passed through.\n if (typeof value !== \"object\" || value === null || Array.isArray(value)) {\n dropped[id] = value;\n continue;\n }\n const subset: Record<string, unknown> = {};\n const rest: Record<string, unknown> = {};\n for (const [key, item] of Object.entries(value as Record<string, unknown>)) {\n if (allowed.includes(key)) subset[key] = item;\n else rest[key] = item;\n }\n if (Object.keys(subset).length > 0) kept[id] = subset;\n if (Object.keys(rest).length > 0) dropped[id] = rest;\n }\n if (Object.keys(dropped).length > 0) {\n recordRedaction(accumulator, \"plugins.settings\", dropped, countLeafValues(dropped));\n }\n // What survives is still swept by the same pass layer configuration gets,\n // so a credentialed URL inside a kept blob is scrubbed rather than trusted.\n plugins = {\n ...plugins,\n manifestUrls,\n settings: redactConfigurationValue(kept, \"plugins.settings\", accumulator) as Record<\n string,\n unknown\n >,\n };\n }\n\n return {\n project: {\n // Remaining project-level fields are structural/user content with no\n // current credential-bearing schema. Add explicit handling above when a\n // future field (for example a processing parameter) accepts credentials.\n ...project,\n basemapStyleUrl,\n ...(preferences ? { preferences } : {}),\n layers,\n ...(plugins ? { plugins } : {}),\n ...(project.metadata\n ? {\n metadata: redactConfigurationValue(project.metadata, \"metadata\", accumulator) as Record<\n string,\n unknown\n >,\n }\n : {}),\n },\n redactedPaths: [...new Set(accumulator.paths)],\n redactedFingerprints: [...new Set(accumulator.fingerprints)],\n hasUnfingerprintableCredential: accumulator.unfingerprintable,\n redactedCount: accumulator.count,\n };\n}\n\n/**\n * Remove credentials from a single configuration value (a layer `source`,\n * `metadata`, or `sourcePath`) with the same rules the project redaction pass\n * applies to layer configuration: credential-named fields and non-reference\n * header values are dropped, and credential URL parameters are stripped from\n * every URL-shaped string.\n *\n * @param value - The configuration value to scrub. Not mutated.\n * @returns A detached, redacted copy.\n */\nexport function redactConfigurationCredentials<T>(value: T): T {\n const accumulator: RedactionAccumulator = {\n paths: [],\n fingerprints: [],\n count: 0,\n unfingerprintable: false,\n };\n return redactConfigurationValue(value, \"\", accumulator) as T;\n}\n\n/** Convenience wrapper for callers that only need the safe project. */\nexport function redactCredentials(project: GeoLibreProject): GeoLibreProject {\n return redactProjectCredentials(project).project;\n}\n","/** Default chapter dwell time after the camera arrives, in seconds. */\nexport const DEFAULT_STORY_CHAPTER_DURATION_SECONDS = 15;\nexport const MIN_STORY_CHAPTER_DURATION_SECONDS = 1;\nexport const MAX_STORY_CHAPTER_DURATION_SECONDS = 3600;\n\n/** Resolve old or hand-authored chapter timings to a safe autoplay duration. */\nexport function normalizeStoryChapterDurationSeconds(value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n return DEFAULT_STORY_CHAPTER_DURATION_SECONDS;\n }\n return Math.min(\n MAX_STORY_CHAPTER_DURATION_SECONDS,\n Math.max(MIN_STORY_CHAPTER_DURATION_SECONDS, value),\n );\n}\n","import type { Feature, Point } from \"geojson\";\nimport { getRuntimeEnvironment } from \"./runtime-env\";\nimport type { GeocodingLocator } from \"./types\";\n\n/**\n * Geocoding client and pure helpers shared by the batch-geocode dialog and the\n * reverse-geocode plugin.\n *\n * This module lives in `@geolibre/core` rather than `@geolibre/processing`\n * because `@geolibre/plugins` depends on core but not on processing, and the\n * reverse-geocode plugin needs the same client. The pure helpers (URL builders,\n * result mappers, pacing) carry no React or MapLibre dependency so they can be\n * unit-tested without a browser or network.\n *\n * Providers: geocoding is dispatched through a small {@link GeocodingProvider}\n * registry so alternatives to Nominatim (ArcGIS World Geocoder, CartoCiudad, Mapbox,\n * Pelias, Google, organization-hosted ArcGIS GeocodeServer locators) can be\n * selected per project. Each provider builds its own request\n * URLs and normalizes its response into a {@link GeocodeMatch} (forward) or a\n * {@link ReverseGeocodeDisplay} (reverse), so the dialog and plugin stay\n * provider-agnostic. Nominatim is the default.\n *\n * The 1 request/second throttle and the row cap are part of Nominatim's public\n * usage policy and are therefore applied ONLY to the default public host; a\n * self-hosted endpoint relaxes both, and keyed providers (Mapbox/ArcGIS/Google)\n * are paced by their own quotas, not by us.\n *\n * Browser fetch cannot set `User-Agent`/`Referer`, so the app is identified to\n * Nominatim via the optional `email` query parameter plus the automatically\n * sent `Referer`. The desktop build overrides this with a native (Tauri) fetch\n * via {@link setGeocodingFetch} — see the note there. See\n * docs/user-guide/data-integrations.md#geocoding.\n */\n\nexport const DEFAULT_FORWARD_GEOCODE_ENDPOINT = \"https://nominatim.openstreetmap.org/search\";\nexport const DEFAULT_REVERSE_GEOCODE_ENDPOINT = \"https://nominatim.openstreetmap.org/reverse\";\n\n/** Host whose public usage policy (1 req/sec, bulk limits) we must respect. */\nexport const NOMINATIM_PUBLIC_HOST = \"nominatim.openstreetmap.org\";\n\n/** Minimum spacing between requests to the public Nominatim endpoint. */\nexport const NOMINATIM_MIN_INTERVAL_MS = 1100;\n\n/** Host of the free, unkeyed public CartoCiudad geocoder (IGN/CNIG). */\nexport const CARTOCIUDAD_PUBLIC_HOST = \"cartociudad.es\";\n\n/**\n * Courtesy spacing (~5 req/sec) between requests to the public CartoCiudad\n * endpoint. Unlike {@link NOMINATIM_MIN_INTERVAL_MS} this is not a published\n * policy — IGN documents no rate limit — it is our own conservative default so\n * a large batch does not burst at a free public government service. See\n * {@link geocoderMinIntervalMs}.\n */\nexport const CARTOCIUDAD_MIN_INTERVAL_MS = 200;\n\n/** Max rows a single batch run will geocode against the public endpoint. */\nexport const PUBLIC_GEOCODE_ROW_CAP = 1000;\n\n/** Property keys added to each geocoded feature. */\nexport const GEOCODE_LAT_KEY = \"geocode_lat\";\nexport const GEOCODE_LON_KEY = \"geocode_lon\";\nexport const GEOCODE_DISPLAY_NAME_KEY = \"geocode_display_name\";\nexport const GEOCODE_SCORE_KEY = \"geocode_importance\";\n/** Provider id that produced (or failed to produce) a match, when stamped. */\nexport const GEOCODE_PROVIDER_KEY = \"geocode_provider\";\n/** Whether a row matched, when stamped. See {@link GeocodeStatus}. */\nexport const GEOCODE_STATUS_KEY = \"geocode_status\";\n/** Prefix of a matched candidate's own attributes, when they are kept. */\nexport const GEOCODE_ATTRIBUTE_PREFIX = \"geocode_attr_\";\n\n/** Identifier of a selectable geocoding backend. */\nexport type GeocodingProviderId =\n | \"nominatim\"\n | \"pelias\"\n | \"arcgis\"\n | \"arcgis-locator\"\n | \"mapbox\"\n | \"google\"\n | \"cartociudad\";\n\n/** The provider used when none is configured. */\nexport const DEFAULT_GEOCODING_PROVIDER_ID: GeocodingProviderId = \"nominatim\";\n\nexport interface GeocoderConfig {\n /** Which backend to dispatch through. */\n providerId: GeocodingProviderId;\n /** Forward (address -> point) endpoint. */\n forwardEndpoint: string;\n /** Reverse (point -> address) endpoint. */\n reverseEndpoint: string;\n /** Contact email sent as the `email` query param to identify the client. */\n email?: string;\n /** API key / access token for providers that require one. */\n apiKey?: string;\n /** The selected locator, for the `arcgis-locator` provider. */\n locator?: GeocodingLocator;\n}\n\n/**\n * An error the geocoding service reported about the request itself, as opposed\n * to a network failure or a search with no matches. ArcGIS answers failures with\n * HTTP 200 and an `error` envelope, so `response.ok` alone would turn them into\n * an empty result. `code` is the service's own code (498/499 for a missing or\n * invalid token, 403 for insufficient permission).\n */\nexport class GeocoderServiceError extends Error {\n readonly code: number | undefined;\n\n constructor(message: string, code?: number) {\n super(message);\n this.name = \"GeocoderServiceError\";\n this.code = code;\n }\n\n /** Whether the service asked for a token or rejected the one it got. */\n get isAuthError(): boolean {\n return this.code === 401 || this.code === 403 || this.code === 498 || this.code === 499;\n }\n}\n\n/** The configured geocoder does not support the requested operation. */\nexport class GeocoderUnsupportedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"GeocoderUnsupportedError\";\n }\n}\n\n/** Per-request options passed to a provider's forward URL builder. */\nexport interface ForwardRequestOptions {\n email?: string;\n limit?: number;\n /** A suggestion's key, so the locator resolves exactly that suggestion. */\n magicKey?: string;\n /** Ask for each candidate's own attributes (see {@link GeocodeMatch.attributes}). */\n attributes?: boolean;\n}\n\n/** Per-request options passed to a provider's reverse URL builder. */\nexport interface ReverseRequestOptions {\n email?: string;\n zoom?: number;\n}\n\n/**\n * A normalized forward-geocoding match. Each provider maps its own response\n * shape onto this so {@link geocodeMatchToFeature} can build a point feature\n * without knowing which backend produced it. `score` is the provider's match\n * confidence/importance on its own scale (Nominatim importance 0..1, ArcGIS\n * 0..100, ...), or null when the provider reports none.\n */\nexport interface GeocodeMatch {\n lat: number;\n lon: number;\n displayName: string;\n score: number | null;\n /**\n * The candidate's own attributes, when the service returns them (an\n * organization locator's parcel or asset identifiers, address type, ...).\n * Empty values are left out.\n */\n attributes?: Record<string, string | number | boolean>;\n}\n\n/** A partial-text suggestion from a locator's `suggest` operation. */\nexport interface GeocodeSuggestion {\n /** The suggested place, as the locator words it. */\n text: string;\n /** Opaque key that `findAddressCandidates` resolves to this suggestion. */\n magicKey: string;\n}\n\n/**\n * A geocoding backend. Builds provider-specific request URLs and normalizes the\n * raw response into the shared {@link GeocodeMatch} / {@link ReverseGeocodeDisplay}\n * shapes. Implementations are pure (no fetch) so they unit-test without a\n * network; {@link geocodeForward}/{@link geocodeReverse} own the actual fetch.\n */\nexport interface GeocodingProvider {\n id: GeocodingProviderId;\n /** Human-readable name shown in the provider picker. */\n label: string;\n /** Whether forward (address -> point) geocoding is supported. */\n forward: boolean;\n /** Whether reverse (point -> address) geocoding is supported. */\n reverse: boolean;\n /**\n * Whether the provider's default configuration cannot work without an API\n * key / access token. Drives the \"missing key\" warning and disables the run.\n */\n requiresApiKey: boolean;\n /**\n * Whether the provider can use an API key at all, so the Settings UI shows a\n * key field. True for every keyed provider plus Pelias (the hosted\n * geocode.earth endpoint needs a key, a self-hosted instance does not).\n */\n acceptsApiKey: boolean;\n /**\n * Whether the provider's API blocks browser cross-origin requests, so it\n * needs a same-origin proxy to work in the webview. The UI surfaces a hint\n * when true. Only set for Google's Geocoding API today.\n */\n browserCorsRestricted?: boolean;\n /** Default forward endpoint used when the project does not override it. */\n defaultForwardEndpoint: string;\n /** Default reverse endpoint used when the project does not override it. */\n defaultReverseEndpoint: string;\n buildForwardUrl(config: GeocoderConfig, query: string, options: ForwardRequestOptions): string;\n parseForward(data: unknown): GeocodeMatch[];\n buildReverseUrl(\n config: GeocoderConfig,\n lon: number,\n lat: number,\n options: ReverseRequestOptions,\n ): string;\n parseReverse(data: unknown): ReverseGeocodeDisplay | null;\n}\n\n/** A single Nominatim forward-geocoding result (jsonv2). */\nexport interface NominatimForwardResult {\n lat: string;\n lon: string;\n display_name?: string;\n importance?: number | string;\n [key: string]: unknown;\n}\n\n/** A Nominatim reverse-geocoding result (jsonv2). */\nexport interface NominatimReverseResult {\n lat?: string;\n lon?: string;\n display_name?: string;\n address?: Record<string, string>;\n /** Present (e.g. \"Unable to geocode\") when no match was found. */\n error?: string;\n [key: string]: unknown;\n}\n\n/** A row queued for geocoding, paired with its source CSV row. */\nexport interface GeocodeRequest {\n /** Zero-based index among the parsed data rows. */\n index: number;\n /** The composed address string sent to the geocoder. */\n address: string;\n /** The original CSV row, copied onto the output feature's properties. */\n row: Record<string, string>;\n}\n\n/** A reverse-geocode result resolved to a display string plus address parts. */\nexport interface ReverseGeocodeDisplay {\n displayName: string;\n parts: Record<string, string>;\n}\n\n/** Whether a row matched during batch geocoding, stamped as {@link GEOCODE_STATUS_KEY}. */\nexport type GeocodeStatus = \"matched\" | \"unmatched\";\n\nfunction coerceScore(value: number | string | undefined | null): number | null {\n if (value === undefined || value === null || value === \"\") return null;\n const num = Number(value);\n return Number.isFinite(num) ? num : null;\n}\n\n/** Append `base` to `existing`, suffixing `_2`, `_3`, ... if the key collides. */\nfunction uniqueKey(base: string, existing: Record<string, unknown>): string {\n if (!(base in existing)) return base;\n let suffix = 2;\n while (`${base}_${suffix}` in existing) suffix += 1;\n return `${base}_${suffix}`;\n}\n\n/**\n * Coerce an address-parts record to `Record<string, string>`. Provider address\n * objects can carry numbers (ArcGIS `Score`/`X`/`Y`, Pelias coordinates), so\n * values are stringified rather than unsafely cast; null/undefined are dropped.\n */\nfunction stringifyParts(obj: Record<string, unknown>): Record<string, string> {\n const parts: Record<string, string> = {};\n for (const [key, value] of Object.entries(obj)) {\n if (value === null || value === undefined) continue;\n parts[key] = typeof value === \"string\" ? value : String(value);\n }\n return parts;\n}\n\n/** Read a string property off an unknown object, or undefined. */\nfunction readString(obj: unknown, key: string): string | undefined {\n if (obj && typeof obj === \"object\" && key in obj) {\n const value = (obj as Record<string, unknown>)[key];\n if (typeof value === \"string\") return value;\n if (typeof value === \"number\") return String(value);\n }\n return undefined;\n}\n\n/** Build a Nominatim forward-geocoding URL (jsonv2, address details on). */\nexport function buildForwardGeocodeUrl(\n endpoint: string,\n query: string,\n options: { email?: string; limit?: number } = {},\n): string {\n const url = new URL(endpoint);\n url.searchParams.set(\"q\", query);\n url.searchParams.set(\"format\", \"jsonv2\");\n url.searchParams.set(\"addressdetails\", \"1\");\n url.searchParams.set(\"limit\", String(options.limit ?? 1));\n if (options.email) url.searchParams.set(\"email\", options.email);\n return url.toString();\n}\n\n/** Build a Nominatim reverse-geocoding URL (jsonv2, address details on). */\nexport function buildReverseGeocodeUrl(\n endpoint: string,\n lon: number,\n lat: number,\n options: { email?: string; zoom?: number } = {},\n): string {\n const url = new URL(endpoint);\n url.searchParams.set(\"lat\", String(lat));\n url.searchParams.set(\"lon\", String(lon));\n url.searchParams.set(\"format\", \"jsonv2\");\n url.searchParams.set(\"addressdetails\", \"1\");\n if (options.zoom !== undefined) url.searchParams.set(\"zoom\", String(options.zoom));\n if (options.email) url.searchParams.set(\"email\", options.email);\n return url.toString();\n}\n\n/** Map one Nominatim forward result onto a normalized match, or null. */\nfunction nominatimForwardResultToMatch(result: NominatimForwardResult): GeocodeMatch | null {\n const lat = Number(result.lat);\n const lon = Number(result.lon);\n if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;\n return {\n lat,\n lon,\n displayName: result.display_name ?? \"\",\n score: coerceScore(result.importance),\n };\n}\n\n/**\n * Convert a normalized {@link GeocodeMatch} into a point Feature whose\n * properties carry the original CSV row plus `geocode_lat`/`geocode_lon`/\n * `geocode_display_name`/`geocode_importance`. The added keys are de-duplicated\n * against the original columns so an existing `geocode_lat` is not clobbered.\n * Geometry coordinates are `[lon, lat]`. Returns null when the match has no\n * finite coordinates.\n *\n * `extra.providerId`, when passed, also stamps `geocode_provider` and\n * `geocode_status: \"matched\"` (dropped when omitted, so the existing\n * `GeocodeDialog` call site keeps its current output unchanged).\n * `extra.attributes` also adds the candidate's own attributes, each under\n * {@link GEOCODE_ATTRIBUTE_PREFIX} plus its name.\n */\nexport function geocodeMatchToFeature(\n match: GeocodeMatch,\n originalRow: Record<string, string> = {},\n extra?: { providerId?: GeocodingProviderId; attributes?: boolean },\n): Feature<Point> | null {\n if (!Number.isFinite(match.lat) || !Number.isFinite(match.lon)) return null;\n\n const properties: Record<string, unknown> = { ...originalRow };\n const added: Record<string, unknown> = {\n [GEOCODE_LAT_KEY]: match.lat,\n [GEOCODE_LON_KEY]: match.lon,\n [GEOCODE_DISPLAY_NAME_KEY]: match.displayName ?? \"\",\n [GEOCODE_SCORE_KEY]: match.score,\n };\n if (extra?.providerId) {\n added[GEOCODE_PROVIDER_KEY] = extra.providerId;\n added[GEOCODE_STATUS_KEY] = \"matched\" satisfies GeocodeStatus;\n }\n // The locator's own fields, prefixed so they never collide with the row's.\n if (extra?.attributes)\n for (const [key, value] of Object.entries(match.attributes ?? {}))\n added[`${GEOCODE_ATTRIBUTE_PREFIX}${key}`] = value;\n for (const [key, value] of Object.entries(added)) {\n properties[uniqueKey(key, properties)] = value;\n }\n\n return {\n type: \"Feature\",\n geometry: { type: \"Point\", coordinates: [match.lon, match.lat] },\n properties,\n };\n}\n\n/**\n * Build a null-geometry Feature for a CSV row that could not be geocoded, so\n * it is kept (not silently dropped) alongside the matched point features.\n * Mirrors the null-geometry shape {@link parseDelimitedTextLayer} already uses\n * for a non-spatial attribute table. Stamps `geocode_status: \"unmatched\"` and\n * `geocode_provider`, de-duplicated against the original columns the same way\n * {@link geocodeMatchToFeature} does.\n */\nexport function unmatchedGeocodeFeature(\n originalRow: Record<string, string>,\n providerId: GeocodingProviderId,\n): Feature<null> {\n const properties: Record<string, unknown> = { ...originalRow };\n const added: Record<string, unknown> = {\n [GEOCODE_STATUS_KEY]: \"unmatched\" satisfies GeocodeStatus,\n [GEOCODE_PROVIDER_KEY]: providerId,\n };\n for (const [key, value] of Object.entries(added)) {\n properties[uniqueKey(key, properties)] = value;\n }\n\n return { type: \"Feature\", geometry: null, properties };\n}\n\n/**\n * Convert a Nominatim forward result into a point Feature. Retained as a thin\n * wrapper over {@link geocodeMatchToFeature} for callers and tests that work\n * directly with Nominatim results. Returns null when the result has no finite\n * coordinates.\n */\nexport function nominatimResultToFeature(\n result: NominatimForwardResult,\n originalRow: Record<string, string> = {},\n): Feature<Point> | null {\n const match = nominatimForwardResultToMatch(result);\n return match ? geocodeMatchToFeature(match, originalRow) : null;\n}\n\n/**\n * Resolve a Nominatim reverse result to a display string and address parts, or\n * null when the point could not be reverse-geocoded.\n */\nexport function nominatimReverseResultToDisplay(\n result: NominatimReverseResult | null,\n): ReverseGeocodeDisplay | null {\n if (!result || result.error) return null;\n const displayName = result.display_name?.trim();\n if (!displayName) return null;\n return { displayName, parts: result.address ?? {} };\n}\n\n// --- Provider implementations ----------------------------------------------\n\nconst nominatimProvider: GeocodingProvider = {\n id: \"nominatim\",\n label: \"Nominatim (OpenStreetMap)\",\n forward: true,\n reverse: true,\n requiresApiKey: false,\n acceptsApiKey: false,\n defaultForwardEndpoint: DEFAULT_FORWARD_GEOCODE_ENDPOINT,\n defaultReverseEndpoint: DEFAULT_REVERSE_GEOCODE_ENDPOINT,\n buildForwardUrl: (config, query, options) =>\n buildForwardGeocodeUrl(config.forwardEndpoint, query, {\n email: options.email,\n limit: options.limit,\n }),\n parseForward: (data) =>\n Array.isArray(data)\n ? (data as NominatimForwardResult[])\n .map(nominatimForwardResultToMatch)\n .filter((m): m is GeocodeMatch => m !== null)\n : [],\n buildReverseUrl: (config, lon, lat, options) =>\n buildReverseGeocodeUrl(config.reverseEndpoint, lon, lat, {\n email: options.email,\n zoom: options.zoom,\n }),\n parseReverse: (data) =>\n nominatimReverseResultToDisplay(\n data && typeof data === \"object\" ? (data as NominatimReverseResult) : null,\n ),\n};\n\n/** Read a GeoJSON FeatureCollection's features array, or []. */\nfunction geojsonFeatures(data: unknown): Record<string, unknown>[] {\n if (\n data &&\n typeof data === \"object\" &&\n Array.isArray((data as { features?: unknown }).features)\n ) {\n return (data as { features: unknown[] }).features.filter(\n (f): f is Record<string, unknown> => !!f && typeof f === \"object\",\n );\n }\n return [];\n}\n\n/** Pull `[lon, lat]` from a GeoJSON Point geometry, or null. */\nfunction pointCoords(feature: Record<string, unknown>): [number, number] | null {\n const geometry = feature.geometry as { coordinates?: unknown } | undefined;\n const coords = geometry?.coordinates;\n if (Array.isArray(coords) && coords.length >= 2) {\n const lon = Number(coords[0]);\n const lat = Number(coords[1]);\n if (Number.isFinite(lon) && Number.isFinite(lat)) return [lon, lat];\n }\n return null;\n}\n\nconst peliasProvider: GeocodingProvider = {\n id: \"pelias\",\n label: \"Pelias\",\n forward: true,\n reverse: true,\n // The default endpoint is hosted geocode.earth (key required), but a\n // self-hosted Pelias needs none, so the key is accepted yet not mandatory.\n requiresApiKey: false,\n acceptsApiKey: true,\n defaultForwardEndpoint: \"https://api.geocode.earth/v1/search\",\n defaultReverseEndpoint: \"https://api.geocode.earth/v1/reverse\",\n buildForwardUrl: (config, query, options) => {\n const url = new URL(config.forwardEndpoint);\n url.searchParams.set(\"text\", query);\n if (options.limit) url.searchParams.set(\"size\", String(options.limit));\n if (config.apiKey) url.searchParams.set(\"api_key\", config.apiKey);\n return url.toString();\n },\n parseForward: (data) =>\n geojsonFeatures(data)\n .map((feature) => {\n const coords = pointCoords(feature);\n if (!coords) return null;\n const props = (feature.properties as Record<string, unknown>) ?? {};\n return {\n lon: coords[0],\n lat: coords[1],\n displayName: readString(props, \"label\") ?? \"\",\n score: coerceScore(props.confidence as number | undefined),\n } satisfies GeocodeMatch;\n })\n .filter((m): m is GeocodeMatch => m !== null),\n buildReverseUrl: (config, lon, lat) => {\n const url = new URL(config.reverseEndpoint);\n url.searchParams.set(\"point.lat\", String(lat));\n url.searchParams.set(\"point.lon\", String(lon));\n if (config.apiKey) url.searchParams.set(\"api_key\", config.apiKey);\n return url.toString();\n },\n parseReverse: (data) => {\n const feature = geojsonFeatures(data)[0];\n if (!feature) return null;\n const props = (feature.properties as Record<string, unknown>) ?? {};\n const displayName = readString(props, \"label\")?.trim();\n return displayName ? { displayName, parts: stringifyParts(props) } : null;\n },\n};\n\nconst arcgisProvider: GeocodingProvider = {\n id: \"arcgis\",\n label: \"ArcGIS World Geocoder\",\n forward: true,\n reverse: true,\n requiresApiKey: true,\n acceptsApiKey: true,\n defaultForwardEndpoint:\n \"https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/findAddressCandidates\",\n defaultReverseEndpoint:\n \"https://geocode.arcgis.com/arcgis/rest/services/World/GeocodeServer/reverseGeocode\",\n buildForwardUrl: (config, query, options) => {\n const url = new URL(config.forwardEndpoint);\n url.searchParams.set(\"SingleLine\", query);\n url.searchParams.set(\"f\", \"json\");\n url.searchParams.set(\"outFields\", \"Match_addr\");\n if (options.limit) url.searchParams.set(\"maxLocations\", String(options.limit));\n if (config.apiKey) url.searchParams.set(\"token\", config.apiKey);\n return url.toString();\n },\n parseForward: parseArcGISCandidates,\n buildReverseUrl: (config, lon, lat) => {\n const url = new URL(config.reverseEndpoint);\n url.searchParams.set(\"location\", `${lon},${lat}`);\n url.searchParams.set(\"f\", \"json\");\n if (config.apiKey) url.searchParams.set(\"token\", config.apiKey);\n return url.toString();\n },\n parseReverse: parseArcGISReverse,\n};\n\n// --- Organization-hosted ArcGIS GeocodeServer locators ----------------------\n\n/** Spatial references ArcGIS reports for Web Mercator. */\nconst WEB_MERCATOR_WKIDS = new Set([102100, 102113, 3857, 900913]);\n/** Earth radius of the Web Mercator sphere, in meters. */\nconst WEB_MERCATOR_RADIUS = 6378137;\n/** The operations a pasted GeocodeServer URL may already end in. */\nconst LOCATOR_OPERATION = /\\/(findAddressCandidates|reverseGeocode|suggest|geocodeAddresses)$/i;\n\n/** The ArcGIS `error` envelope in a response body, as a {@link GeocoderServiceError}. */\nfunction arcgisServiceError(data: unknown): GeocoderServiceError | null {\n const error = data && typeof data === \"object\" ? (data as { error?: unknown }).error : undefined;\n if (!error || typeof error !== \"object\") return null;\n const code = Number((error as { code?: unknown }).code);\n const message = readString(error, \"message\") ?? \"ArcGIS geocoding service error\";\n return new GeocoderServiceError(message, Number.isFinite(code) ? code : undefined);\n}\n\n/** The well-known ID of an ArcGIS `spatialReference`, preferring `latestWkid`. */\nfunction spatialReferenceWkid(spatialReference: unknown): number | null {\n if (!spatialReference || typeof spatialReference !== \"object\") return null;\n const sr = spatialReference as { wkid?: unknown; latestWkid?: unknown };\n const wkid = Number(sr.latestWkid ?? sr.wkid);\n return Number.isFinite(wkid) ? wkid : null;\n}\n\n/**\n * A candidate's position as longitude/latitude. Organization-hosted locators\n * often work in a projected system, and ArcGIS ignores an `outSR` it cannot\n * honor, so the reported spatial reference decides: geographic WGS 84 (or none,\n * the World Geocoder's default) is read as is, Web Mercator is unprojected, and\n * anything else is refused rather than plotted in the wrong place.\n *\n * @param x - The candidate's x coordinate.\n * @param y - The candidate's y coordinate.\n * @param wkid - The response's spatial reference, or null when it reports none.\n * @returns The position, or null when the coordinates are not finite or not on Earth.\n * @throws GeocoderServiceError When the coordinates are in an unsupported spatial reference.\n */\nfunction arcgisCandidatePosition(\n x: number,\n y: number,\n wkid: number | null,\n): { lon: number; lat: number } | null {\n if (!Number.isFinite(x) || !Number.isFinite(y)) return null;\n let lon = x;\n let lat = y;\n if (wkid !== null && WEB_MERCATOR_WKIDS.has(wkid)) {\n lon = (x / WEB_MERCATOR_RADIUS) * (180 / Math.PI);\n lat = (2 * Math.atan(Math.exp(y / WEB_MERCATOR_RADIUS)) - Math.PI / 2) * (180 / Math.PI);\n } else if (wkid !== null && wkid !== 4326) {\n throw new GeocoderServiceError(\n `The geocoder returned coordinates in spatial reference ${wkid}, which GeoLibre cannot place on the map.`,\n );\n }\n if (lon < -180 || lon > 180 || lat < -90 || lat > 90) return null;\n return { lon, lat };\n}\n\n/**\n * A candidate's attributes worth keeping: plain values that are not empty.\n * The shape and the location fields the match already carries are dropped.\n */\nfunction candidateAttributes(\n value: unknown,\n): Record<string, string | number | boolean> | undefined {\n if (!value || typeof value !== \"object\") return undefined;\n const kept: Record<string, string | number | boolean> = {};\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n if (/^(shape|x|y|displayx|displayy|xmin|xmax|ymin|ymax)$/i.test(key)) continue;\n if (\n typeof entry === \"string\"\n ? entry.trim() === \"\"\n : typeof entry !== \"number\" && typeof entry !== \"boolean\"\n )\n continue;\n if (typeof entry === \"number\" && !Number.isFinite(entry)) continue;\n kept[key] = entry as string | number | boolean;\n }\n return Object.keys(kept).length ? kept : undefined;\n}\n\n/**\n * Normalize an ArcGIS `findAddressCandidates` response. Throws the service's\n * error envelope instead of reporting it as \"no matches\".\n *\n * @param data - The parsed response body.\n * @returns The candidates as normalized matches.\n */\nfunction parseArcGISCandidates(data: unknown): GeocodeMatch[] {\n const error = arcgisServiceError(data);\n if (error) throw error;\n if (!data || typeof data !== \"object\") return [];\n const body = data as { candidates?: unknown; spatialReference?: unknown };\n if (!Array.isArray(body.candidates)) return [];\n const wkid = spatialReferenceWkid(body.spatialReference);\n return (body.candidates as Record<string, unknown>[])\n .map((candidate) => {\n const location = candidate.location as { x?: unknown; y?: unknown } | undefined;\n const position = arcgisCandidatePosition(Number(location?.x), Number(location?.y), wkid);\n if (!position) return null;\n const attributes = candidateAttributes(candidate.attributes);\n return {\n ...position,\n displayName: readString(candidate, \"address\") ?? \"\",\n score: coerceScore(candidate.score as number | undefined),\n ...(attributes ? { attributes } : {}),\n } satisfies GeocodeMatch;\n })\n .filter((m): m is GeocodeMatch => m !== null);\n}\n\n/**\n * Whether an ArcGIS error envelope is the \"Unable to find address\" answer:\n * code 400 with that text in its message or (Enterprise) its details. Other\n * 400s, such as malformed parameters, are real failures.\n */\nfunction isNoAddressError(data: unknown, error: GeocoderServiceError): boolean {\n if (error.code !== 400) return false;\n const envelope = (data as { error?: { details?: unknown } }).error;\n const details = Array.isArray(envelope?.details) ? envelope.details.join(\" \") : \"\";\n return /unable to find address/i.test(`${error.message} ${details}`);\n}\n\n/**\n * Normalize an ArcGIS `reverseGeocode` response. \"Unable to find address\" comes\n * back as an `error` envelope with code 400, which is a valid empty answer\n * rather than a failure.\n *\n * @param data - The parsed response body.\n * @returns The address, or null when there is none.\n */\nfunction parseArcGISReverse(data: unknown): ReverseGeocodeDisplay | null {\n const error = arcgisServiceError(data);\n if (error && !isNoAddressError(data, error)) throw error;\n const address =\n data && typeof data === \"object\" ? (data as { address?: unknown }).address : undefined;\n if (!address || typeof address !== \"object\") return null;\n const displayName = (\n readString(address, \"LongLabel\") ??\n readString(address, \"Match_addr\") ??\n readString(address, \"Address\")\n )?.trim();\n return displayName\n ? { displayName, parts: stringifyParts(address as Record<string, unknown>) }\n : null;\n}\n\n/**\n * The GeocodeServer base URL of a pasted locator URL: an operation suffix\n * (`/findAddressCandidates`, ...), the query string and any credentials are\n * dropped, so a token in a pasted URL is never stored.\n *\n * @param input - The URL as typed.\n * @returns The base URL ending in `/GeocodeServer`, or null when it is not one.\n */\nexport function normalizeArcGISLocatorUrl(input: string | undefined | null): string | null {\n let url: URL;\n try {\n url = new URL((input ?? \"\").trim());\n } catch {\n return null;\n }\n if (url.protocol !== \"https:\" && url.protocol !== \"http:\") return null;\n const path = url.pathname.replace(/\\/+$/, \"\").replace(LOCATOR_OPERATION, \"\");\n if (!/\\/GeocodeServer$/i.test(path)) return null;\n url.pathname = path;\n url.search = \"\";\n url.hash = \"\";\n url.username = \"\";\n url.password = \"\";\n return url.toString();\n}\n\n/**\n * Coerce a stored locator to a {@link GeocodingLocator}, or null when it has no\n * usable id or GeocodeServer URL. Used for project files and runtime env, so\n * anything unexpected is dropped rather than trusted.\n *\n * @param value - The stored value.\n * @returns The locator, or null.\n */\nexport function normalizeGeocodingLocator(value: unknown): GeocodingLocator | null {\n if (!value || typeof value !== \"object\") return null;\n const candidate = value as Record<string, unknown>;\n const id = typeof candidate.id === \"string\" ? candidate.id.trim() : \"\";\n const url = normalizeArcGISLocatorUrl(\n typeof candidate.url === \"string\" ? candidate.url : undefined,\n );\n if (!id || !url) return null;\n const text = (key: string) =>\n typeof candidate[key] === \"string\" && (candidate[key] as string).trim()\n ? (candidate[key] as string).trim()\n : undefined;\n const flag = (key: string) =>\n typeof candidate[key] === \"boolean\" ? (candidate[key] as boolean) : undefined;\n const addressFields = Array.isArray(candidate.addressFields)\n ? candidate.addressFields.filter(\n (field): field is string => typeof field === \"string\" && field.trim() !== \"\",\n )\n : undefined;\n const locator: GeocodingLocator = { id, name: text(\"name\") ?? id, url };\n const portalUrl = text(\"portalUrl\");\n if (portalUrl) locator.portalUrl = portalUrl;\n const singleLineField = text(\"singleLineField\");\n if (singleLineField) locator.singleLineField = singleLineField;\n if (addressFields?.length) locator.addressFields = addressFields;\n const reverse = flag(\"reverse\");\n if (reverse !== undefined) locator.reverse = reverse;\n const suggest = flag(\"suggest\");\n if (suggest !== undefined) locator.suggest = suggest;\n const searchField = text(\"searchField\");\n if (searchField) locator.searchField = searchField;\n return locator;\n}\n\n/** What a GeocodeServer's `?f=json` metadata says it supports. */\nexport type ArcGISLocatorCapabilities = Required<Pick<GeocodingLocator, \"reverse\" | \"suggest\">> &\n Pick<GeocodingLocator, \"singleLineField\" | \"addressFields\">;\n\n/**\n * Read a GeocodeServer's capabilities from its `?f=json` metadata. A service\n * that lists no `capabilities` (older ArcGIS Server) is taken to support\n * geocoding and reverse geocoding but not suggestions.\n *\n * @param data - The parsed metadata.\n * @returns The capabilities.\n * @throws GeocoderServiceError For an error envelope, or metadata that is not a\n * GeocodeServer's.\n * @throws GeocoderUnsupportedError When the service does not geocode.\n */\nexport function parseArcGISLocatorInfo(data: unknown): ArcGISLocatorCapabilities {\n const error = arcgisServiceError(data);\n if (error) throw error;\n if (!data || typeof data !== \"object\") {\n throw new GeocoderServiceError(\"The service did not return GeocodeServer metadata.\");\n }\n const info = data as Record<string, unknown>;\n const fieldName = (field: unknown) =>\n field && typeof field === \"object\" ? readString(field, \"name\")?.trim() || undefined : undefined;\n const addressFields = Array.isArray(info.addressFields)\n ? info.addressFields.map(fieldName).filter((name): name is string => !!name)\n : [];\n const singleLineField = fieldName(info.singleLineAddressField);\n if (!singleLineField && !addressFields.length) {\n throw new GeocoderServiceError(\"The service did not return GeocodeServer metadata.\");\n }\n const listed =\n typeof info.capabilities === \"string\"\n ? new Set(info.capabilities.split(\",\").map((entry) => entry.trim().toLowerCase()))\n : null;\n if (listed && !listed.has(\"geocode\")) {\n throw new GeocoderUnsupportedError(\"This locator does not support geocoding.\");\n }\n return {\n singleLineField,\n addressFields: addressFields.length ? addressFields : undefined,\n reverse: listed ? listed.has(\"reversegeocode\") : true,\n suggest: listed ? listed.has(\"suggest\") : false,\n };\n}\n\n/**\n * The input field a single search string is sent in: the published field the\n * user chose for it, else the locator's single-line field, else its only\n * structured field (an asset ID locator often publishes just one), else\n * ArcGIS's conventional `SingleLine`.\n *\n * @param locator - The locator.\n * @returns The input field name.\n */\nexport function arcgisLocatorInputField(locator: GeocodingLocator): string {\n // The user's choice, while the service still publishes that field.\n const published = [locator.singleLineField, ...(locator.addressFields ?? [])];\n if (locator.searchField && published.includes(locator.searchField)) return locator.searchField;\n if (locator.singleLineField) return locator.singleLineField;\n if (locator.addressFields?.length === 1) return locator.addressFields[0];\n return \"SingleLine\";\n}\n\n/** The config's locator, or an error naming what is missing. */\nfunction requireLocator(config: GeocoderConfig): GeocodingLocator {\n if (!config.locator) {\n throw new GeocoderUnsupportedError(\"No ArcGIS GeocodeServer locator is selected.\");\n }\n return config.locator;\n}\n\nconst arcgisLocatorProvider: GeocodingProvider = {\n id: \"arcgis-locator\",\n label: \"ArcGIS GeocodeServer (organization)\",\n forward: true,\n // Per locator: see geocoderSupportsReverse.\n reverse: true,\n // A public locator needs no key; a secured one borrows a portal sign-in.\n requiresApiKey: false,\n acceptsApiKey: false,\n // Derived from the selected locator's URL by resolveGeocoderConfig.\n defaultForwardEndpoint: \"\",\n defaultReverseEndpoint: \"\",\n buildForwardUrl: (config, query, options) => {\n const locator = requireLocator(config);\n const url = new URL(config.forwardEndpoint || `${locator.url}/findAddressCandidates`);\n url.searchParams.set(arcgisLocatorInputField(locator), query);\n url.searchParams.set(\"f\", \"json\");\n // Locators often work in a projected system; ask for WGS 84 and check the\n // spatial reference the response reports (see arcgisCandidatePosition).\n url.searchParams.set(\"outSR\", \"4326\");\n // The candidate's own fields (parcel or asset IDs, address type, ...),\n // only when wanted: a locator can return dozens of them.\n if (options.attributes) url.searchParams.set(\"outFields\", \"*\");\n if (options.magicKey) url.searchParams.set(\"magicKey\", options.magicKey);\n if (options.limit) url.searchParams.set(\"maxLocations\", String(options.limit));\n return url.toString();\n },\n parseForward: parseArcGISCandidates,\n buildReverseUrl: (config, lon, lat) => {\n const locator = requireLocator(config);\n const url = new URL(config.reverseEndpoint || `${locator.url}/reverseGeocode`);\n url.searchParams.set(\n \"location\",\n JSON.stringify({ x: lon, y: lat, spatialReference: { wkid: 4326 } }),\n );\n url.searchParams.set(\"outSR\", \"4326\");\n url.searchParams.set(\"f\", \"json\");\n return url.toString();\n },\n parseReverse: parseArcGISReverse,\n};\n\nconst mapboxProvider: GeocodingProvider = {\n id: \"mapbox\",\n label: \"Mapbox\",\n forward: true,\n reverse: true,\n requiresApiKey: true,\n acceptsApiKey: true,\n defaultForwardEndpoint: \"https://api.mapbox.com/geocoding/v5/mapbox.places\",\n defaultReverseEndpoint: \"https://api.mapbox.com/geocoding/v5/mapbox.places\",\n buildForwardUrl: (config, query, options) => {\n const base = config.forwardEndpoint.replace(/\\/+$/, \"\");\n const url = new URL(`${base}/${encodeURIComponent(query)}.json`);\n if (options.limit) url.searchParams.set(\"limit\", String(options.limit));\n if (config.apiKey) url.searchParams.set(\"access_token\", config.apiKey);\n return url.toString();\n },\n parseForward: (data) =>\n geojsonFeatures(data)\n .map((feature) => {\n const center = feature.center as unknown;\n let coords: [number, number] | null = null;\n if (Array.isArray(center) && center.length >= 2) {\n const lon = Number(center[0]);\n const lat = Number(center[1]);\n if (Number.isFinite(lon) && Number.isFinite(lat)) coords = [lon, lat];\n }\n coords ??= pointCoords(feature);\n if (!coords) return null;\n return {\n lon: coords[0],\n lat: coords[1],\n displayName: readString(feature, \"place_name\") ?? \"\",\n score: coerceScore(feature.relevance as number | undefined),\n } satisfies GeocodeMatch;\n })\n .filter((m): m is GeocodeMatch => m !== null),\n buildReverseUrl: (config, lon, lat) => {\n const base = config.reverseEndpoint.replace(/\\/+$/, \"\");\n const url = new URL(`${base}/${lon},${lat}.json`);\n if (config.apiKey) url.searchParams.set(\"access_token\", config.apiKey);\n return url.toString();\n },\n parseReverse: (data) => {\n const feature = geojsonFeatures(data)[0];\n if (!feature) return null;\n const displayName = readString(feature, \"place_name\")?.trim();\n return displayName\n ? {\n displayName,\n parts: stringifyParts((feature.properties as Record<string, unknown>) ?? {}),\n }\n : null;\n },\n};\n\n/**\n * Detect a Google Geocoding API error embedded in an HTTP 200 body. Google v3\n * returns REQUEST_DENIED / OVER_QUERY_LIMIT / INVALID_REQUEST and friends with\n * a 200 status and the failure only in the `status` field, so `response.ok`\n * alone would silently turn those into empty results. Returns null for OK and\n * ZERO_RESULTS (a successful empty match).\n */\nfunction googleErrorMessage(data: unknown): string | null {\n const status = readString(data, \"status\");\n if (!status || status === \"OK\" || status === \"ZERO_RESULTS\") return null;\n const detail = readString(data, \"error_message\");\n return detail\n ? `Google geocoder error: ${status} - ${detail}`\n : `Google geocoder error: ${status}`;\n}\n\nconst googleProvider: GeocodingProvider = {\n id: \"google\",\n label: \"Google\",\n forward: true,\n reverse: true,\n requiresApiKey: true,\n acceptsApiKey: true,\n browserCorsRestricted: true,\n defaultForwardEndpoint: \"https://maps.googleapis.com/maps/api/geocode/json\",\n defaultReverseEndpoint: \"https://maps.googleapis.com/maps/api/geocode/json\",\n // The Geocoding API has no result-count parameter, so the batch loop's\n // `limit` is intentionally not forwarded; only the top result is used.\n buildForwardUrl: (config, query) => {\n const url = new URL(config.forwardEndpoint);\n url.searchParams.set(\"address\", query);\n if (config.apiKey) url.searchParams.set(\"key\", config.apiKey);\n return url.toString();\n },\n parseForward: (data) => {\n const error = googleErrorMessage(data);\n if (error) throw new Error(error);\n return parseGoogleResults(data);\n },\n buildReverseUrl: (config, lon, lat) => {\n const url = new URL(config.reverseEndpoint);\n url.searchParams.set(\"latlng\", `${lat},${lon}`);\n if (config.apiKey) url.searchParams.set(\"key\", config.apiKey);\n return url.toString();\n },\n parseReverse: (data) => {\n const error = googleErrorMessage(data);\n if (error) throw new Error(error);\n const results =\n data && typeof data === \"object\" && Array.isArray((data as { results?: unknown }).results)\n ? ((data as { results: unknown[] }).results as Record<string, unknown>[])\n : [];\n const displayName = readString(results[0], \"formatted_address\")?.trim();\n return displayName ? { displayName, parts: {} } : null;\n },\n};\n\n/** Map a Google Geocoding API `results` array onto normalized matches. */\nfunction parseGoogleResults(data: unknown): GeocodeMatch[] {\n const results =\n data && typeof data === \"object\" && Array.isArray((data as { results?: unknown }).results)\n ? ((data as { results: unknown[] }).results as Record<string, unknown>[])\n : [];\n return results\n .map((result): GeocodeMatch | null => {\n const geometry = result.geometry as { location?: unknown } | undefined;\n const location = geometry?.location as { lat?: unknown; lng?: unknown } | undefined;\n const lat = Number(location?.lat);\n const lon = Number(location?.lng);\n if (!Number.isFinite(lat) || !Number.isFinite(lon)) return null;\n return {\n lat,\n lon,\n displayName: readString(result, \"formatted_address\") ?? \"\",\n score: null,\n };\n })\n .filter((m): m is GeocodeMatch => m !== null);\n}\n\n// --- CartoCiudad (IGN/CNIG — Spanish national geocoder) -------------------\n\n/**\n * A single CartoCiudad forward/reverse geocoding result. The API returns one\n * object (not an array) for both forward (`find`) and reverse (`reverseGeocode`)\n * lookups, even for an ambiguous query. `lat`/`lng` are top-level numbers.\n *\n * There is no in-body success flag or confidence signal to check. A query the\n * geocoder cannot match comes back as HTTP 204 with an empty body rather than\n * an object carrying a failure code, so {@link readGeocodeJson} turns it into\n * \"no results\" before a parser ever sees it. The `state`/`stateMsg` fields are\n * vestigial: IGN's own service docs record them as suppressed in the current\n * Elasticsearch-backed geocoder, which \"cannot configure candidate output by\n * degree of match\", so `state` is permanently 0 and `stateMsg` empty\n * (github.com/IDEESpain/Cartociudad). Neither is modeled here, and matches\n * carry `score: null` because the API exposes no ranking to map onto it.\n */\ninterface CartoCiudadResult {\n lat: number;\n lng: number;\n address?: string;\n type?: string;\n tip_via?: string;\n portalNumber?: number | string;\n postalCode?: string;\n poblacion?: string;\n muni?: string;\n province?: string;\n comunidadAutonoma?: string;\n refCatastral?: string;\n [key: string]: unknown;\n}\n\n/**\n * Read a CartoCiudad `lat`/`lng` field as a finite number, or null when the\n * field is absent, blank, or not numeric (`\"NaN\"` and `\"invalid\"` included).\n * Guarding the raw value matters because `Number(null)` is 0, which would turn\n * a coordinate-less response into a match in the Gulf of Guinea.\n */\nfunction cartociudadCoordinate(value: unknown): number | null {\n if (typeof value === \"number\") return Number.isFinite(value) ? value : null;\n if (typeof value !== \"string\" || !value.trim()) return null;\n const parsed = Number(value);\n return Number.isFinite(parsed) ? parsed : null;\n}\n\n/** Validated WGS84 position for a result, or null when it carries none. */\nfunction cartociudadCoords(r: CartoCiudadResult): { lat: number; lon: number } | null {\n const lat = cartociudadCoordinate(r.lat);\n const lon = cartociudadCoordinate(r.lng);\n if (lat === null || lon === null) return null;\n if (lat < -90 || lat > 90 || lon < -180 || lon > 180) return null;\n return { lat, lon };\n}\n\n/** Default endpoints for the public CartoCiudad REST API (no auth required). */\nconst CARTOCIUDAD_FORWARD_ENDPOINT = \"https://www.cartociudad.es/geocoder/api/geocoder/find\";\nconst CARTOCIUDAD_REVERSE_ENDPOINT =\n \"https://www.cartociudad.es/geocoder/api/geocoder/reverseGeocode\";\n\n/**\n * Build a human-readable display name from CartoCiudad address fields.\n * Format: \"Tip Via Address Num, CP Poblacion (Province)\"\n */\nfunction cartociudadDisplayName(r: CartoCiudadResult): string {\n const parts: string[] = [];\n const streetParts = [r.tip_via, r.address]\n .filter((s) => s && String(s).trim())\n .map((s) => String(s).trim());\n if (streetParts.length) {\n let street = streetParts.join(\" \");\n const portalNumber =\n r.portalNumber === undefined || r.portalNumber === null ? \"\" : String(r.portalNumber).trim();\n if (portalNumber) street += ` ${portalNumber}`;\n parts.push(street);\n }\n // `poblacion` is null on some results that still carry `muni` — a road\n // kilometre point such as \"A-6 km 120\" answers with muni \"Espinosa de los\n // Caballeros\" and no poblacion — so fall back to the municipality rather than\n // drop it from the display name. When both are present, `poblacion` wins as\n // the more specific locality.\n const localityName = [r.poblacion, r.muni].find((s) => s && String(s).trim());\n const locality = [r.postalCode, localityName]\n .filter((s) => s && String(s).trim())\n .map((s) => String(s).trim())\n .join(\" \");\n if (locality) parts.push(locality);\n const province = r.province ? String(r.province).trim() : \"\";\n if (province) parts.push(`(${province})`);\n return parts.join(\", \") || r.address || r.poblacion || r.muni || \"\";\n}\n\nconst cartociudadProvider: GeocodingProvider = {\n id: \"cartociudad\",\n label: \"CartoCiudad (IGN España)\",\n forward: true,\n reverse: true,\n requiresApiKey: false,\n acceptsApiKey: false,\n defaultForwardEndpoint: CARTOCIUDAD_FORWARD_ENDPOINT,\n defaultReverseEndpoint: CARTOCIUDAD_REVERSE_ENDPOINT,\n buildForwardUrl: (config, query) => {\n const url = new URL(config.forwardEndpoint);\n url.searchParams.set(\"q\", query);\n // `find` answers with one best result and has no result-count parameter, so\n // the batch loop's `limit` is intentionally not forwarded.\n return url.toString();\n },\n parseForward: (data) => {\n // `find` returns a single object, never an array — verified against the live\n // endpoint with an ambiguous query (\"calle mayor\"), which still answers with\n // one object rather than a candidate list.\n if (!data || typeof data !== \"object\" || Array.isArray(data)) return [];\n const r = data as CartoCiudadResult;\n const coords = cartociudadCoords(r);\n if (!coords) return [];\n const displayName = cartociudadDisplayName(r).trim();\n if (!displayName) return [];\n return [\n {\n lat: coords.lat,\n lon: coords.lon,\n displayName,\n score: null,\n },\n ];\n },\n buildReverseUrl: (config, lon, lat) => {\n const url = new URL(config.reverseEndpoint);\n url.searchParams.set(\"lat\", String(lat));\n url.searchParams.set(\"lon\", String(lon));\n return url.toString();\n },\n parseReverse: (data) => {\n if (!data || typeof data !== \"object\" || Array.isArray(data)) return null;\n const r = data as CartoCiudadResult;\n if (!cartociudadCoords(r)) return null;\n const displayName = cartociudadDisplayName(r).trim();\n if (!displayName) return null;\n // Build a structured address-parts record from the rich CartoCiudad fields.\n const parts: Record<string, string> = {};\n for (const [key, value] of Object.entries({\n type: r.type,\n tip_via: r.tip_via,\n address: r.address,\n portalNumber: r.portalNumber,\n postalCode: r.postalCode,\n poblacion: r.poblacion,\n muni: r.muni,\n province: r.province,\n comunidadAutonoma: r.comunidadAutonoma,\n refCatastral: r.refCatastral,\n })) {\n if (value !== undefined && value !== null && value !== \"\") parts[key] = String(value);\n }\n return { displayName, parts };\n },\n};\n\n/** All selectable geocoding providers, Nominatim first (the default). */\nexport const GEOCODING_PROVIDERS: readonly GeocodingProvider[] = [\n nominatimProvider,\n peliasProvider,\n arcgisProvider,\n arcgisLocatorProvider,\n mapboxProvider,\n googleProvider,\n cartociudadProvider,\n];\n\nconst PROVIDERS_BY_ID = new Map<GeocodingProviderId, GeocodingProvider>(\n GEOCODING_PROVIDERS.map((provider) => [provider.id, provider]),\n);\n\n/** Coerce an arbitrary string to a known provider id, defaulting to Nominatim. */\nexport function normalizeGeocodingProviderId(\n value: string | undefined | null,\n): GeocodingProviderId {\n const id = value?.trim() as GeocodingProviderId | undefined;\n return id && PROVIDERS_BY_ID.has(id) ? id : DEFAULT_GEOCODING_PROVIDER_ID;\n}\n\n/** Look up a provider by id, falling back to Nominatim for unknown ids. */\nexport function getGeocodingProvider(id: string | undefined | null): GeocodingProvider {\n return PROVIDERS_BY_ID.get(normalizeGeocodingProviderId(id)) ?? nominatimProvider;\n}\n\n/**\n * Resolve the geocoder configuration from runtime env, falling back to the\n * selected provider's default endpoints. `VITE_GEOCODER_PROVIDER` selects the\n * backend; `VITE_GEOCODER_ENDPOINT`/`VITE_GEOCODER_REVERSE_ENDPOINT` override\n * the endpoints (except for `arcgis-locator`, whose endpoints always come from\n * `VITE_GEOCODER_LOCATOR`); `VITE_GEOCODER_EMAIL` supplies the contact email; and\n * `VITE_GEOCODER_API_KEY` supplies the API key for keyed providers.\n */\nexport function getGeocoderConfig(): GeocoderConfig {\n const env = getRuntimeEnvironment();\n const provider = getGeocodingProvider(env.VITE_GEOCODER_PROVIDER);\n // The `arcgis-locator` provider's selected locator travels as JSON in\n // `VITE_GEOCODER_LOCATOR`, so the reverse-geocode plugin (which reads runtime\n // env, not the store) can build its requests.\n let locator: GeocodingLocator | undefined;\n if (provider.id === \"arcgis-locator\" && env.VITE_GEOCODER_LOCATOR) {\n try {\n locator = normalizeGeocodingLocator(JSON.parse(env.VITE_GEOCODER_LOCATOR)) ?? undefined;\n } catch {\n locator = undefined;\n }\n }\n const endpoints = locator ? locatorEndpoints(locator) : provider;\n // A locator's endpoints always come from its own URL: an override would send\n // its requests, and the portal token they may carry, somewhere else.\n const overrides = provider.id === \"arcgis-locator\" ? {} : env;\n return {\n providerId: provider.id,\n forwardEndpoint: overrides.VITE_GEOCODER_ENDPOINT?.trim() || endpoints.defaultForwardEndpoint,\n reverseEndpoint:\n overrides.VITE_GEOCODER_REVERSE_ENDPOINT?.trim() || endpoints.defaultReverseEndpoint,\n email: env.VITE_GEOCODER_EMAIL?.trim() || undefined,\n apiKey: env.VITE_GEOCODER_API_KEY?.trim() || undefined,\n ...(locator ? { locator } : {}),\n };\n}\n\n/** A locator's operation URLs, derived from its base GeocodeServer URL. */\nfunction locatorEndpoints(locator: GeocodingLocator): {\n defaultForwardEndpoint: string;\n defaultReverseEndpoint: string;\n} {\n return {\n defaultForwardEndpoint: `${locator.url}/findAddressCandidates`,\n defaultReverseEndpoint: `${locator.url}/reverseGeocode`,\n };\n}\n\n/**\n * The locator the `arcgis-locator` provider uses: the one `locatorId` names,\n * else the first configured.\n *\n * @param input - The geocoding preference.\n * @returns The locator, or undefined when none is configured.\n */\nexport function selectedGeocodingLocator(\n input: Pick<GeocodingPreferenceInput, \"locators\" | \"locatorId\">,\n): GeocodingLocator | undefined {\n const locators = input.locators ?? [];\n return locators.find((locator) => locator.id === input.locatorId) ?? locators[0];\n}\n\n/** Inputs to {@link resolveGeocoderConfig}, mirroring the project preference. */\nexport interface GeocodingPreferenceInput {\n providerId?: string;\n /** Per-provider API keys keyed by provider id. */\n apiKeys?: Record<string, string>;\n /** Optional custom forward endpoint (else the provider default). */\n forwardEndpoint?: string;\n /** Optional custom reverse endpoint (else the provider default). */\n reverseEndpoint?: string;\n email?: string;\n /** Configured ArcGIS GeocodeServer locators. */\n locators?: GeocodingLocator[];\n /** The locator the `arcgis-locator` provider uses. */\n locatorId?: string;\n}\n\n/**\n * Build a {@link GeocoderConfig} from structured project preferences. Used by\n * the geocode dialog so a per-run provider choice resolves the right endpoints\n * and API key without round-tripping through runtime env.\n */\nexport function resolveGeocoderConfig(input: GeocodingPreferenceInput): GeocoderConfig {\n const provider = getGeocodingProvider(input.providerId);\n if (provider.id === \"arcgis-locator\") {\n // A locator's endpoints come from its own URL; the project-wide overrides\n // belong to the built-in providers.\n const locator = selectedGeocodingLocator(input);\n const endpoints = locator ? locatorEndpoints(locator) : provider;\n return {\n providerId: provider.id,\n forwardEndpoint: endpoints.defaultForwardEndpoint,\n reverseEndpoint: endpoints.defaultReverseEndpoint,\n email: input.email?.trim() || undefined,\n ...(locator ? { locator } : {}),\n };\n }\n return {\n providerId: provider.id,\n forwardEndpoint: input.forwardEndpoint?.trim() || provider.defaultForwardEndpoint,\n reverseEndpoint: input.reverseEndpoint?.trim() || provider.defaultReverseEndpoint,\n email: input.email?.trim() || undefined,\n apiKey: input.apiKeys?.[provider.id]?.trim() || undefined,\n };\n}\n\n/** Hostname of `endpoint`, or null when it does not parse as a URL. */\nfunction geocoderHostname(endpoint: string): string | null {\n try {\n return new URL(endpoint).hostname;\n } catch {\n return null;\n }\n}\n\n/** Whether `hostname` is the public CartoCiudad service or a subdomain of it. */\nfunction isCartociudadHost(hostname: string | null): boolean {\n if (hostname === null) return false;\n return hostname === CARTOCIUDAD_PUBLIC_HOST || hostname.endsWith(`.${CARTOCIUDAD_PUBLIC_HOST}`);\n}\n\n/**\n * Whether requests to `endpoint` must be throttled AND row-capped. True for the\n * public Nominatim host (its usage policy applies) and, defensively, for any\n * endpoint that does not parse as a URL. Every other host (a self-hosted\n * Nominatim or a keyed provider) returns false.\n *\n * Note this is the *Nominatim policy* gate, not \"is this host paced at all\" —\n * see {@link geocoderMinIntervalMs}, which paces CartoCiudad more gently\n * without capping its row count.\n */\nexport function shouldThrottle(endpoint: string): boolean {\n const hostname = geocoderHostname(endpoint);\n return hostname === null || hostname === NOMINATIM_PUBLIC_HOST;\n}\n\n/**\n * The row cap to apply for `endpoint`: a finite cap for the public Nominatim\n * host, else Infinity. CartoCiudad is uncapped — IGN publishes no row limit and\n * its own bulk address geocoder processes up to 60,000 records per run, so a\n * cap here would be an invented constraint. It is still paced; see\n * {@link geocoderMinIntervalMs}.\n */\nexport function rowCap(endpoint: string): number {\n return shouldThrottle(endpoint) ? PUBLIC_GEOCODE_ROW_CAP : Number.POSITIVE_INFINITY;\n}\n\n/**\n * Minimum spacing (ms) between requests for `endpoint`. Gated on the hostname,\n * not the selected provider, so pointing any provider at the public Nominatim\n * host still honors its 1 req/sec policy.\n *\n * CartoCiudad gets a gentler pace of its own. IGN documents no rate limit for\n * the single-lookup `find`/`reverseGeocode` endpoints either way, so the honest\n * position is that unbounded bursting is unverified rather than sanctioned:\n * a large CSV run against a free public government service with zero spacing\n * risks getting the caller's IP throttled and degrading the service for\n * everyone. Nominatim's 1 req/sec is that provider's published policy and is\n * not borrowed here. Keyed providers and self-hosted endpoints are paced by\n * their own quotas, not by us.\n */\nexport function geocoderMinIntervalMs(endpoint: string): number {\n if (shouldThrottle(endpoint)) return NOMINATIM_MIN_INTERVAL_MS;\n return isCartociudadHost(geocoderHostname(endpoint)) ? CARTOCIUDAD_MIN_INTERVAL_MS : 0;\n}\n\n/** Hosted Pelias service that rejects keyless requests. */\nconst GEOCODE_EARTH_HOST_SUFFIX = \"geocode.earth\";\n\n/**\n * Whether `config` cannot geocode without an API key. True for the keyed\n * providers (ArcGIS, Mapbox, Google) and for Pelias only when it targets the\n * hosted geocode.earth endpoint (a self-hosted Pelias needs no key). Drives the\n * batch dialog's \"missing key\" warning and disabled run.\n */\nexport function geocoderNeedsApiKey(config: GeocoderConfig): boolean {\n const provider = getGeocodingProvider(config.providerId);\n if (provider.requiresApiKey) return true;\n if (provider.id === \"pelias\") {\n try {\n const host = new URL(config.forwardEndpoint).hostname;\n return host === GEOCODE_EARTH_HOST_SUFFIX || host.endsWith(`.${GEOCODE_EARTH_HOST_SUFFIX}`);\n } catch {\n return false;\n }\n }\n return false;\n}\n\n/**\n * Milliseconds to wait before starting the next request so consecutive\n * requests are spaced at least `intervalMs` apart, measured from the previous\n * request's start time (not its completion) so a slow network does not double\n * the wait. Returns 0 for the first request or when enough time has elapsed.\n */\nexport function nextDelayMs(lastStartedAt: number | null, now: number, intervalMs: number): number {\n if (lastStartedAt === null) return 0;\n return Math.max(0, intervalMs - (now - lastStartedAt));\n}\n\n/**\n * Build geocoding requests from parsed CSV rows. The address for each row is\n * the selected columns trimmed and joined with \", \" (so multi-part addresses\n * like street/city/country can be combined). Rows whose composed address is\n * empty are skipped.\n */\nexport function csvRowsToGeocodeRequests(\n rows: Record<string, string>[],\n addressColumns: string[],\n): GeocodeRequest[] {\n const requests: GeocodeRequest[] = [];\n rows.forEach((row, index) => {\n const address = addressColumns\n .map((column) => (row[column] ?? \"\").trim())\n .filter(Boolean)\n .join(\", \")\n .trim();\n if (!address) return;\n requests.push({ index, address, row });\n });\n return requests;\n}\n\n/**\n * The fetch implementation used by {@link geocodeForward}/{@link geocodeReverse}.\n * Null means \"use the global browser `fetch`\" (the default for the web and\n * embedded builds).\n */\nlet geocodingFetch: typeof globalThis.fetch | null = null;\n\n/**\n * Override the fetch used for geocoding requests, or pass null to restore the\n * global browser `fetch`.\n *\n * The desktop shell sets this to Tauri's native HTTP fetch so geocoding requests\n * are made from the Rust side, which:\n * - bypasses the WebView's CORS enforcement. Public Nominatim's CDN\n * intermittently omits the `Access-Control-Allow-Origin` header on cached\n * responses, which the browser then rejects — surfacing to the user as\n * \"Search failed. Try again.\" (the symptom that failed Microsoft Store\n * certification); and\n * - can send a `User-Agent` identifying the app, as Nominatim's usage policy\n * requires (browser fetch cannot set that header).\n *\n * Safe to call multiple times; only the most recent implementation is used.\n */\nexport function setGeocodingFetch(fetchImpl: typeof globalThis.fetch | null): void {\n geocodingFetch = fetchImpl;\n}\n\n/**\n * Resolves the access token for a secured locator, or undefined to send the\n * request without one. See {@link setGeocodingTokenProvider}.\n */\nexport type GeocodingTokenProvider = (locator: GeocodingLocator) => Promise<string | undefined>;\n\nlet geocodingTokenProvider: GeocodingTokenProvider | null = null;\n\n/**\n * Register how a secured locator gets its token, or pass null to remove it.\n *\n * Core has no sign-in of its own: the app installs a provider that reuses its\n * ArcGIS portal sessions. It is only consulted for a locator that names a\n * `portalUrl`, and the provider decides whether that portal's token may be sent\n * to the locator's host, so a token is never forwarded to an unrelated server.\n *\n * @param provider - The token provider, or null.\n */\nexport function setGeocodingTokenProvider(provider: GeocodingTokenProvider | null): void {\n geocodingTokenProvider = provider;\n}\n\n/** The token to send with a request for `config`, when its locator is secured. */\nasync function locatorToken(config: GeocoderConfig): Promise<string | undefined> {\n const locator = config.providerId === \"arcgis-locator\" ? config.locator : undefined;\n if (!locator?.portalUrl || !geocodingTokenProvider) return undefined;\n return geocodingTokenProvider(locator);\n}\n\n/**\n * `url` with the locator's token added, when there is one. The token is only\n * attached to a request under the locator's own GeocodeServer URL, whatever\n * endpoint the config carries.\n *\n * @returns The URL, and whether a token was attached (such a request must not\n * follow redirects, which could replay the token to another host).\n */\nasync function withLocatorToken(\n url: string,\n config: GeocoderConfig,\n): Promise<{ url: string; token: boolean }> {\n const token = await locatorToken(config);\n if (!token) return { url, token: false };\n const withToken = new URL(url);\n const base = `${config.locator?.url}/`;\n if (!`${withToken.origin}${withToken.pathname}`.startsWith(base)) {\n throw new GeocoderServiceError(\n \"The geocoding request does not go to the locator's own server, so its sign-in is not sent.\",\n );\n }\n withToken.searchParams.set(\"token\", token);\n return { url: withToken.toString(), token: true };\n}\n\n/**\n * Whether `config` can reverse-geocode: a locator only when its metadata\n * advertises `reverseGeocode` (unchecked locators are assumed to).\n *\n * @param config - The geocoder configuration.\n * @returns Whether reverse geocoding is available.\n */\nexport function geocoderSupportsReverse(config: GeocoderConfig): boolean {\n const provider = getGeocodingProvider(config.providerId);\n if (!provider.reverse) return false;\n return provider.id !== \"arcgis-locator\" || config.locator?.reverse !== false;\n}\n\n/**\n * Whether `config` offers suggestions while typing: a locator whose metadata\n * advertises `suggest`, searched through its single-line field (a suggestion\n * is a whole address, so it does not fit a structured field the user chose).\n *\n * @param config - The geocoder configuration.\n * @returns Whether {@link geocodeSuggest} can be used.\n */\nexport function geocoderSupportsSuggest(config: GeocoderConfig): boolean {\n const locator = config.providerId === \"arcgis-locator\" ? config.locator : undefined;\n if (!locator?.suggest || !locator.singleLineField) return false;\n return arcgisLocatorInputField(locator) === locator.singleLineField;\n}\n\n/**\n * Normalize an ArcGIS `suggest` response. Collection suggestions (a category\n * such as \"Coffee shop\") are dropped: they name many places, not one.\n *\n * @param data - The parsed response body.\n * @returns The suggestions.\n */\nexport function parseArcGISSuggestions(data: unknown): GeocodeSuggestion[] {\n const error = arcgisServiceError(data);\n if (error) throw error;\n const suggestions =\n data && typeof data === \"object\" ? (data as { suggestions?: unknown }).suggestions : undefined;\n if (!Array.isArray(suggestions)) return [];\n return suggestions.flatMap((entry) => {\n const text = readString(entry, \"text\")?.trim();\n const magicKey = readString(entry, \"magicKey\");\n const collection = (entry as { isCollection?: unknown } | null)?.isCollection === true;\n return text && magicKey && !collection ? [{ text, magicKey }] : [];\n });\n}\n\n/**\n * Ask the selected locator for suggestions that complete `text`. Resolve one\n * with {@link geocodeForward} and its `magicKey`.\n *\n * @param text - What the user has typed so far.\n * @param options - Abort signal, configuration and the most suggestions wanted.\n * @returns The suggestions.\n * @throws GeocoderUnsupportedError When the geocoder does not suggest.\n * @throws GeocoderServiceError For an HTTP or service error.\n */\nexport async function geocodeSuggest(\n text: string,\n options: { signal?: AbortSignal; config?: GeocoderConfig; limit?: number } = {},\n): Promise<GeocodeSuggestion[]> {\n const config = options.config ?? getGeocoderConfig();\n if (!geocoderSupportsSuggest(config)) {\n throw new GeocoderUnsupportedError(\"The selected geocoder does not offer suggestions.\");\n }\n const url = new URL(`${requireLocator(config).url}/suggest`);\n url.searchParams.set(\"text\", text);\n url.searchParams.set(\"f\", \"json\");\n if (options.limit) url.searchParams.set(\"maxSuggestions\", String(options.limit));\n const request = await withLocatorToken(url.toString(), config);\n const response = await geocodeFetch()(request.url, {\n signal: options.signal,\n headers: { Accept: \"application/json\" },\n ...(request.token ? { redirect: \"error\" as const } : {}),\n });\n if (!response.ok) {\n throw new GeocoderServiceError(`Geocoder returned HTTP ${response.status}`, response.status);\n }\n return parseArcGISSuggestions(await readGeocodeJson(response));\n}\n\n/**\n * Fetch and read a GeocodeServer's capabilities. A secured service is asked\n * with `token`.\n *\n * @param url - The locator's GeocodeServer URL (normalized first).\n * @param options - An abort signal and an optional token.\n * @returns The capabilities.\n * @throws GeocoderServiceError For an HTTP or service error.\n */\nexport async function fetchArcGISLocatorInfo(\n url: string,\n options: { signal?: AbortSignal; token?: string } = {},\n): Promise<ArcGISLocatorCapabilities> {\n const base = normalizeArcGISLocatorUrl(url);\n if (!base) throw new GeocoderServiceError(\"Not an ArcGIS GeocodeServer URL.\");\n const request = new URL(base);\n request.searchParams.set(\"f\", \"json\");\n if (options.token) request.searchParams.set(\"token\", options.token);\n const response = await geocodeFetch()(request.toString(), {\n signal: options.signal,\n headers: { Accept: \"application/json\" },\n // A redirect would replay the token to wherever it points.\n ...(options.token ? { redirect: \"error\" as const } : {}),\n });\n if (!response.ok) {\n throw new GeocoderServiceError(`Geocoder returned HTTP ${response.status}`, response.status);\n }\n return parseArcGISLocatorInfo(await readGeocodeJson(response));\n}\n\n/** The active geocoding fetch: the injected override, else the global fetch. */\nfunction geocodeFetch(): typeof globalThis.fetch {\n return geocodingFetch ?? fetch;\n}\n\n/**\n * Read a geocoder response body as JSON, treating an empty one as \"no results\".\n *\n * CartoCiudad answers an unmatched query with HTTP 204 and an empty body rather\n * than a JSON null (verified against the live `find` and `reverseGeocode`\n * endpoints). `response.ok` is true for 204, so parsing unconditionally would\n * throw a JSON syntax error and surface a nonsense address as \"Search failed\"\n * instead of \"no match\". Returning undefined lets each provider's parser report\n * no results the way it already does for a null body.\n */\nasync function readGeocodeJson(response: Response): Promise<unknown> {\n if (response.status === 204) return undefined;\n const text = await response.text();\n if (!text.trim()) return undefined;\n return JSON.parse(text) as unknown;\n}\n\n/**\n * Normalize a longitude into [-180, 180], or null when it is not finite.\n *\n * MapLibre reports `lngLat.lng` unwrapped once the map is zoomed out far enough\n * to show repeated world copies, so a click on a non-primary copy arrives as\n * e.g. 210 rather than -150. Rejecting those outright would silently drop a\n * legitimate reverse-geocode click for every provider, so wrap instead.\n *\n * An in-range longitude returns untouched: the modulo round-trip is not exact\n * in floating point (-3.7 comes back as -3.7000000000000455), and the common\n * case should not be perturbed to handle the rare one.\n */\nfunction wrapLongitude(lon: number): number | null {\n if (!Number.isFinite(lon)) return null;\n if (lon >= -180 && lon <= 180) return lon;\n return ((((lon + 180) % 360) + 360) % 360) - 180;\n}\n\n/**\n * Forward-geocode a single query through the configured provider, returning\n * normalized matches.\n */\nexport async function geocodeForward(\n query: string,\n options: {\n signal?: AbortSignal;\n config?: GeocoderConfig;\n limit?: number;\n /** A {@link GeocodeSuggestion}'s key, to resolve that suggestion. */\n magicKey?: string;\n /** Ask for each candidate's own attributes, where the provider supports it. */\n attributes?: boolean;\n } = {},\n): Promise<GeocodeMatch[]> {\n const config = options.config ?? getGeocoderConfig();\n const provider = getGeocodingProvider(config.providerId);\n const request = await withLocatorToken(\n provider.buildForwardUrl(config, query, {\n email: config.email,\n limit: options.limit,\n magicKey: options.magicKey,\n attributes: options.attributes,\n }),\n config,\n );\n const response = await geocodeFetch()(request.url, {\n signal: options.signal,\n headers: { Accept: \"application/json\" },\n ...(request.token ? { redirect: \"error\" as const } : {}),\n });\n if (!response.ok) {\n throw new GeocoderServiceError(`Geocoder returned HTTP ${response.status}`, response.status);\n }\n const data: unknown = await readGeocodeJson(response);\n return provider.parseForward(data);\n}\n\n/**\n * Reverse-geocode a single point ([lon, lat]) through the configured provider,\n * returning a normalized display result or null when no address was found.\n */\nexport async function geocodeReverse(\n lon: number,\n lat: number,\n options: { signal?: AbortSignal; config?: GeocoderConfig; zoom?: number } = {},\n): Promise<ReverseGeocodeDisplay | null> {\n const wrappedLon = wrapLongitude(lon);\n if (wrappedLon === null || !Number.isFinite(lat) || lat < -90 || lat > 90) {\n return null;\n }\n const config = options.config ?? getGeocoderConfig();\n const provider = getGeocodingProvider(config.providerId);\n if (!geocoderSupportsReverse(config)) {\n throw new GeocoderUnsupportedError(\"The selected geocoder does not support reverse geocoding.\");\n }\n const request = await withLocatorToken(\n provider.buildReverseUrl(config, wrappedLon, lat, {\n email: config.email,\n zoom: options.zoom,\n }),\n config,\n );\n const response = await geocodeFetch()(request.url, {\n signal: options.signal,\n headers: { Accept: \"application/json\" },\n ...(request.token ? { redirect: \"error\" as const } : {}),\n });\n if (!response.ok) {\n throw new GeocoderServiceError(`Geocoder returned HTTP ${response.status}`, response.status);\n }\n const data: unknown = await readGeocodeJson(response);\n return provider.parseReverse(data);\n}\n","import { normalizeArcgisWkid } from \"./arcgis-projection\";\nimport { normalizeMapCrs } from \"./map-crs\";\nimport { normalizeCesiumBasemap } from \"./cesium-imagery\";\nimport { parseCesiumIonAssetId } from \"./cesium-ion\";\nimport { redactUrlCredentials } from \"./credentials\";\nimport { normalizeStoryChapterDurationSeconds } from \"./storymap-duration\";\nimport { v4 as uuidv4 } from \"uuid\";\nimport {\n DEFAULT_BASEMAP,\n DEFAULT_LAYER_STYLE,\n DEFAULT_LEGEND_CONFIG,\n DEFAULT_PROJECT_PREFERENCES,\n DEFAULT_DASHBOARD_COLUMNS,\n DEFAULT_MAP_GRID_LAYOUT,\n DEFAULT_PRIMARY_RENDERER,\n DEFAULT_STORY_MAP,\n MAX_DASHBOARD_COLUMNS,\n MAX_MAP_GRID_DIM,\n MIN_DASHBOARD_COLUMNS,\n PROJECT_VERSION,\n type DashboardWidget,\n type ProjectBookmark,\n type ProjectBookmarkGroup,\n type DashboardWidgetAggregation,\n type DashboardWidgetType,\n type GeoLibreLayer,\n type GeoLibreProject,\n type IndicatorAggregation,\n type LayerGroup,\n type LayerStyle,\n type LegendConfig,\n type LegendCustomEntry,\n type LegendCustomItem,\n type LegendItemOverride,\n type MapGridLayout,\n type MapRendererKind,\n type MapScaleUnit,\n type MapViewState,\n MAX_PROCESSING_HISTORY,\n type ModelGraphEdge,\n type ModelGraphNode,\n type ModelGraphNodeKind,\n type ModelToolProvider,\n type ProcessingModel,\n type ProcessingModelGraph,\n type ProcessingRun,\n type ProcessingRunKind,\n type SecondaryMapView,\n type ProcessingModelStep,\n type ProjectPluginControlPosition,\n type ProjectPluginState,\n type ProjectPreferences,\n type RuntimeEnvironmentVariable,\n type StoryChapter,\n type StoryChapterAlignment,\n type StoryChapterAnimation,\n type StoryInsetPosition,\n type StoryLayerOpacityChange,\n type StoryMap,\n type StorySlideMode,\n type StyleLibraryEntry,\n type CommentAnchor,\n type CommentAuthor,\n type CommentReply,\n type ProjectComment,\n type ProjectControlSetting,\n type ProjectInteraction,\n} from \"./types\";\nimport { DEFAULT_LAYER_GROUP_OPACITY, normalizeGroupContiguity } from \"./layer-groups\";\nimport { normalizeStyleLibraryEntries } from \"./style-library\";\nimport { normalizeLayerCapabilities } from \"./capabilities\";\nimport { withRasterSourceDefaults } from \"./raster-source-defaults\";\nimport { validateMapExpression } from \"./expressions\";\nimport { normalizeLayerDescriptiveMetadata } from \"./layer-descriptive-metadata\";\nimport {\n createDefaultPrintLayout,\n isDefaultPrintLayout,\n normalizePrintLayoutConfig,\n scrubPrintLayoutForLayers,\n type PrintLayoutConfig,\n} from \"./print-layout-config\";\nimport { getEllipsoid } from \"./ellipsoids\";\nimport { normalizeGeocodingLocator } from \"./geocoding\";\nimport {\n scrubWidgetsForRemovedLayers,\n scrubCommentsForRemovedLayers,\n scrubLegendForRemovedLayers,\n} from \"./layer-ref-scrub\";\n\n/** Placeholder name a project carries before the user names it. */\nexport const DEFAULT_PROJECT_NAME = \"Untitled Project\";\n\nexport interface CreateProjectOptions {\n basemapStyleUrl?: string;\n mapView?: MapViewState;\n /** Celestial body the project describes; defaults to Earth when omitted. */\n ellipsoidId?: string;\n}\n\nexport function normalizeBlankBackgroundColor(value: unknown): string | null {\n return typeof value === \"string\" && /^#[0-9a-f]{6}$/i.test(value) ? value : null;\n}\n\nexport function createDefaultMapView(): MapViewState {\n return {\n center: [-100, 40],\n zoom: 2,\n bearing: 0,\n pitch: 0,\n };\n}\n\nexport function createEmptyProject(\n name = DEFAULT_PROJECT_NAME,\n options: CreateProjectOptions = {},\n): GeoLibreProject {\n return {\n version: PROJECT_VERSION,\n name,\n mapView: options.mapView ?? createDefaultMapView(),\n basemapStyleUrl: options.basemapStyleUrl ?? DEFAULT_BASEMAP,\n basemapVisible: true,\n basemapOpacity: 1,\n blankBackgroundColor: null,\n layers: [],\n layerGroups: [],\n styles: {},\n // A copy, never the shared constant: a caller that edits the new project's\n // preferences — `project.preferences.map.cesiumBasemap = …` — would\n // otherwise rewrite the app-wide defaults for the rest of the process, and\n // every later \"what is the default\" read would answer with its edit.\n preferences: {\n ...DEFAULT_PROJECT_PREFERENCES,\n map: {\n ...DEFAULT_PROJECT_PREFERENCES.map,\n ...(options.ellipsoidId ? { ellipsoidId: getEllipsoid(options.ellipsoidId).id } : {}),\n },\n },\n legend: { ...DEFAULT_LEGEND_CONFIG },\n comments: [],\n metadata: {},\n };\n}\n\n/**\n * GeoJSON `type` values that mark a subtree as feature data rather than project\n * structure. Everything under one of these is written compactly by\n * {@link serializeProject}.\n */\nconst GEOJSON_TYPES = new Set([\n \"FeatureCollection\",\n \"Feature\",\n \"GeometryCollection\",\n \"Point\",\n \"MultiPoint\",\n \"LineString\",\n \"MultiLineString\",\n \"Polygon\",\n \"MultiPolygon\",\n]);\n\n/** One level of indentation in a serialized project. */\nconst PROJECT_INDENT = \" \";\n\n/**\n * Whether a value is a GeoJSON feature, geometry, or collection.\n *\n * Decided from the `type` string alone, so this is an implicit contract on the\n * project schema: no field may store a non-GeoJSON object under a `type` of one\n * of the nine {@link GEOJSON_TYPES} names, or it would silently be written\n * compact as if it were feature data. Nothing in `types.ts` does today; a new\n * field that wants one of those names (a drawing-tool or geometry-filter config,\n * say) needs a different discriminator.\n */\nfunction isGeoJsonValue(value: object): boolean {\n const type = (value as { type?: unknown }).type;\n return typeof type === \"string\" && GEOJSON_TYPES.has(type);\n}\n\n/**\n * Serialize a value exactly as `JSON.stringify(value, null, 2)` would, except\n * that GeoJSON features, geometries and collections are written compactly on a\n * single line.\n *\n * Coordinate arrays are never hand-edited, but indenting them costs roughly\n * three bytes of whitespace for every one byte of data: a project embedding\n * 13,000 features weighed 179 MB pretty-printed and 54 MB compact\n * (GeoLibre#1829), which is the difference between reopening and running the\n * tab out of memory. The surrounding project structure stays indented so the\n * file is still readable and diffs still make sense.\n *\n * @param value Value to serialize.\n * @param depth Current nesting depth, driving the indent width.\n * @param key Property name (or stringified array index) this value sits under,\n * `\"\"` at the root — the argument `JSON.stringify` passes to `toJSON`.\n * @param ancestors Containers currently open on the recursion stack, used to\n * detect cycles.\n * @param presets Objects whose serialized text is already known at the depth\n * they sit at, written verbatim instead of being walked again. Used by\n * {@link serializeProjectWithLayerCache} to splice in cached layers.\n * @returns The serialized text, or undefined for values `JSON.stringify` also\n * drops (undefined, functions, symbols).\n */\nfunction serializeProjectValue(\n value: unknown,\n depth: number,\n key: string,\n ancestors: Set<object>,\n presets?: ReadonlyMap<object, string>,\n): string | undefined {\n if (presets !== undefined && value !== null && typeof value === \"object\") {\n const preset = presets.get(value);\n if (preset !== undefined) return preset;\n }\n if (value !== null && typeof value === \"object\") {\n // Honor the toJSON hook the way JSON.stringify does, so a value that\n // replaces itself is inspected in its replaced form. It receives the same\n // key JSON.stringify would pass, since a custom hook may branch on it.\n const toJSON = (value as { toJSON?: unknown }).toJSON;\n if (typeof toJSON === \"function\") {\n value = (toJSON as (key: string) => unknown).call(value, key);\n }\n }\n if (value === null || typeof value !== \"object\") return JSON.stringify(value);\n // A boxed Number/String/Boolean writes as its primitive rather than as an\n // object, the way JSON.stringify unwraps it.\n if (value instanceof Number || value instanceof String || value instanceof Boolean) {\n return JSON.stringify(value);\n }\n // Feature data: hand the whole subtree to JSON.stringify with no spacing.\n if (isGeoJsonValue(value)) return JSON.stringify(value);\n\n // A cycle would recurse until the stack overflowed, and the RangeError that\n // raises is indistinguishable from the string-length cap the save path reads\n // as \"project too large\". Fail the way JSON.stringify does instead. Only the\n // open ancestors are tracked, so a value referenced twice side by side (not a\n // cycle) still serializes.\n if (ancestors.has(value)) throw new TypeError(\"Converting circular structure to JSON\");\n ancestors.add(value);\n try {\n const pad = PROJECT_INDENT.repeat(depth + 1);\n const closePad = PROJECT_INDENT.repeat(depth);\n if (Array.isArray(value)) {\n if (value.length === 0) return \"[]\";\n // Indexed rather than mapped so a sparse array's holes are visited: like\n // an unserializable entry, a hole becomes null, matching JSON.stringify.\n const entries = Array.from(\n { length: value.length },\n (_unused, index) =>\n serializeProjectValue(value[index], depth + 1, String(index), ancestors, presets) ??\n \"null\",\n );\n return `[\\n${pad}${entries.join(`,\\n${pad}`)}\\n${closePad}]`;\n }\n const entries: string[] = [];\n for (const [entryKey, entry] of Object.entries(value)) {\n const serialized = serializeProjectValue(entry, depth + 1, entryKey, ancestors, presets);\n // An unserializable object value is omitted, matching JSON.stringify.\n if (serialized === undefined) continue;\n entries.push(`${JSON.stringify(entryKey)}: ${serialized}`);\n }\n if (entries.length === 0) return \"{}\";\n return `{\\n${pad}${entries.join(`,\\n${pad}`)}\\n${closePad}}`;\n } finally {\n ancestors.delete(value);\n }\n}\n\n/**\n * Serialize a project to `.geolibre.json` text: indented project structure with\n * compact (unindented) embedded GeoJSON. See {@link serializeProjectValue} for\n * why the two halves are formatted differently.\n *\n * @param project Project to serialize.\n * @returns The file contents to write.\n */\nexport function serializeProject(project: GeoLibreProject): string {\n return (\n serializeProjectValue(\n { ...project, layers: project.layers.map(portableLayer) },\n 0,\n \"\",\n new Set(),\n ) ?? \"null\"\n );\n}\n\n/**\n * Per-layer serialized text kept across {@link serializeProjectWithLayerCache}\n * calls, keyed by the layer record each entry was built from. A `WeakMap`, so a\n * layer the store has replaced or removed drops its text with it.\n */\nexport type ProjectLayerSerializationCache = WeakMap<object, { index: number; text: string }>;\n\n/** Create an empty {@link ProjectLayerSerializationCache}. */\nexport function createProjectLayerSerializationCache(): ProjectLayerSerializationCache {\n return new WeakMap();\n}\n\n/**\n * Serialize a project exactly as {@link serializeProject} does, reusing the\n * text of every layer whose source record is unchanged since an earlier call.\n *\n * Feature data lives inside layer records, so re-stringifying every layer on\n * each autosave costs megabytes of main-thread work for a project with large\n * GeoJSON layers — even when only the camera moved (GeoLibre#2633). The store\n * replaces a layer object whenever it changes, so object identity is a sound\n * cache key: a camera move reuses every layer, and an edit to one layer\n * re-serializes only that one.\n *\n * `layerSources[i]` must be the record `project.layers[i]` was derived from\n * (e.g. the store's `layers`, before `projectFromStore` prepared them), and that\n * derivation must depend on the record alone — as `buildProjectSnapshot`'s does\n * — or a cached entry could outlive a change it should have reflected. When the\n * two arrays differ in length the cache is bypassed rather than trusted.\n *\n * @param project Project to serialize, as built from `layerSources`.\n * @param layerSources Immutable source record for each entry of\n * `project.layers`, in the same order, used as the cache key.\n * @param cache Cache shared between calls; see\n * {@link createProjectLayerSerializationCache}.\n * @returns The same text {@link serializeProject} returns for `project`.\n */\nexport function serializeProjectWithLayerCache(\n project: GeoLibreProject,\n layerSources: readonly object[],\n cache: ProjectLayerSerializationCache,\n): string {\n const steps = serializeProjectWithLayerCacheSteps(project, layerSources, cache);\n for (;;) {\n const step = steps.next();\n if (step.done) return step.value;\n }\n}\n\n/** Time a main-thread serialization slice may run before it yields (ms). */\nconst SERIALIZE_SLICE_MS = 8;\n\n/** Resolve on a macrotask, letting input and paint run in between slices. */\nfunction yieldToMainThread(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, 0));\n}\n\n/**\n * {@link serializeProjectWithLayerCache} that hands the main thread back\n * between layers, so autosave on a project with several large layers is many\n * short tasks instead of one 300-400 ms freeze (GeoLibre#2869).\n *\n * The cache makes a camera move free, so the cost lands on the first autosave\n * and on edits to a big layer: each layer that misses the cache is serialized\n * in its own slice, and the thread is released once a slice has run longer than\n * a few milliseconds. A single huge layer is still one slice. The result is\n * identical to the synchronous call. `project` and `layerSources` must not be\n * mutated while this runs; the store replaces records rather than editing them,\n * so a snapshot taken from it stays valid.\n *\n * @param project Project to serialize, as built from `layerSources`.\n * @param layerSources Immutable source record for each entry of\n * `project.layers`, in the same order, used as the cache key.\n * @param cache Cache shared between calls.\n * @param yieldFn Hands the thread back; replaced in tests.\n * @returns The same text {@link serializeProject} returns for `project`.\n */\nexport async function serializeProjectWithLayerCacheAsync(\n project: GeoLibreProject,\n layerSources: readonly object[],\n cache: ProjectLayerSerializationCache,\n yieldFn: () => Promise<void> = yieldToMainThread,\n): Promise<string> {\n const steps = serializeProjectWithLayerCacheSteps(project, layerSources, cache);\n let sliceStart = Date.now();\n for (;;) {\n const step = steps.next();\n if (step.done) return step.value;\n if (Date.now() - sliceStart >= SERIALIZE_SLICE_MS) {\n await yieldFn();\n sliceStart = Date.now();\n }\n }\n}\n\n/** Shared body of the sync and async serializers; yields once per layer text built. */\nfunction* serializeProjectWithLayerCacheSteps(\n project: GeoLibreProject,\n layerSources: readonly object[],\n cache: ProjectLayerSerializationCache,\n): Generator<void, string, void> {\n if (layerSources.length !== project.layers.length) return serializeProject(project);\n const layers = project.layers.map(portableLayer);\n // Presets are keyed by layer object, so a record listed twice would get one\n // index's text in both places. The store never does that; bypass if it does.\n if (new Set(layers).size !== layers.length) return serializeProject(project);\n const presets = new Map<object, string>();\n for (let index = 0; index < layers.length; index += 1) {\n const layer = layers[index];\n const source = layerSources[index];\n const cached = cache.get(source);\n // The layer is serialized under its array index as the key (a `toJSON`\n // hook would see it), so text is reused only at the index it was built for.\n let text = cached?.index === index ? cached.text : undefined;\n if (text === undefined) {\n // Depth 2: the root object is depth 0 and its `layers` array depth 1.\n text = serializeProjectValue(layer, 2, String(index), new Set()) ?? \"null\";\n cache.set(source, { index, text });\n yield;\n }\n presets.set(layer, text);\n }\n return serializeProjectValue({ ...project, layers }, 0, \"\", new Set(), presets) ?? \"null\";\n}\n\nexport function parseProject(json: string): GeoLibreProject {\n const data = JSON.parse(json) as Partial<GeoLibreProject>;\n if (!data.version || !data.name || !data.mapView) {\n throw new Error(\"Invalid GeoLibre project: missing required fields\");\n }\n const layerGroups = normalizeLayerGroups(data.layerGroups);\n const validGroupIds = new Set(layerGroups.map((g) => g.id));\n const layers = (data.layers ?? [])\n .map(normalizeLayer)\n .map((layer) =>\n layer.groupId && !validGroupIds.has(layer.groupId) ? { ...layer, groupId: undefined } : layer,\n );\n const selectedLayerId =\n data.selectedLayerId === null\n ? null\n : typeof data.selectedLayerId === \"string\" &&\n layers.some((layer) => layer.id === data.selectedLayerId)\n ? data.selectedLayerId\n : undefined;\n const basemapStyleUrl = data.basemapStyleUrl ?? DEFAULT_BASEMAP;\n const basemapVisible = data.basemapVisible ?? true;\n const basemapOpacity = data.basemapOpacity ?? 1;\n const blankBackgroundColor = normalizeBlankBackgroundColor(data.blankBackgroundColor);\n // Secondary panes already go through normalizeMapViewState; the primary\n // camera must too so a hand-edited project cannot store an out-of-range\n // view that MapLibre would silently clamp, leaving saved state wrong.\n const mapView = normalizeMapViewState(data.mapView);\n const { mapLayout, secondaryMapViews } = resolveMapGrid(\n normalizeMapLayout(data.mapLayout),\n normalizeSecondaryMapViews(data.secondaryMapViews),\n { mapView },\n );\n const styleLibrary = normalizeStyleLibraryEntries(data.styleLibrary);\n const parsedComments = normalizeProjectComments(data.comments);\n const parsedInteraction = normalizeProjectInteraction(data.interaction);\n return {\n version: data.version,\n name: data.name,\n mapView,\n basemapStyleUrl,\n basemapVisible,\n basemapOpacity,\n blankBackgroundColor,\n layers,\n ...(selectedLayerId !== undefined ? { selectedLayerId } : {}),\n ...(layerGroups.length > 0 ? { layerGroups } : {}),\n styles: data.styles ?? {},\n preferences: normalizeProjectPreferences(data.preferences),\n plugins: normalizeProjectPlugins(data.plugins) ?? undefined,\n legend: normalizeLegendConfig(data.legend),\n printLayout: normalizePrintLayoutConfig(data.printLayout) ?? undefined,\n storymap: normalizeStoryMap(data.storymap) ?? undefined,\n models: normalizeModels(data.models) ?? undefined,\n processingHistory: normalizeProcessingHistory(data.processingHistory) ?? undefined,\n obia: normalizeObiaWorkbench(data.obia) ?? undefined,\n widgets: normalizeWidgets(data.widgets) ?? undefined,\n ...bookmarkFields(data.bookmarks, data.bookmarkGroups),\n ...(data.dashboardColumns === undefined\n ? {}\n : { dashboardColumns: normalizeDashboardColumns(data.dashboardColumns) }),\n // Only persist the grid when it is larger than a single pane, so default\n // single-map projects serialize byte-identically to before this feature.\n ...(mapLayout.rows * mapLayout.cols > 1\n ? {\n mapLayout,\n secondaryMapViews,\n ...(normalizeString(data.primaryMapLabel)\n ? { primaryMapLabel: normalizeString(data.primaryMapLabel) }\n : {}),\n }\n : {}),\n // The primary renderer is independent of the grid, so it sits outside the\n // `mapLayout` block above: a 1x1 Cesium project has no grid to persist.\n ...(normalizePrimaryRenderer(data.primaryRenderer)\n ? { primaryRenderer: normalizePrimaryRenderer(data.primaryRenderer)! }\n : {}),\n ...(styleLibrary.length > 0 ? { styleLibrary } : {}),\n ...(parsedComments.length > 0 ? { comments: parsedComments } : {}),\n ...(parsedInteraction ? { interaction: parsedInteraction } : {}),\n metadata: data.metadata ?? {},\n };\n}\n\n/**\n * Coerce an untrusted (possibly hand-edited) `layerGroups` array into valid\n * {@link LayerGroup} records, dropping entries without a usable id and\n * de-duplicating by id. Always returns an array (empty when absent).\n *\n * @param value Raw `layerGroups` value from the project JSON.\n * @returns Normalized, de-duplicated group definitions.\n */\nfunction normalizeLayerGroups(value: unknown): LayerGroup[] {\n if (!Array.isArray(value)) return [];\n const groups: LayerGroup[] = [];\n const seen = new Set<string>();\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue;\n const candidate = entry as Partial<LayerGroup>;\n const id = typeof candidate.id === \"string\" ? candidate.id.trim() : \"\";\n if (!id || seen.has(id)) continue;\n seen.add(id);\n const opacity =\n typeof candidate.opacity === \"number\" && Number.isFinite(candidate.opacity)\n ? Math.min(Math.max(candidate.opacity, 0), 1)\n : DEFAULT_LAYER_GROUP_OPACITY;\n groups.push({\n id,\n name: typeof candidate.name === \"string\" ? candidate.name : id,\n ...(typeof candidate.parentId === \"string\" && candidate.parentId.trim()\n ? { parentId: candidate.parentId.trim() }\n : {}),\n collapsed: candidate.collapsed === true,\n visible: candidate.visible !== false,\n opacity,\n });\n }\n const ids = new Set(groups.map((group) => group.id));\n const byId = new Map(groups.map((group) => [group.id, group]));\n return groups.map((group) => {\n if (!group.parentId || !ids.has(group.parentId) || group.parentId === group.id) {\n return group.parentId ? { ...group, parentId: undefined } : group;\n }\n let id: string | undefined = group.parentId;\n const seen = new Set([group.id]);\n while (id) {\n if (seen.has(id)) return { ...group, parentId: undefined };\n seen.add(id);\n id = byId.get(id)?.parentId;\n }\n return group;\n });\n}\n\n/**\n * Coerce an untrusted (possibly hand-edited) legend config into a valid\n * {@link LegendConfig}, dropping malformed entries. Returns undefined when no\n * usable config is present so the default is applied downstream.\n */\nexport function normalizeLegendConfig(legend: unknown): LegendConfig | undefined {\n if (!legend || typeof legend !== \"object\") return undefined;\n const candidate = legend as Partial<LegendConfig>;\n\n const order = Array.isArray(candidate.order) ? uniqueStrings(candidate.order) : [];\n\n const overrides: Record<string, LegendItemOverride> = {};\n if (candidate.overrides && typeof candidate.overrides === \"object\") {\n for (const [key, value] of Object.entries(candidate.overrides)) {\n if (!key.trim() || !value || typeof value !== \"object\") continue;\n const override = value as Partial<LegendItemOverride>;\n const normalized: LegendItemOverride = {};\n // Mirror setLegendItemLabel / renderedLabel: a blank or whitespace-only\n // label is treated as \"no override\", so don't persist it.\n if (typeof override.label === \"string\" && override.label.trim() !== \"\") {\n normalized.label = override.label;\n }\n // Only the truthy hidden flag is meaningful; `hidden: false` is the\n // default, so dropping it keeps round-tripped projects from accumulating\n // no-op overrides (matches what the UI mutations store).\n if (override.hidden === true) normalized.hidden = true;\n if (normalized.label !== undefined || normalized.hidden !== undefined) {\n overrides[key.trim()] = normalized;\n }\n }\n }\n\n // Hand-authored entries: keep only well-formed items (string label + color);\n // an entry whose items all fail validation is dropped entirely so the panel\n // never renders an empty custom section from a hand-edited file.\n const customEntries: Record<string, LegendCustomEntry> = {};\n if (\n candidate.customEntries &&\n typeof candidate.customEntries === \"object\" &&\n !Array.isArray(candidate.customEntries)\n ) {\n for (const [key, value] of Object.entries(candidate.customEntries)) {\n if (!key.trim() || !value || typeof value !== \"object\" || Array.isArray(value)) continue;\n const entry = value as Partial<LegendCustomEntry>;\n if (!Array.isArray(entry.items)) continue;\n const items: LegendCustomItem[] = [];\n for (const item of entry.items) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) continue;\n const row = item as Partial<LegendCustomItem>;\n if (typeof row.label !== \"string\" || typeof row.color !== \"string\") continue;\n const shape =\n row.shape === \"circle\" || row.shape === \"line\" || row.shape === \"square\"\n ? row.shape\n : undefined;\n // Proportional symbol size in map pixels; bounded so a hand-edited file\n // cannot ask the panel for an absurd swatch.\n const size =\n typeof row.size === \"number\" && Number.isFinite(row.size) && row.size > 0\n ? Math.min(row.size, 1000)\n : undefined;\n items.push({\n label: row.label,\n color: row.color,\n ...(shape ? { shape } : {}),\n ...(size !== undefined ? { size } : {}),\n });\n }\n if (items.length === 0) continue;\n customEntries[key.trim()] = {\n ...(typeof entry.title === \"string\" && entry.title.trim() !== \"\"\n ? { title: entry.title }\n : {}),\n items,\n };\n }\n }\n\n const panelPosition =\n candidate.panelPosition === \"top-left\" ||\n candidate.panelPosition === \"top-right\" ||\n candidate.panelPosition === \"bottom-left\" ||\n candidate.panelPosition === \"bottom-right\"\n ? candidate.panelPosition\n : undefined;\n\n // Hand-resized panel dimensions: keep only sane finite values so a\n // hand-edited file can't collapse the panel or blow it past any viewport.\n const panelSize = (value: unknown): number | undefined =>\n typeof value === \"number\" && Number.isFinite(value) && value >= 120 && value <= 4000\n ? Math.round(value)\n : undefined;\n const panelWidth = panelSize(candidate.panelWidth);\n const panelHeight = panelSize(candidate.panelHeight);\n\n return {\n title: typeof candidate.title === \"string\" ? candidate.title : DEFAULT_LEGEND_CONFIG.title,\n groupByLayer: normalizeBoolean(candidate.groupByLayer, DEFAULT_LEGEND_CONFIG.groupByLayer),\n order,\n overrides,\n ...(Object.keys(customEntries).length > 0 ? { customEntries } : {}),\n ...(candidate.panelVisible === true ? { panelVisible: true } : {}),\n ...(candidate.panelCollapsed === true ? { panelCollapsed: true } : {}),\n ...(panelPosition ? { panelPosition } : {}),\n ...(panelWidth !== undefined ? { panelWidth } : {}),\n ...(panelHeight !== undefined ? { panelHeight } : {}),\n };\n}\n\n/**\n * Validate and coerce a story map loaded from an untrusted project file.\n *\n * Returns null when the value carries no chapters so empty story maps stay out\n * of the saved project, mirroring how plugins are only persisted when present.\n *\n * @param storymap Raw value read from the project JSON.\n * @returns A normalized story map, or null when there is nothing to keep.\n */\nexport function normalizeStoryMap(storymap: unknown): StoryMap | null {\n if (!storymap || typeof storymap !== \"object\") return null;\n\n const candidate = storymap as Partial<StoryMap>;\n // Drop duplicate chapter ids so updates/removals stay unambiguous and keyed\n // rendering stays stable.\n const seenChapterIds = new Set<string>();\n const chapters = Array.isArray(candidate.chapters)\n ? candidate.chapters.map(normalizeStoryChapter).filter((chapter): chapter is StoryChapter => {\n if (!chapter || seenChapterIds.has(chapter.id)) return false;\n seenChapterIds.add(chapter.id);\n return true;\n })\n : [];\n\n const normalized: StoryMap = {\n title: normalizeString(candidate.title),\n subtitle: normalizeString(candidate.subtitle),\n byline: normalizeString(candidate.byline),\n footer: normalizeString(candidate.footer),\n theme: candidate.theme === \"light\" ? \"light\" : \"dark\",\n showMarkers: normalizeBoolean(candidate.showMarkers, false),\n markerColor: normalizeString(candidate.markerColor) || DEFAULT_STORY_MAP.markerColor,\n inset: normalizeBoolean(candidate.inset, false),\n insetPosition: STORY_INSET_POSITIONS.has(candidate.insetPosition as StoryInsetPosition)\n ? (candidate.insetPosition as StoryInsetPosition)\n : DEFAULT_STORY_MAP.insetPosition,\n hideChapterNav: normalizeBoolean(candidate.hideChapterNav, false),\n startSlide: STORY_SLIDE_MODES.has(candidate.startSlide as StorySlideMode)\n ? (candidate.startSlide as StorySlideMode)\n : DEFAULT_STORY_MAP.startSlide,\n endSlide: STORY_SLIDE_MODES.has(candidate.endSlide as StorySlideMode)\n ? (candidate.endSlide as StorySlideMode)\n : DEFAULT_STORY_MAP.endSlide,\n chapters,\n };\n\n // Keep the story if it has chapters or any author-entered settings; only a\n // wholly-default, chapter-less story is dropped (so blank stories stay out of\n // saved projects without discarding settings entered before the first chapter).\n return storyMapHasContent(normalized) ? normalized : null;\n}\n\n/** Whether a story map carries chapters or any non-default setting. */\nexport function storyMapHasContent(story: StoryMap): boolean {\n if (story.chapters.length > 0) return true;\n return (\n story.title.trim() !== \"\" ||\n story.subtitle.trim() !== \"\" ||\n story.byline.trim() !== \"\" ||\n story.footer.trim() !== \"\" ||\n story.theme !== DEFAULT_STORY_MAP.theme ||\n story.showMarkers !== DEFAULT_STORY_MAP.showMarkers ||\n story.markerColor !== DEFAULT_STORY_MAP.markerColor ||\n story.inset !== DEFAULT_STORY_MAP.inset ||\n story.insetPosition !== DEFAULT_STORY_MAP.insetPosition ||\n story.hideChapterNav !== DEFAULT_STORY_MAP.hideChapterNav ||\n story.startSlide !== DEFAULT_STORY_MAP.startSlide ||\n story.endSlide !== DEFAULT_STORY_MAP.endSlide\n );\n}\n\nconst STORY_ALIGNMENTS = new Set<StoryChapterAlignment>([\"left\", \"center\", \"right\", \"full\"]);\n\nconst STORY_ANIMATIONS = new Set<StoryChapterAnimation>([\"flyTo\", \"easeTo\", \"jumpTo\"]);\n\nconst STORY_INSET_POSITIONS = new Set<StoryInsetPosition>([\n \"top-left\",\n \"top-right\",\n \"bottom-left\",\n \"bottom-right\",\n]);\n\nconst STORY_SLIDE_MODES = new Set<StorySlideMode>([\"none\", \"blank\", \"black\", \"global\", \"adjacent\"]);\n\nfunction normalizeStoryChapter(chapter: unknown): StoryChapter | null {\n if (!chapter || typeof chapter !== \"object\") return null;\n\n const candidate = chapter as Partial<StoryChapter>;\n const id = normalizeString(candidate.id);\n if (!id) return null;\n\n const location = candidate.location;\n const center = location?.center;\n if (\n !Array.isArray(center) ||\n center.length !== 2 ||\n !center.every((value) => Number.isFinite(value))\n ) {\n return null;\n }\n\n return {\n id,\n title: normalizeString(candidate.title),\n description: normalizeString(candidate.description),\n image: normalizeString(candidate.image) || undefined,\n alignment: STORY_ALIGNMENTS.has(candidate.alignment as StoryChapterAlignment)\n ? (candidate.alignment as StoryChapterAlignment)\n : \"left\",\n hidden: normalizeBoolean(candidate.hidden, false),\n location: {\n // Clamp to valid lng/lat so a hand-edited file can't make flyTo throw.\n center: [\n clampCoordinate(Number(center[0]), -180, 180),\n clampCoordinate(Number(center[1]), -90, 90),\n ],\n // Clamp to MapLibre's valid ranges so a stored value matches the camera\n // that actually lands (bearing wraps to 0-360).\n zoom: clamp(normalizeNumber(location?.zoom, 2), 0, 24),\n pitch: clamp(normalizeNumber(location?.pitch, 0), 0, 85),\n bearing: ((normalizeNumber(location?.bearing, 0) % 360) + 360) % 360,\n },\n mapAnimation: STORY_ANIMATIONS.has(candidate.mapAnimation as StoryChapterAnimation)\n ? (candidate.mapAnimation as StoryChapterAnimation)\n : \"flyTo\",\n rotateAnimation: normalizeBoolean(candidate.rotateAnimation, false),\n // Leave old files unchanged; absent/invalid timings use the playback default.\n ...(typeof candidate.durationSeconds === \"number\" && Number.isFinite(candidate.durationSeconds)\n ? { durationSeconds: normalizeStoryChapterDurationSeconds(candidate.durationSeconds) }\n : {}),\n onChapterEnter: normalizeOpacityChanges(candidate.onChapterEnter),\n onChapterExit: normalizeOpacityChanges(candidate.onChapterExit),\n };\n}\n\nfunction normalizeOpacityChanges(value: unknown): StoryLayerOpacityChange[] {\n if (!Array.isArray(value)) return [];\n return value\n .map((entry): StoryLayerOpacityChange | null => {\n if (!entry || typeof entry !== \"object\") return null;\n const candidate = entry as Partial<StoryLayerOpacityChange>;\n const layerId = normalizeString(candidate.layerId);\n if (!layerId) return null;\n const id = normalizeString(candidate.id);\n return {\n ...(id ? { id } : {}),\n layerId,\n opacity: clamp(normalizeNumber(candidate.opacity, 1), 0, 1),\n ...(Number.isFinite(candidate.duration)\n ? { duration: Math.max(0, Number(candidate.duration)) }\n : {}),\n };\n })\n .filter((entry): entry is StoryLayerOpacityChange => Boolean(entry));\n}\n\nfunction normalizeString(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\n/**\n * Coerce an untrusted (possibly hand-edited) `models` array into valid\n * {@link ProcessingModel} records. Drops models and steps without a usable id or\n * tool id, de-duplicates models by id, and keeps step `parameters` as a plain\n * object (the runner validates parameter values per tool at run time). Returns\n * `null` when there is nothing worth persisting, so a model-less project stays\n * free of the key.\n *\n * @param value Raw `models` value from the project JSON.\n * @returns Normalized models, or `null` when none survive.\n */\nexport function normalizeModels(value: unknown): ProcessingModel[] | null {\n if (!Array.isArray(value)) return null;\n const models: ProcessingModel[] = [];\n const seen = new Set<string>();\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue;\n const candidate = entry as Partial<ProcessingModel>;\n const id = normalizeString(candidate.id).trim();\n if (!id || seen.has(id)) continue;\n const steps: ProcessingModelStep[] = [];\n const rawSteps = Array.isArray(candidate.steps) ? candidate.steps : [];\n const seenStepIds = new Set<string>();\n for (const rawStep of rawSteps) {\n if (!rawStep || typeof rawStep !== \"object\") continue;\n const step = rawStep as Partial<ProcessingModelStep>;\n const stepId = normalizeString(step.id).trim();\n const toolId = normalizeString(step.toolId).trim();\n if (!stepId || !toolId || seenStepIds.has(stepId)) continue;\n seenStepIds.add(stepId);\n const inputParam = normalizeString(step.inputParam).trim();\n steps.push({\n id: stepId,\n toolId,\n parameters:\n step.parameters && typeof step.parameters === \"object\"\n ? (step.parameters as Record<string, unknown>)\n : {},\n ...(inputParam ? { inputParam } : {}),\n });\n }\n seen.add(id);\n const graph = normalizeModelGraph((candidate as { graph?: unknown }).graph);\n models.push({\n id,\n name: normalizeString(candidate.name),\n steps,\n ...(graph ? { graph } : {}),\n });\n }\n return models.length > 0 ? models : null;\n}\n\nconst MODEL_NODE_KINDS = new Set<ModelGraphNodeKind>([\n \"input\",\n \"tool\",\n \"output\",\n \"iterate-features\",\n \"iterate-rasters\",\n \"collect-results\",\n \"conditional-switch\",\n \"conditional-merge\",\n]);\nconst MODEL_TOOL_PROVIDERS = new Set<ModelToolProvider>([\"vector\", \"whitebox\"]);\n\n/** Deepest loop-body nesting a loaded model keeps; anything deeper is dropped. */\nconst MAX_MODEL_BODY_DEPTH = 16;\n\n/** Coerce an untrusted number to a finite canvas coordinate. */\nfunction normalizeCoordinate(value: unknown): number {\n return typeof value === \"number\" && Number.isFinite(value) ? value : 0;\n}\n\n/**\n * Coerce an untrusted `graph` value into a {@link ProcessingModelGraph}. Drops\n * nodes without a usable id or an unknown kind, de-duplicates node ids, and\n * drops edges that do not connect two surviving nodes or that name an empty\n * port. Self-edges are dropped too, since a node cannot feed itself.\n *\n * Structural validity beyond this (cycles, type mismatches, missing required\n * inputs) is the runner's job — those depend on the tool registries, which the\n * project layer deliberately does not import.\n *\n * @param value Raw `graph` value from the project JSON.\n * @param depth How many loop bodies deep this graph sits (internal).\n * @returns The normalized graph, or `null` when it has no usable nodes.\n */\nexport function normalizeModelGraph(value: unknown, depth = 0): ProcessingModelGraph | null {\n if (!value || typeof value !== \"object\") return null;\n const raw = value as Partial<ProcessingModelGraph>;\n const nodes: ModelGraphNode[] = [];\n const nodeIds = new Set<string>();\n for (const entry of Array.isArray(raw.nodes) ? raw.nodes : []) {\n if (!entry || typeof entry !== \"object\") continue;\n const node = entry as Partial<ModelGraphNode>;\n const nodeId = normalizeString(node.id).trim();\n const kind = node.kind as ModelGraphNodeKind;\n if (!nodeId || nodeIds.has(nodeId) || !MODEL_NODE_KINDS.has(kind)) continue;\n nodeIds.add(nodeId);\n const layerId = normalizeString(node.layerId).trim();\n const toolId = normalizeString(node.toolId).trim();\n const name = normalizeString(node.name).trim();\n const provider = node.provider as ModelToolProvider;\n // Loop bodies nest; cap the depth so a crafted project cannot overflow the\n // stack here or in runModelGraph, which recurses the same way.\n const body =\n node.body && depth < MAX_MODEL_BODY_DEPTH ? normalizeModelGraph(node.body, depth + 1) : null;\n nodes.push({\n id: nodeId,\n kind,\n x: normalizeCoordinate(node.x),\n y: normalizeCoordinate(node.y),\n ...(layerId ? { layerId } : {}),\n ...(toolId ? { toolId } : {}),\n ...(MODEL_TOOL_PROVIDERS.has(provider) ? { provider } : {}),\n ...(node.parameters && typeof node.parameters === \"object\" && !Array.isArray(node.parameters)\n ? { parameters: node.parameters as Record<string, unknown> }\n : {}),\n ...(name ? { name } : {}),\n ...(body ? { body } : {}),\n });\n }\n if (nodes.length === 0) return null;\n\n const edges: ModelGraphEdge[] = [];\n const edgeIds = new Set<string>();\n for (const entry of Array.isArray(raw.edges) ? raw.edges : []) {\n if (!entry || typeof entry !== \"object\") continue;\n const edge = entry as Partial<ModelGraphEdge>;\n const edgeId = normalizeString(edge.id).trim();\n const from = normalizeString(edge.from).trim();\n const to = normalizeString(edge.to).trim();\n const fromPort = normalizeString(edge.fromPort).trim();\n const toPort = normalizeString(edge.toPort).trim();\n if (!edgeId || edgeIds.has(edgeId)) continue;\n if (!nodeIds.has(from) || !nodeIds.has(to) || from === to) continue;\n if (!fromPort || !toPort) continue;\n edgeIds.add(edgeId);\n edges.push({ id: edgeId, from, fromPort, to, toPort });\n }\n return { nodes, edges };\n}\n\nconst PROCESSING_RUN_KINDS = new Set<ProcessingRunKind>([\n \"vector\",\n \"statistics\",\n \"network\",\n \"whitebox\",\n \"raster\",\n \"conversion\",\n \"algorithm\",\n]);\n\n/**\n * Old H3 vector-tool IDs from projects saved before the DGGS rename.\n * Mapped onto current tool ids during project load.\n */\nconst LEGACY_H3_PROCESSING_TOOL_IDS: Readonly<Record<string, string>> = {\n \"h3-grid\": \"dggs-grid\",\n \"h3-bin-points\": \"dggs-bin\",\n};\n\n/**\n * Coerce an untrusted (possibly hand-edited) `processingHistory` array into\n * valid {@link ProcessingRun} records. Drops entries without a usable id, tool\n * id, or known kind, de-duplicates by id, keeps `parameters` as a plain object,\n * and caps the list at {@link MAX_PROCESSING_HISTORY} (keeping the newest,\n * i.e. last, entries). Returns `null` when nothing survives, so a history-less\n * project stays free of the key.\n *\n * @param value Raw `processingHistory` value from the project JSON.\n * @returns Normalized runs, or `null` when none survive.\n */\n/**\n * Accept the Object-Based Analysis workbench blob (`obia`) only as a plain,\n * JSON-serializable object carrying a numeric `version`. Its fields are owned\n * and validated by the desktop app, which treats anything it does not\n * recognise as absent; core only guarantees the shape survives a round trip.\n *\n * @param value Raw `obia` value from the project JSON.\n * @returns A detached copy, or `null` when absent or malformed.\n */\nexport function normalizeObiaWorkbench(value: unknown): Record<string, unknown> | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return null;\n if (typeof (value as { version?: unknown }).version !== \"number\") return null;\n try {\n const copy: unknown = JSON.parse(JSON.stringify(value));\n return copy && typeof copy === \"object\" ? (copy as Record<string, unknown>) : null;\n } catch {\n return null;\n }\n}\n\nexport function normalizeProcessingHistory(value: unknown): ProcessingRun[] | null {\n if (!Array.isArray(value)) return null;\n // Bound the work for a crafted or corrupted file (shared/collaboration\n // projects reach this path too): only the newest entries can survive the\n // cap anyway, so ignore all but a generous tail up front.\n const source =\n value.length > MAX_PROCESSING_HISTORY * 10 ? value.slice(-MAX_PROCESSING_HISTORY * 10) : value;\n const runs: ProcessingRun[] = [];\n const seen = new Set<string>();\n for (const entry of source) {\n if (!entry || typeof entry !== \"object\") continue;\n const candidate = entry as Partial<ProcessingRun>;\n const id = normalizeString(candidate.id).trim();\n let toolId = normalizeString(candidate.toolId).trim();\n const kind = candidate.kind;\n if (!id || !toolId || seen.has(id)) continue;\n if (!kind || !PROCESSING_RUN_KINDS.has(kind)) continue;\n seen.add(id);\n const inputLayerNames =\n candidate.inputLayerNames && typeof candidate.inputLayerNames === \"object\"\n ? Object.fromEntries(\n Object.entries(candidate.inputLayerNames).filter(\n ([, name]) => typeof name === \"string\",\n ),\n )\n : undefined;\n const outputLayerNames = Array.isArray(candidate.outputLayerNames)\n ? candidate.outputLayerNames.filter((name): name is string => typeof name === \"string\")\n : undefined;\n let parameters: Record<string, unknown> =\n candidate.parameters && typeof candidate.parameters === \"object\"\n ? { ...(candidate.parameters as Record<string, unknown>) }\n : {};\n const migrated = LEGACY_H3_PROCESSING_TOOL_IDS[toolId];\n if (migrated) {\n toolId = migrated;\n if (parameters.dggsType == null) parameters = { ...parameters, dggsType: \"h3\" };\n }\n runs.push({\n id,\n kind,\n toolId,\n toolName: normalizeString(candidate.toolName) || toolId,\n engine: normalizeString(candidate.engine),\n parameters,\n ...(inputLayerNames && Object.keys(inputLayerNames).length > 0 ? { inputLayerNames } : {}),\n ...(outputLayerNames?.length ? { outputLayerNames } : {}),\n ...(normalizeString(candidate.inputPath)\n ? { inputPath: normalizeString(candidate.inputPath) }\n : {}),\n ...(normalizeString(candidate.outputPath)\n ? { outputPath: normalizeString(candidate.outputPath) }\n : {}),\n startedAt: normalizeString(candidate.startedAt),\n ...(Number.isFinite(candidate.durationMs)\n ? { durationMs: Math.max(0, Number(candidate.durationMs)) }\n : {}),\n // Only an explicit \"success\" earns the green checkmark; a missing or\n // corrupted status from hand-edited JSON degrades to \"error\" rather than\n // presenting an indeterminate run as having succeeded.\n status: candidate.status === \"success\" ? \"success\" : \"error\",\n ...(normalizeString(candidate.error) ? { error: normalizeString(candidate.error) } : {}),\n });\n }\n if (runs.length === 0) return null;\n return runs.slice(-MAX_PROCESSING_HISTORY);\n}\n\n/**\n * Coerce an untrusted (possibly hand-edited) camera object into a valid\n * {@link MapViewState}, falling back to the default view for missing parts.\n */\nexport function normalizeMapViewState(value: unknown): MapViewState {\n const fallback = createDefaultMapView();\n if (!value || typeof value !== \"object\") return fallback;\n const candidate = value as Partial<MapViewState>;\n const center = Array.isArray(candidate.center) ? candidate.center : fallback.center;\n // Clamp to MapLibre's valid ranges (matching normalizeStoryChapter) so a\n // hand-edited project file can't store an out-of-range camera that jumpTo\n // would silently clamp or reject, leaving the saved state inconsistent with\n // what lands on screen. Bearing wraps into [0, 360).\n const view: MapViewState = {\n center: [\n clampCoordinate(normalizeNumber(center[0], fallback.center[0]), -180, 180),\n clampCoordinate(normalizeNumber(center[1], fallback.center[1]), -90, 90),\n ],\n zoom: clamp(normalizeNumber(candidate.zoom, fallback.zoom), 0, 24),\n bearing: ((normalizeNumber(candidate.bearing, fallback.bearing) % 360) + 360) % 360,\n pitch: clamp(normalizeNumber(candidate.pitch, fallback.pitch), 0, 85),\n };\n if (\n Array.isArray(candidate.bbox) &&\n candidate.bbox.length === 4 &&\n candidate.bbox.every((n) => Number.isFinite(n))\n ) {\n view.bbox = [\n Number(candidate.bbox[0]),\n Number(candidate.bbox[1]),\n Number(candidate.bbox[2]),\n Number(candidate.bbox[3]),\n ];\n }\n return view;\n}\n\n/**\n * Coerce an untrusted `mapLayout` into a valid {@link MapGridLayout}. Returns\n * null when absent or effectively single-pane so default projects stay\n * byte-identical (the field is only written when the grid is larger than 1x1).\n */\nexport function normalizeMapLayout(value: unknown): MapGridLayout | null {\n if (!value || typeof value !== \"object\") return null;\n const candidate = value as Partial<MapGridLayout>;\n const rows = clamp(Math.floor(normalizeNumber(candidate.rows, 1)), 1, MAX_MAP_GRID_DIM);\n const cols = clamp(Math.floor(normalizeNumber(candidate.cols, 1)), 1, MAX_MAP_GRID_DIM);\n if (rows * cols <= 1) return null;\n return {\n rows,\n cols,\n syncView: normalizeBoolean(candidate.syncView, DEFAULT_MAP_GRID_LAYOUT.syncView),\n };\n}\n\n/**\n * Coerce an untrusted `primaryRenderer` into a known engine id (issue #2217).\n *\n * Returns null for the default 2D map — absent, unknown, or an explicit\n * `\"maplibre\"` — because the field is only written when it is not the default,\n * so a MapLibre project serializes byte-identically to before this existed.\n * The return type is narrowed to `\"cesium\" | \"mapbox\" | \"arcgis\" | null` rather\n * than the full {@link MapRendererKind} for that reason: `\"maplibre\"` is never a\n * result.\n */\nexport function normalizePrimaryRenderer(value: unknown): \"cesium\" | \"mapbox\" | \"arcgis\" | null {\n return value === \"cesium\" || value === \"mapbox\" || value === \"arcgis\" ? value : null;\n}\n\n/**\n * Coerce an untrusted `secondaryMapViews` array into valid\n * {@link SecondaryMapView} records, dropping entries without a usable id and\n * de-duplicating by id. Returns null when none are valid.\n */\nexport function normalizeSecondaryMapViews(value: unknown): SecondaryMapView[] | null {\n if (!Array.isArray(value)) return null;\n const views: SecondaryMapView[] = [];\n const seen = new Set<string>();\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue;\n const candidate = entry as Partial<SecondaryMapView>;\n const id = normalizeString(candidate.id).trim();\n if (!id || seen.has(id)) continue;\n seen.add(id);\n const label = normalizeString(candidate.label);\n // Only the known engine ids survive; an absent/unknown value is omitted so\n // the pane defaults to the 2D map (back-compat with pre-globe projects).\n /* eslint-disable local/no-renderer-kind-checks -- validates a renderer name */\n const viewKind =\n candidate.viewKind === \"cesium\" ||\n candidate.viewKind === \"maplibre\" ||\n candidate.viewKind === \"mapbox\" ||\n candidate.viewKind === \"arcgis\"\n ? candidate.viewKind\n : undefined;\n /* eslint-enable local/no-renderer-kind-checks */\n views.push({\n id,\n view: normalizeMapViewState(candidate.view),\n ...(label ? { label } : {}),\n ...(viewKind ? { viewKind } : {}),\n layerVisibility: normalizeLayerVisibility(candidate.layerVisibility),\n });\n }\n return views.length > 0 ? views : null;\n}\n\n/** Coerce an untrusted per-layer visibility map into `Record<string, boolean>`. */\nfunction normalizeLayerVisibility(value: unknown): Record<string, boolean> {\n if (!value || typeof value !== \"object\") return {};\n const result: Record<string, boolean> = {};\n for (const [key, raw] of Object.entries(value as Record<string, unknown>)) {\n if (typeof raw === \"boolean\") result[key] = raw;\n }\n return result;\n}\n\n/**\n * Reconcile a parsed grid layout with its secondary panes so the store invariant\n * holds: `secondaryMapViews.length === rows * cols - 1`. Surplus panes are\n * dropped; missing panes are filled by cloning the primary map. A null/absent\n * layout (or a 1x1 grid) collapses to the single-map default.\n */\nexport function resolveMapGrid(\n layout: MapGridLayout | null,\n secondaryViews: SecondaryMapView[] | null,\n primary: { mapView: MapViewState },\n): { mapLayout: MapGridLayout; secondaryMapViews: SecondaryMapView[] } {\n if (!layout) {\n return { mapLayout: { ...DEFAULT_MAP_GRID_LAYOUT }, secondaryMapViews: [] };\n }\n const desired = layout.rows * layout.cols - 1;\n let views = secondaryViews ?? [];\n if (views.length > desired) {\n views = views.slice(0, desired);\n } else if (views.length < desired) {\n const seen = new Set(views.map((v) => v.id));\n const additions: SecondaryMapView[] = [];\n for (let i = views.length; i < desired; i++) {\n let id = `secondary-${i}`;\n // Append a counter (rather than growing the string) so a crafted file\n // with colliding ids resolves in O(1) per attempt instead of O(n).\n let suffix = 0;\n while (seen.has(id)) id = `secondary-${i}-${++suffix}`;\n seen.add(id);\n additions.push({\n id,\n view: { ...primary.mapView },\n layerVisibility: {},\n });\n }\n views = [...views, ...additions];\n }\n return { mapLayout: layout, secondaryMapViews: views };\n}\n\n/** A 3- or 6-digit hex color, the only widget color format we persist. */\nconst HEX_COLOR = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n\n/** Upper bound for a persisted histogram bin count, mirroring the chart\n * renderer's clamp (`MAX_HISTOGRAM_BINS` in the desktop app's chart helpers). */\nconst MAX_PERSISTED_BINS = 50;\n\n/** Upper bound for a persisted list-widget row limit, mirroring the widget\n * editor's row-count input (`max={500}` in `WidgetEditorDialog`). */\nconst MAX_PERSISTED_LIST_ROWS = 500;\n\n// Spelled as a Record so adding a member to DashboardWidgetType fails to\n// compile until it is listed here. A plain array accepted a short list\n// silently, and a type missing from it makes normalizeWidgets drop every widget\n// of that type — which is how selector widgets vanished on save and reload.\nconst DASHBOARD_WIDGET_TYPES = Object.keys({\n histogram: true,\n scatter: true,\n bar: true,\n line: true,\n box: true,\n pie: true,\n indicator: true,\n selector: true,\n list: true,\n} satisfies Record<DashboardWidgetType, true>) as readonly DashboardWidgetType[];\nconst DASHBOARD_WIDGET_AGGREGATIONS: readonly DashboardWidgetAggregation[] = [\n \"count\",\n \"sum\",\n \"mean\",\n];\nconst INDICATOR_AGGREGATIONS: readonly IndicatorAggregation[] = [\n \"count\",\n \"sum\",\n \"mean\",\n \"min\",\n \"max\",\n \"median\",\n];\n\n/** Bookmarks kept per project, as the panel's own default limit allows. */\nexport const MAX_PROJECT_BOOKMARKS = 500;\n\n/**\n * Coerce an untrusted `bookmarkGroups` array into valid folders: drops entries\n * without a string id and de-duplicates by id. A missing name becomes\n * \"Folder\"; `collapsed` defaults to false.\n *\n * @param value Raw `bookmarkGroups` value from the project JSON.\n * @returns The folders (possibly empty).\n */\nexport function normalizeBookmarkGroups(value: unknown): ProjectBookmarkGroup[] {\n if (!Array.isArray(value)) return [];\n const groups: ProjectBookmarkGroup[] = [];\n const seen = new Set<string>();\n for (const raw of value) {\n if (!raw || typeof raw !== \"object\") continue;\n const entry = raw as Record<string, unknown>;\n const id = typeof entry.id === \"string\" ? entry.id.trim() : \"\";\n if (!id || seen.has(id)) continue;\n seen.add(id);\n groups.push({\n id,\n name: typeof entry.name === \"string\" && entry.name.trim() ? entry.name : \"Folder\",\n collapsed: entry.collapsed === true,\n });\n }\n return groups;\n}\n\n/**\n * Coerce an untrusted `bookmarks` array into valid saved views: drops entries\n * without a string id or a finite, in-range camera, de-duplicates by id, wraps\n * the bearing into 0-360, and drops a `groupId` that names no folder in\n * `groups`. Keeps at most {@link MAX_PROJECT_BOOKMARKS}.\n *\n * @param value Raw `bookmarks` value from the project JSON.\n * @param groups The project's (normalized) folders.\n * @returns The bookmarks (possibly empty).\n */\nexport function normalizeBookmarks(\n value: unknown,\n groups: readonly ProjectBookmarkGroup[] = [],\n): ProjectBookmark[] {\n if (!Array.isArray(value)) return [];\n const groupIds = new Set(groups.map((group) => group.id));\n const bookmarks: ProjectBookmark[] = [];\n const seen = new Set<string>();\n const finite = (input: unknown): number | null =>\n typeof input === \"number\" && Number.isFinite(input) ? input : null;\n for (const raw of value) {\n if (bookmarks.length >= MAX_PROJECT_BOOKMARKS) break;\n if (!raw || typeof raw !== \"object\") continue;\n const entry = raw as Record<string, unknown>;\n const id = typeof entry.id === \"string\" ? entry.id.trim() : \"\";\n const lng = finite(entry.lng);\n const lat = finite(entry.lat);\n const zoom = finite(entry.zoom);\n if (!id || seen.has(id) || lng === null || lat === null || zoom === null) continue;\n if (lng < -180 || lng > 180 || lat < -90 || lat > 90 || zoom < 0 || zoom > 24) continue;\n seen.add(id);\n const pitch = Math.min(85, Math.max(0, finite(entry.pitch) ?? 0));\n const bearing = (((finite(entry.bearing) ?? 0) % 360) + 360) % 360;\n const bookmark: ProjectBookmark = {\n id,\n name: typeof entry.name === \"string\" ? entry.name : \"\",\n lng,\n lat,\n zoom,\n pitch,\n bearing,\n createdAt: finite(entry.createdAt) ?? 0,\n };\n // Folder ids are trimmed, so compare a trimmed groupId with them.\n const groupId = typeof entry.groupId === \"string\" ? entry.groupId.trim() : \"\";\n if (groupId && groupIds.has(groupId)) bookmark.groupId = groupId;\n if (entry.extra && typeof entry.extra === \"object\" && !Array.isArray(entry.extra)) {\n bookmark.extra = entry.extra as Record<string, unknown>;\n }\n bookmarks.push(bookmark);\n }\n return bookmarks;\n}\n\n/**\n * The `bookmarks`/`bookmarkGroups` project fields for normalized input, each\n * omitted when empty so a bookmark-less project stays free of the keys.\n */\nfunction bookmarkFields(\n rawBookmarks: unknown,\n rawGroups: unknown,\n): { bookmarks?: ProjectBookmark[]; bookmarkGroups?: ProjectBookmarkGroup[] } {\n const bookmarkGroups = normalizeBookmarkGroups(rawGroups);\n const bookmarks = normalizeBookmarks(rawBookmarks, bookmarkGroups);\n return {\n ...(bookmarks.length > 0 ? { bookmarks } : {}),\n ...(bookmarkGroups.length > 0 ? { bookmarkGroups } : {}),\n };\n}\n\n/**\n * Coerce an untrusted (possibly hand-edited) `widgets` array into valid\n * {@link DashboardWidget} records. Drops widgets without a usable id, layer id,\n * or recognized chart type, de-duplicates by id, and keeps only the optional\n * keys that are present and well-typed (the Dashboard panel falls back to\n * sensible defaults for anything missing). Returns `null` when there is nothing\n * worth persisting, so a widget-less project stays free of the key.\n *\n * @param value Raw `widgets` value from the project JSON.\n * @returns Normalized widgets, or `null` when none survive.\n */\nexport function normalizeWidgets(value: unknown): DashboardWidget[] | null {\n if (!Array.isArray(value)) return null;\n const widgets: DashboardWidget[] = [];\n const seen = new Set<string>();\n for (const entry of value) {\n if (!entry || typeof entry !== \"object\") continue;\n const candidate = entry as Partial<DashboardWidget>;\n const id = normalizeString(candidate.id).trim();\n const layerId = normalizeString(candidate.layerId).trim();\n if (!id || !layerId || seen.has(id)) continue;\n const type = candidate.type;\n if (!type || !DASHBOARD_WIDGET_TYPES.includes(type)) continue;\n seen.add(id);\n const widget: DashboardWidget = { id, layerId, type };\n const title = normalizeString(candidate.title).trim();\n if (title) widget.title = title;\n const color = normalizeString(candidate.color).trim();\n if (HEX_COLOR.test(color)) widget.color = color;\n const field = normalizeString(candidate.field).trim();\n if (field) widget.field = field;\n const xField = normalizeString(candidate.xField).trim();\n if (xField) widget.xField = xField;\n const yField = normalizeString(candidate.yField).trim();\n if (yField) widget.yField = yField;\n if (typeof candidate.bins === \"number\" && Number.isFinite(candidate.bins)) {\n // Persist only a sane positive bin count; the histogram renderer clamps to\n // [1, 50], so mirror that here rather than round-tripping 0 or huge values.\n const bins = Math.trunc(candidate.bins);\n if (bins >= 1) widget.bins = Math.min(MAX_PERSISTED_BINS, bins);\n }\n const category = normalizeString(candidate.category).trim();\n if (category) widget.category = category;\n if (\n candidate.aggregation &&\n DASHBOARD_WIDGET_AGGREGATIONS.includes(candidate.aggregation) &&\n // A pie has no \"average\"; the renderer would silently treat mean as sum,\n // so drop it here and let the default (count) stand for hand-edited files.\n !(type === \"pie\" && candidate.aggregation === \"mean\")\n ) {\n widget.aggregation = candidate.aggregation;\n }\n const valueField = normalizeString(candidate.valueField).trim();\n if (valueField) widget.valueField = valueField;\n // Indicator widget fields (issue #1381). Only an indicator reads them, so\n // drop them elsewhere rather than round-tripping dead configuration.\n if (type === \"indicator\") {\n if (\n candidate.indicatorAggregation &&\n INDICATOR_AGGREGATIONS.includes(candidate.indicatorAggregation)\n ) {\n widget.indicatorAggregation = candidate.indicatorAggregation;\n }\n // Prefix/suffix are not trimmed: a leading/trailing space is intentional\n // (e.g. \" ha\" or \"$ \").\n const prefix = normalizeString(candidate.prefix);\n if (prefix) widget.prefix = prefix;\n const suffix = normalizeString(candidate.suffix);\n if (suffix) widget.suffix = suffix;\n }\n // Selector widget fields (issue #1381). Only a selector reads the flag, and\n // false is the default, so persist it only when it is on.\n if (type === \"selector\" && candidate.multiple === true) {\n widget.multiple = true;\n }\n // List widget fields (issue #1381). normalizeWidgets also runs on the save\n // path (projectFromStore), so dropping these would blank a list widget the\n // moment its project is saved — the renderer falls back to \"no data\"\n // without listFields.\n if (type === \"list\") {\n if (Array.isArray(candidate.listFields)) {\n const listFields = candidate.listFields\n .map((entry) => normalizeString(entry).trim())\n .filter((entry) => entry !== \"\");\n if (listFields.length > 0) widget.listFields = listFields;\n }\n const sortBy = normalizeString(candidate.sortBy).trim();\n if (sortBy) widget.sortBy = sortBy;\n if (candidate.sortDir === \"asc\" || candidate.sortDir === \"desc\") {\n widget.sortDir = candidate.sortDir;\n }\n if (typeof candidate.limit === \"number\" && Number.isFinite(candidate.limit)) {\n // Clamp to the editor's range so a hand-edited 0 or 10_000 cannot reach\n // the renderer.\n const limit = Math.trunc(candidate.limit);\n if (limit >= 1) widget.limit = Math.min(MAX_PERSISTED_LIST_ROWS, limit);\n }\n }\n widgets.push(widget);\n }\n return widgets.length > 0 ? widgets : null;\n}\n\n/**\n * Clamp an untrusted dashboard column count into the supported range, falling\n * back to the default for a missing or non-finite value.\n *\n * @param value Raw `dashboardColumns` value from the project JSON.\n * @returns An integer column count within [MIN, MAX].\n */\nexport function normalizeDashboardColumns(value: unknown): number {\n if (typeof value !== \"number\" || !Number.isFinite(value)) {\n return DEFAULT_DASHBOARD_COLUMNS;\n }\n return Math.max(MIN_DASHBOARD_COLUMNS, Math.min(MAX_DASHBOARD_COLUMNS, Math.trunc(value)));\n}\n\n/**\n * Coerce an untrusted `interaction` block into a {@link ProjectInteraction}.\n *\n * Layer ids are kept even when no such layer exists: the block is replayed\n * against whatever layers the project has when it opens, and the loader drops\n * the ones it cannot find. Control names are kept as written for the same\n * reason; the app ignores names it does not know.\n *\n * @param value - Raw `interaction` value from the project JSON or the store.\n * @returns The normalized block, or null when it carries nothing to apply.\n */\nexport function normalizeProjectInteraction(value: unknown): ProjectInteraction | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return null;\n const raw = value as { identify?: unknown; controls?: unknown };\n const result: ProjectInteraction = {};\n if (raw.identify === null) {\n result.identify = null;\n } else if (typeof raw.identify === \"string\" && raw.identify) {\n result.identify = raw.identify;\n } else if (Array.isArray(raw.identify)) {\n const ids = [\n ...new Set(raw.identify.filter((id): id is string => typeof id === \"string\" && !!id)),\n ];\n if (ids.length > 0) result.identify = ids;\n }\n if (raw.controls && typeof raw.controls === \"object\" && !Array.isArray(raw.controls)) {\n const controls: Record<string, boolean | ProjectControlSetting> = {};\n for (const [name, shown] of Object.entries(raw.controls)) {\n if (!name) continue;\n if (typeof shown === \"boolean\") {\n controls[name] = shown;\n continue;\n }\n // `{ visible, ...options }` (issue #3241). Options are kept as written,\n // like the names: the app validates them when it applies them.\n if (!shown || typeof shown !== \"object\" || Array.isArray(shown)) continue;\n const setting = shown as Record<string, unknown>;\n if (typeof setting.visible !== \"boolean\") continue;\n const kept: ProjectControlSetting = { visible: setting.visible };\n for (const [option, value] of Object.entries(setting)) {\n if (\n typeof value === \"string\" ||\n typeof value === \"boolean\" ||\n (typeof value === \"number\" && Number.isFinite(value))\n ) {\n kept[option] = value;\n }\n }\n controls[name] = kept;\n }\n if (Object.keys(controls).length > 0) result.controls = controls;\n }\n return \"identify\" in result || result.controls ? result : null;\n}\n\nfunction normalizeProjectPreferences(preferences: unknown): ProjectPreferences {\n if (!preferences || typeof preferences !== \"object\") {\n return DEFAULT_PROJECT_PREFERENCES;\n }\n\n const candidate = preferences as Partial<ProjectPreferences>;\n const map = candidate.map ?? {};\n // Every MapPreferences field is normalized explicitly below, so the map\n // object is not spread in: that would forward unknown keys from a\n // hand-edited project file straight into app state.\n return {\n map: {\n ...DEFAULT_PROJECT_PREFERENCES.map,\n bounds: normalizeBounds((map as Partial<ProjectPreferences[\"map\"]>).bounds),\n minZoom: normalizeNumber(\n (map as Partial<ProjectPreferences[\"map\"]>).minZoom,\n DEFAULT_PROJECT_PREFERENCES.map.minZoom,\n ),\n maxZoom: normalizeNumber(\n (map as Partial<ProjectPreferences[\"map\"]>).maxZoom,\n DEFAULT_PROJECT_PREFERENCES.map.maxZoom,\n ),\n maxPitch: normalizeNumber(\n (map as Partial<ProjectPreferences[\"map\"]>).maxPitch,\n DEFAULT_PROJECT_PREFERENCES.map.maxPitch,\n ),\n restrictBounds: Boolean((map as Partial<ProjectPreferences[\"map\"]>).restrictBounds),\n renderWorldCopies: normalizeBoolean(\n (map as Partial<ProjectPreferences[\"map\"]>).renderWorldCopies,\n true,\n ),\n projection:\n (map as Partial<ProjectPreferences[\"map\"]>).projection === \"mercator\"\n ? \"mercator\"\n : \"globe\",\n // Coerce unknown/missing bodies to Earth so measurements never break.\n ellipsoidId: getEllipsoid((map as Partial<ProjectPreferences[\"map\"]>).ellipsoidId).id,\n scaleUnit: normalizeScaleUnit((map as Partial<ProjectPreferences[\"map\"]>).scaleUnit),\n // Absent in every project written before #1813, and the default is off,\n // so an older project opens without the elevation lookup enabled.\n showPointerElevation: normalizeBoolean(\n (map as Partial<ProjectPreferences[\"map\"]>).showPointerElevation,\n DEFAULT_PROJECT_PREFERENCES.map.showPointerElevation,\n ),\n // Missing means follow the saved project basemap. Do not reapply the\n // new-project Streets default after a user has selected a shared style.\n mapboxStyleUrl:\n normalizeString((map as Partial<ProjectPreferences[\"map\"]>).mapboxStyleUrl) || undefined,\n arcgisBasemap:\n normalizeString((map as Partial<ProjectPreferences[\"map\"]>).arcgisBasemap) || undefined,\n // Missing (every project before #2708) or Web Mercator is the default map.\n arcgisWkid: normalizeArcgisWkid((map as Partial<ProjectPreferences[\"map\"]>).arcgisWkid),\n // Missing (every project before #1128) or Web Mercator is the default map.\n mapCrs: normalizeMapCrs((map as Partial<ProjectPreferences[\"map\"]>).mapCrs),\n // Missing means follow the saved project basemap, as it does for\n // `mapboxStyleUrl` above: a project written before this field existed\n // chose nothing, and reapplying the new-project default would repaint\n // its globe with Ion imagery the next time it opened. New projects get\n // the default from `DEFAULT_PROJECT_PREFERENCES` and save it explicitly.\n cesiumBasemap: normalizeCesiumBasemap(\n (map as Partial<ProjectPreferences[\"map\"]>).cesiumBasemap,\n ),\n // Older projects omit this field and continue to open with terrain off.\n terrainEnabled: normalizeBoolean(\n (map as Partial<ProjectPreferences[\"map\"]>).terrainEnabled,\n DEFAULT_PROJECT_PREFERENCES.map.terrainEnabled,\n ),\n terrainIonAssetId:\n parseCesiumIonAssetId((map as Partial<ProjectPreferences[\"map\"]>).terrainIonAssetId) ??\n undefined,\n // Kept as a free string here; the app coerces an unknown notation to\n // decimal degrees when it renders, so a hand-edited project cannot break\n // the readout.\n coordinateFormat:\n typeof (map as Partial<ProjectPreferences[\"map\"]>).coordinateFormat === \"string\"\n ? ((map as Partial<ProjectPreferences[\"map\"]>).coordinateFormat as string)\n : DEFAULT_PROJECT_PREFERENCES.map.coordinateFormat,\n // Absent in projects written before the EPSG readout existed; only a\n // positive integer survives, so a hand-edited value cannot reach proj4.\n coordinateEpsgCode: normalizeEpsgCode(\n (map as Partial<ProjectPreferences[\"map\"]>).coordinateEpsgCode,\n ),\n // Older projects omit this field and keep fitting to newly added data.\n zoomToNewLayers: normalizeBoolean(\n (map as Partial<ProjectPreferences[\"map\"]>).zoomToNewLayers,\n DEFAULT_PROJECT_PREFERENCES.map.zoomToNewLayers,\n ),\n },\n environmentVariables: Array.isArray(candidate.environmentVariables)\n ? candidate.environmentVariables\n .map(normalizeEnvironmentVariable)\n .filter((variable): variable is RuntimeEnvironmentVariable => Boolean(variable))\n : [],\n geocoding: normalizeGeocodingPreferences(candidate.geocoding),\n };\n}\n\n/**\n * Keep a stored EPSG code only when it is a positive integer.\n *\n * Args:\n * value: The stored value, of unknown shape.\n *\n * Returns:\n * The code, or undefined for anything else.\n */\nfunction normalizeEpsgCode(value: unknown): number | undefined {\n return typeof value === \"number\" && Number.isInteger(value) && value > 0 ? value : undefined;\n}\n\nfunction normalizeGeocodingPreferences(geocoding: unknown): ProjectPreferences[\"geocoding\"] {\n if (!geocoding || typeof geocoding !== \"object\") {\n return { ...DEFAULT_PROJECT_PREFERENCES.geocoding, apiKeys: {} };\n }\n const candidate = geocoding as Partial<ProjectPreferences[\"geocoding\"]>;\n const apiKeys: Record<string, string> = {};\n if (candidate.apiKeys && typeof candidate.apiKeys === \"object\") {\n for (const [key, value] of Object.entries(candidate.apiKeys)) {\n const normalizedKey = key.trim();\n if (normalizedKey && typeof value === \"string\") {\n apiKeys[normalizedKey] = value;\n }\n }\n }\n return {\n providerId:\n typeof candidate.providerId === \"string\" && candidate.providerId.trim()\n ? candidate.providerId.trim()\n : DEFAULT_PROJECT_PREFERENCES.geocoding.providerId,\n apiKeys,\n forwardEndpoint:\n typeof candidate.forwardEndpoint === \"string\" && candidate.forwardEndpoint.trim()\n ? candidate.forwardEndpoint.trim()\n : undefined,\n reverseEndpoint:\n typeof candidate.reverseEndpoint === \"string\" && candidate.reverseEndpoint.trim()\n ? candidate.reverseEndpoint.trim()\n : undefined,\n email:\n typeof candidate.email === \"string\" && candidate.email.trim()\n ? candidate.email.trim()\n : undefined,\n ...normalizeGeocodingLocators(candidate.locators, candidate.locatorId),\n };\n}\n\n/**\n * The project's ArcGIS GeocodeServer locators, dropping malformed entries and\n * duplicate ids, plus the selected locator id when it still names one. Both are\n * omitted when there are none, so older projects round-trip unchanged.\n */\nfunction normalizeGeocodingLocators(\n locators: unknown,\n locatorId: unknown,\n): Pick<ProjectPreferences[\"geocoding\"], \"locators\" | \"locatorId\"> {\n if (!Array.isArray(locators)) return {};\n const seen = new Set<string>();\n const result = locators\n .map(normalizeGeocodingLocator)\n .filter((locator): locator is NonNullable<typeof locator> => {\n if (!locator || seen.has(locator.id)) return false;\n seen.add(locator.id);\n return true;\n });\n if (!result.length) return {};\n return typeof locatorId === \"string\" && seen.has(locatorId)\n ? { locators: result, locatorId }\n : { locators: result };\n}\n\n/** Coerce an unknown value to a supported scale unit, defaulting to metric. */\nfunction normalizeScaleUnit(value: unknown): MapScaleUnit {\n return value === \"imperial\" || value === \"nautical\" ? value : \"metric\";\n}\n\nfunction normalizeBounds(bounds: unknown): ProjectPreferences[\"map\"][\"bounds\"] {\n if (\n Array.isArray(bounds) &&\n bounds.length === 4 &&\n bounds.every((value) => Number.isFinite(value))\n ) {\n // Clamp to valid lng/lat ranges so the stored bounds match what the map\n // controller applies, then keep the ordering check so an empty or\n // inverted region falls back to the default instead of being persisted.\n const west = clampCoordinate(Number(bounds[0]), -180, 180);\n const south = clampCoordinate(Number(bounds[1]), -85, 85);\n const east = clampCoordinate(Number(bounds[2]), -180, 180);\n const north = clampCoordinate(Number(bounds[3]), -85, 85);\n if (west < east && south < north) {\n return [west, south, east, north];\n }\n }\n\n return DEFAULT_PROJECT_PREFERENCES.map.bounds;\n}\n\nfunction normalizeNumber(value: unknown, fallback: number): number {\n return Number.isFinite(value) ? Number(value) : fallback;\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n return Math.min(max, Math.max(min, value));\n}\n\nfunction clampCoordinate(value: number, min: number, max: number): number {\n return clamp(value, min, max);\n}\n\nfunction normalizeBoolean(value: unknown, fallback: boolean): boolean {\n return typeof value === \"boolean\" ? value : fallback;\n}\n\nconst ENVIRONMENT_VARIABLE_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\nfunction normalizeEnvironmentVariable(variable: unknown): RuntimeEnvironmentVariable | null {\n if (!variable || typeof variable !== \"object\") return null;\n const candidate = variable as Partial<RuntimeEnvironmentVariable>;\n const key = typeof candidate.key === \"string\" ? candidate.key.trim() : \"\";\n if (!key || !ENVIRONMENT_VARIABLE_NAME_PATTERN.test(key)) return null;\n\n return {\n key,\n value: typeof candidate.value === \"string\" ? candidate.value : \"\",\n enabled: normalizeBoolean(candidate.enabled, true),\n ...(candidate.secret === false ? { secret: false } : {}),\n };\n}\n\nconst PROJECT_PLUGIN_CONTROL_POSITIONS = new Set<ProjectPluginControlPosition>([\n \"top-left\",\n \"top-right\",\n \"bottom-left\",\n \"bottom-right\",\n]);\n\nfunction normalizeProjectPlugins(plugins: unknown): ProjectPluginState | null {\n if (!plugins || typeof plugins !== \"object\") return null;\n\n const candidate = plugins as Partial<ProjectPluginState>;\n const manifestUrls = Array.isArray(candidate.manifestUrls)\n ? uniqueStrings(candidate.manifestUrls).filter(isAllowedPluginManifestUrl)\n : [];\n const activePluginIds = Array.isArray(candidate.activePluginIds)\n ? uniqueStrings(candidate.activePluginIds)\n : [];\n const mapControlPositions: Record<string, ProjectPluginControlPosition> = {};\n const settings: Record<string, unknown> = {};\n\n if (candidate.mapControlPositions && typeof candidate.mapControlPositions === \"object\") {\n for (const [pluginId, position] of Object.entries(candidate.mapControlPositions)) {\n if (\n typeof pluginId === \"string\" &&\n pluginId.trim() &&\n PROJECT_PLUGIN_CONTROL_POSITIONS.has(position as ProjectPluginControlPosition)\n ) {\n mapControlPositions[pluginId.trim()] = position as ProjectPluginControlPosition;\n }\n }\n }\n\n if (candidate.settings && typeof candidate.settings === \"object\") {\n for (const [pluginId, value] of Object.entries(candidate.settings)) {\n if (typeof pluginId === \"string\" && pluginId.trim() && isJsonCompatible(value)) {\n settings[pluginId.trim()] = value;\n }\n }\n }\n\n return {\n manifestUrls,\n activePluginIds,\n mapControlPositions,\n settings,\n };\n}\n\n// Plugin manifest URLs lead to fetched and executed code, so both the\n// Settings dialog and project-file loading enforce the same scheme rule:\n// HTTPS, or HTTP on a loopback host for local development.\nexport function isAllowedPluginManifestUrl(url: string): boolean {\n try {\n const { protocol, hostname } = new URL(url);\n return (\n protocol === \"https:\" ||\n (protocol === \"http:\" && [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(hostname))\n );\n } catch {\n return false;\n }\n}\n\nfunction uniqueStrings(values: unknown[]): string[] {\n const seen = new Set<string>();\n const normalized: string[] = [];\n\n for (const value of values) {\n if (typeof value !== \"string\") continue;\n const id = value.trim();\n if (!id || seen.has(id)) continue;\n seen.add(id);\n normalized.push(id);\n }\n\n return normalized;\n}\n\nfunction isJsonCompatible(value: unknown): boolean {\n if (value === null) return true;\n\n switch (typeof value) {\n case \"string\":\n case \"boolean\":\n return true;\n case \"number\":\n return Number.isFinite(value);\n case \"object\":\n if (Array.isArray(value)) return value.every(isJsonCompatible);\n if (!isPlainObject(value)) return false;\n return Object.values(value).every(isJsonCompatible);\n default:\n return false;\n }\n}\n\nfunction isPlainObject(value: object): boolean {\n const prototype = Object.getPrototypeOf(value);\n return prototype === Object.prototype || prototype === null;\n}\n\n/** Browser byte URLs belong to the live session, never a saved project. */\nfunction withoutLocalRasterBytes(layer: GeoLibreLayer): GeoLibreLayer {\n if (layer.metadata?.localBytesUrl === undefined) return layer;\n const { localBytesUrl: _localBytesUrl, ...metadata } = layer.metadata;\n return { ...layer, metadata };\n}\n\n/**\n * Drop a Zarr layer's request headers. They authenticate the store (a bearer\n * token, an API key), so they are credentials: the Zarr adds keep them in a\n * session-only map the renderer reads (`registerZarrHeaders` in\n * @geolibre/map), but a project saved before that change carries them on\n * `source` (opengeos/GeoLibre#2643). Not applied on parse, so such a project\n * still authenticates for the session it is opened in.\n *\n * @param layer - Any layer.\n * @returns The layer without `source.headers` when it is a Zarr layer, else\n * the same object.\n */\nfunction withoutZarrHeaders(layer: GeoLibreLayer): GeoLibreLayer {\n if (layer.type !== \"zarr\" || layer.source?.headers === undefined) return layer;\n const { headers: _headers, ...source } = layer.source;\n return { ...layer, source };\n}\n\nfunction withoutRuntimeGeoArrow(layer: GeoLibreLayer): GeoLibreLayer {\n if (layer.geoarrow === undefined) return layer;\n const { geoarrow: _geoarrow, ...rest } = layer;\n return rest;\n}\n\n/**\n * Strip the session-only state every serializer must leave out, whether or not\n * the project came through `projectFromStore`.\n *\n * @param layer - Any layer.\n * @returns The layer as it may be written to a project file.\n */\nfunction portableLayer(layer: GeoLibreLayer): GeoLibreLayer {\n return withoutRuntimeGeoArrow(withoutZarrHeaders(withoutLocalRasterBytes(layer)));\n}\n\nfunction normalizeLayer(layer: GeoLibreLayer): GeoLibreLayer {\n layer = withRasterSourceDefaults(withoutLocalRasterBytes(layer));\n // `capabilities` is split off the spread rather than overwritten: a raw value\n // that normalizes to nothing (`{}`, an array, a string, an object with no\n // boolean flag) must not survive into the normalized layer and be written\n // back out on the next save.\n const {\n capabilities: rawCapabilities,\n filterExpression: rawFilterExpression,\n descriptiveMetadata: rawDescriptiveMetadata,\n geoarrow: _rawGeoarrow,\n ...rest\n } = layer;\n const capabilities = normalizeLayerCapabilities(rawCapabilities);\n // Same split for the user-authored catalog metadata: a block that cleans to\n // nothing (every field blank) is dropped rather than round-tripped.\n const descriptiveMetadata = normalizeLayerDescriptiveMetadata(rawDescriptiveMetadata);\n const filterExpression =\n Array.isArray(rawFilterExpression) &&\n rawFilterExpression.length > 0 &&\n validateMapExpression(JSON.stringify(rawFilterExpression), { expectedType: \"boolean\" }).ok\n ? rawFilterExpression\n : undefined;\n // Batches are runtime caches. A hydrated batch cannot be verified against\n // the saved GeoJSON, so always discard it to avoid rendering stale data.\n return {\n ...rest,\n style: { ...DEFAULT_LAYER_STYLE, ...layer.style },\n visible: layer.visible ?? true,\n opacity: layer.opacity ?? 1,\n metadata: layer.metadata ?? {},\n source: layer.source ?? {},\n ...(capabilities ? { capabilities } : {}),\n ...(filterExpression ? { filterExpression } : {}),\n ...(descriptiveMetadata ? { descriptiveMetadata } : {}),\n };\n}\n\nexport function normalizeProjectComments(rawComments: unknown): ProjectComment[] {\n if (!Array.isArray(rawComments)) return [];\n const result: ProjectComment[] = [];\n for (const item of rawComments) {\n if (!item || typeof item !== \"object\") continue;\n const c = item as Record<string, unknown>;\n if (typeof c.id !== \"string\" || !c.id) continue;\n\n if (!c.anchor || typeof c.anchor !== \"object\") continue;\n const anchorObj = c.anchor as Record<string, unknown>;\n let anchor: CommentAnchor;\n if (\n anchorObj.type === \"point\" &&\n Array.isArray(anchorObj.lngLat) &&\n anchorObj.lngLat.length === 2 &&\n typeof anchorObj.lngLat[0] === \"number\" &&\n typeof anchorObj.lngLat[1] === \"number\"\n ) {\n anchor = { type: \"point\", lngLat: [anchorObj.lngLat[0], anchorObj.lngLat[1]] };\n } else if (\n anchorObj.type === \"feature\" &&\n typeof anchorObj.layerId === \"string\" &&\n (typeof anchorObj.featureId === \"string\" || typeof anchorObj.featureId === \"number\")\n ) {\n const featLngLat =\n Array.isArray(anchorObj.lngLat) &&\n anchorObj.lngLat.length === 2 &&\n typeof anchorObj.lngLat[0] === \"number\" &&\n typeof anchorObj.lngLat[1] === \"number\"\n ? ([anchorObj.lngLat[0], anchorObj.lngLat[1]] as [number, number])\n : undefined;\n anchor = {\n type: \"feature\",\n layerId: anchorObj.layerId,\n featureId: anchorObj.featureId,\n ...(featLngLat ? { lngLat: featLngLat } : {}),\n };\n } else {\n continue;\n }\n\n const authorObj =\n c.author && typeof c.author === \"object\" ? (c.author as Record<string, unknown>) : {};\n const HEX = /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/;\n const author: CommentAuthor = {\n name:\n typeof authorObj.name === \"string\" && authorObj.name.trim()\n ? authorObj.name.trim()\n : \"Anonymous\",\n color:\n typeof authorObj.color === \"string\" && HEX.test(authorObj.color.trim())\n ? authorObj.color.trim()\n : \"#3b82f6\",\n };\n\n const replies: CommentReply[] = [];\n if (Array.isArray(c.replies)) {\n for (const rItem of c.replies) {\n if (!rItem || typeof rItem !== \"object\") continue;\n const r = rItem as Record<string, unknown>;\n if (typeof r.id !== \"string\" || !r.id) continue;\n const rAuthorObj =\n r.author && typeof r.author === \"object\" ? (r.author as Record<string, unknown>) : {};\n replies.push({\n id: r.id,\n author: {\n name:\n typeof rAuthorObj.name === \"string\" && rAuthorObj.name.trim()\n ? rAuthorObj.name.trim()\n : \"Anonymous\",\n color:\n typeof rAuthorObj.color === \"string\" && HEX.test(rAuthorObj.color.trim())\n ? rAuthorObj.color.trim()\n : \"#3b82f6\",\n },\n body: typeof r.body === \"string\" ? r.body : \"\",\n createdAt: typeof r.createdAt === \"string\" ? r.createdAt : new Date().toISOString(),\n });\n }\n }\n\n result.push({\n id: c.id,\n anchor,\n author,\n body: typeof c.body === \"string\" ? c.body : \"\",\n createdAt: typeof c.createdAt === \"string\" ? c.createdAt : new Date().toISOString(),\n resolved: Boolean(c.resolved),\n replies,\n });\n }\n return result;\n}\n\nexport function projectFromStore(state: {\n projectName: string;\n mapView: MapViewState;\n basemapStyleUrl: string;\n basemapVisible: boolean;\n basemapOpacity: number;\n blankBackgroundColor?: string | null;\n layers: GeoLibreLayer[];\n selectedLayerId?: string | null;\n layerGroups?: LayerGroup[];\n preferences: ProjectPreferences;\n plugins?: ProjectPluginState | null;\n legend?: LegendConfig | null;\n printLayout?: PrintLayoutConfig | null;\n storymap?: StoryMap | null;\n models?: ProcessingModel[] | null;\n processingHistory?: ProcessingRun[] | null;\n obia?: Record<string, unknown> | null;\n widgets?: DashboardWidget[] | null;\n bookmarks?: ProjectBookmark[] | null;\n bookmarkGroups?: ProjectBookmarkGroup[] | null;\n dashboardColumns?: number;\n mapLayout?: MapGridLayout;\n secondaryMapViews?: SecondaryMapView[];\n primaryMapLabel?: string;\n primaryRenderer?: MapRendererKind;\n /** Project-scoped Style Manager entries (the store's `projectStyleLibrary`). */\n styleLibrary?: StyleLibraryEntry[] | null;\n comments?: ProjectComment[] | null;\n /** Startup interaction state loaded with the project, written back as-is. */\n interaction?: ProjectInteraction | null;\n metadata: Record<string, unknown>;\n}): GeoLibreProject {\n const layers = state.layers.filter((layer) => !isSessionOnlyLayer(layer));\n const styles: Record<string, LayerStyle> = {};\n for (const layer of layers) {\n styles[layer.id] = layer.style;\n }\n const plugins = normalizeProjectPlugins(state.plugins);\n const legend = normalizeLegendConfig(state.legend);\n // Persist the composer only once it differs from the defaults, so a project\n // that never opened Print Layout keeps its previous byte-for-byte shape.\n const printLayout = normalizePrintLayoutConfig(state.printLayout);\n const storymap = normalizeStoryMap(state.storymap);\n const models = normalizeModels(state.models);\n const processingHistory = normalizeProcessingHistory(state.processingHistory);\n const obia = normalizeObiaWorkbench(state.obia);\n const widgets = normalizeWidgets(state.widgets);\n const comments = normalizeProjectComments(state.comments);\n const interaction = normalizeProjectInteraction(state.interaction);\n // Persist a non-default column count only; a default-layout dashboard (or a\n // widget-less project) stays free of the key for legacy readers.\n const dashboardColumns =\n state.dashboardColumns === undefined\n ? DEFAULT_DASHBOARD_COLUMNS\n : normalizeDashboardColumns(state.dashboardColumns);\n // Persist every group (including empty folders, which the UI supports). The\n // key is spread only when non-empty so legacy readers that don't recognise it\n // are unaffected; normalizeLayerGroups round-trips them back on load.\n const layerGroups = state.layerGroups ?? [];\n // Persist the grid only when it is more than a single pane; default single-map\n // projects stay byte-identical and unaffected by this feature. The reconcile\n // keeps `secondaryMapViews` exactly `rows * cols - 1` long even if state drifted.\n const { mapLayout, secondaryMapViews } = resolveMapGrid(\n normalizeMapLayout(state.mapLayout),\n normalizeSecondaryMapViews(state.secondaryMapViews),\n { mapView: state.mapView },\n );\n const persistGrid = mapLayout.rows * mapLayout.cols > 1;\n const styleLibrary = normalizeStyleLibraryEntries(state.styleLibrary);\n const selectedLayerId =\n state.selectedLayerId === null\n ? null\n : typeof state.selectedLayerId === \"string\" &&\n layers.some((layer) => layer.id === state.selectedLayerId)\n ? state.selectedLayerId\n : undefined;\n return {\n version: PROJECT_VERSION,\n name: state.projectName,\n mapView: state.mapView,\n basemapStyleUrl: state.basemapStyleUrl,\n basemapVisible: state.basemapVisible,\n basemapOpacity: state.basemapOpacity,\n ...(state.blankBackgroundColor ? { blankBackgroundColor: state.blankBackgroundColor } : {}),\n layers: layers.map(prepareLayerForSave),\n ...(selectedLayerId !== undefined ? { selectedLayerId } : {}),\n ...(layerGroups.length > 0 ? { layerGroups } : {}),\n styles,\n preferences: state.preferences,\n ...(plugins ? { plugins } : {}),\n ...(legend ? { legend } : {}),\n ...(printLayout && !isDefaultPrintLayout(printLayout) ? { printLayout } : {}),\n ...(storymap ? { storymap } : {}),\n ...(models ? { models } : {}),\n ...(processingHistory ? { processingHistory } : {}),\n ...(obia ? { obia } : {}),\n ...(widgets ? { widgets } : {}),\n ...bookmarkFields(state.bookmarks, state.bookmarkGroups),\n ...(dashboardColumns !== DEFAULT_DASHBOARD_COLUMNS ? { dashboardColumns } : {}),\n ...(persistGrid\n ? {\n mapLayout,\n secondaryMapViews,\n ...(normalizeString(state.primaryMapLabel)\n ? { primaryMapLabel: normalizeString(state.primaryMapLabel) }\n : {}),\n }\n : {}),\n // Written only for the non-default renderer, and independently of the grid:\n // a single-pane Cesium project persists `primaryRenderer` with no\n // `mapLayout`, and a MapLibre project writes neither.\n ...(normalizePrimaryRenderer(state.primaryRenderer)\n ? { primaryRenderer: normalizePrimaryRenderer(state.primaryRenderer)! }\n : {}),\n ...(styleLibrary.length > 0 ? { styleLibrary } : {}),\n ...(comments.length > 0 ? { comments } : {}),\n ...(interaction ? { interaction } : {}),\n metadata: state.metadata,\n };\n}\n\n// An external native layer can drop its persisted `geojson` only if its\n// features can be reconstructed on reopen, i.e. it has a fetchable source URL\n// (the Add Vector Layer / WFS / geojson-url cases). Layers loaded from local\n// files or built in-memory (e.g. by a plugin's drawing/annotation control)\n// have no such URL, so the persisted `geojson` is their ONLY copy and must be\n// kept.\nfunction hasRestorableSourceUrl(layer: GeoLibreLayer): boolean {\n const sourceUrl = layer.source.url;\n const originalUrl = layer.metadata.originalUrl;\n return (\n (typeof sourceUrl === \"string\" && sourceUrl.trim() !== \"\") ||\n (typeof originalUrl === \"string\" && originalUrl.trim() !== \"\")\n );\n}\n\n/**\n * Whether a layer lives only for the current session and is never written to a\n * project file. A plugin sets `metadata.sessionOnly` on a store row that mirrors\n * something it draws itself and cannot restore, such as a point cloud streamed\n * from a short-lived signed URL, so the row shows in the Layers panel while the\n * plugin runs but a reopened project carries no dead entry for it.\n *\n * @param layer - The layer to check.\n * @returns `true` when the layer must be left out of saved projects.\n */\nexport function isSessionOnlyLayer(layer: GeoLibreLayer): boolean {\n return layer.metadata.sessionOnly === true;\n}\n\nfunction prepareLayerForSave(layer: GeoLibreLayer): GeoLibreLayer {\n const hasGeometryEdits = layer.metadata.geometryEdited === true;\n layer = portableLayer(layer);\n // WFS feature URLs can contain inline authentication supplied by a plugin.\n // Persist a sanitized reference, and keep the fetched collection embedded so\n // the saved project remains renderable without storing those credentials.\n // The stripped flag is persisted alongside that sanitized reference: once the\n // reference is stored credential-free, a later save of the reopened project\n // cannot tell that the URL ever needed a secret, and would drop the embedded\n // features the reference can no longer re-fetch without it.\n let wfsCredentialsRedacted = layer.metadata.wfsCredentialsRedacted === true;\n if (layer.metadata.sourceKind === \"wfs-getfeature\") {\n const sourceUrl =\n typeof layer.source.url === \"string\"\n ? redactUrlCredentials(layer.source.url)\n : layer.source.url;\n const sourcePath =\n typeof layer.sourcePath === \"string\"\n ? redactUrlCredentials(layer.sourcePath)\n : layer.sourcePath;\n const originalUrl =\n typeof layer.metadata.originalUrl === \"string\"\n ? redactUrlCredentials(layer.metadata.originalUrl)\n : layer.metadata.originalUrl;\n const stripped =\n sourceUrl !== layer.source.url ||\n sourcePath !== layer.sourcePath ||\n originalUrl !== layer.metadata.originalUrl;\n wfsCredentialsRedacted ||= stripped;\n if (stripped) {\n layer = {\n ...layer,\n source: sourceUrl === layer.source.url ? layer.source : { ...layer.source, url: sourceUrl },\n sourcePath,\n metadata: {\n ...layer.metadata,\n ...(originalUrl === layer.metadata.originalUrl ? {} : { originalUrl }),\n wfsCredentialsRedacted: true,\n },\n };\n }\n }\n // This flag describes unsaved changes to the live source, not persisted\n // project state. A reference-only save reloads the original geometries;\n // carrying the flag into that project would warn about nonexistent edits.\n if (layer.metadata.geometryEdited !== undefined) {\n const { geometryEdited: _geometryEdited, ...metadata } = layer.metadata;\n layer = { ...layer, metadata };\n }\n // The live time filter is derived from the Time Slider's current date, so it\n // is transient: strip it before saving so a reopened project never starts\n // with a stale time-window filter hiding most of a layer's features. The\n // binding config in `metadata.timeBinding` persists, and the Time Slider\n // re-applies the filter the next time it activates.\n if (layer.timeFilter !== undefined) {\n const { timeFilter: _timeFilter, ...rest } = layer;\n layer = rest;\n }\n if (layer.embedFilter !== undefined) {\n const { embedFilter: _embedFilter, ...rest } = layer;\n layer = rest;\n }\n // Catalog metadata is written only when it says something: a record a\n // plugin (or an older build) left blank, or with blank fields, is cleaned or\n // dropped so an empty `descriptiveMetadata` block never reaches the file.\n if (layer.descriptiveMetadata !== undefined) {\n const { descriptiveMetadata: rawDescriptiveMetadata, ...rest } = layer;\n const descriptiveMetadata = normalizeLayerDescriptiveMetadata(rawDescriptiveMetadata);\n layer = descriptiveMetadata ? { ...rest, descriptiveMetadata } : rest;\n }\n\n // Some live plugin layers publish a large in-memory row model solely for\n // the Attribute Table and rebuild it from their feed on activation. Keeping\n // those rows in the store makes them queryable; embedding them in every\n // project/autosave would persist stale positions and can cross the history\n // snapshot ceiling.\n if (layer.geojson && layer.metadata.transientGeojson === true) {\n const { geojson: _geojson, ...rest } = layer;\n layer = rest;\n }\n\n // Live CZML feeds likewise rebuild their renderer payload on activation.\n // Persisting thousands of packets in every autosave duplicates the feed,\n // stores stale positions, and can exceed the history snapshot limit.\n if (layer.source.czmlData !== undefined && layer.metadata.transientCzml === true) {\n const { czmlData: _czmlData, ...source } = layer.source;\n layer = { ...layer, source };\n }\n\n // External native layers that restore their features from a source URL keep\n // a `geojson` copy on the map only for the attribute table; it is redundant\n // in a saved project and would only bloat it, so strip it. Layers without a\n // restorable URL (local-file or in-memory) keep their `geojson` because it is\n // the sole copy GeoLibre's restore path (`ensureExternalGeoJsonNativeLayer`)\n // re-renders from.\n //\n // Add Vector Layer (`maplibre-gl-vector`) layers are the exception: they are\n // restored by the control, not from `geojson` — from the file path on desktop\n // or embedded `metadata.embeddedGeoJSON` on the web. Their `geojson` is only\n // the attribute table's copy, so persisting it would silently embed the whole\n // dataset (bypassing the web embed prompt) instead of saving the path. Strip\n // it regardless of a restorable URL.\n const isVectorControlLayer = layer.metadata.sourceKind === \"maplibre-gl-vector\";\n if (\n layer.metadata.externalNativeLayer === true &&\n layer.geojson &&\n (hasRestorableSourceUrl(layer) || isVectorControlLayer)\n ) {\n const { geojson: _geojson, ...rest } = layer;\n layer = rest;\n }\n\n const wfsSourceUrl = layer.source.url;\n let hasHttpWfsSource = false;\n if (typeof wfsSourceUrl === \"string\") {\n try {\n const url = new URL(wfsSourceUrl);\n hasHttpWfsSource = url.protocol === \"http:\" || url.protocol === \"https:\";\n } catch {\n hasHttpWfsSource = false;\n }\n }\n\n // Host-managed WFS records reload through their persisted successful\n // GetFeature URL. Keep unedited feature collections reference-only in saved\n // projects. Edited geometry becomes authoritative embedded data, so omit\n // the request URL in that saved copy; otherwise a later save/reopen could\n // replace the edits with the service response.\n if (\n layer.type === \"geojson\" &&\n layer.metadata.sourceKind === \"wfs-getfeature\" &&\n layer.geojson &&\n hasHttpWfsSource\n ) {\n if (hasGeometryEdits) {\n const { url: _url, ...source } = layer.source;\n layer = { ...layer, source };\n } else if (!wfsCredentialsRedacted) {\n const { geojson: _geojson, ...rest } = layer;\n // Credential-bearing references are deliberately kept with their data.\n layer = rest;\n }\n }\n\n // An Add Vector Layer layer GeoLibre adopted (`maplibre-gl-vector-adopted`,\n // ADOPTED_VECTOR_SOURCE_KIND in @geolibre/plugins) holds its features in\n // `geojson`, but a URL-backed one saves the URL and is re-read through the\n // control on reopen, like the control layer it was. A browser-picked file has\n // no URL and keeps its features, like a drag-and-drop layer.\n if (\n layer.geojson &&\n layer.metadata.sourceKind === \"maplibre-gl-vector-adopted\" &&\n hasRestorableSourceUrl(layer)\n ) {\n const { geojson: _geojson, ...rest } = layer;\n layer = rest;\n }\n\n // A local-file layer the desktop host can re-read from its absolute path on\n // reopen (a drag-dropped or Add Data vector file) does not embed its features\n // either: the path is saved and the data is reloaded from disk. The flag is\n // only set when a real path was captured (desktop), so a web project — which\n // cannot re-read a path — never sets it and keeps the embedded copy.\n if (layer.geojson && layer.metadata.localFileReloadable === true) {\n const { geojson: _geojson, ...rest } = layer;\n layer = rest;\n }\n\n if (layer.type === \"wms\") {\n const tiles = layer.source.tiles;\n if (!Array.isArray(tiles)) return layer;\n const portableTiles = tiles.map((tile) => portableWmsTileUrl(tile));\n return portableTiles.some((tile, index) => tile !== tiles[index])\n ? { ...layer, source: { ...layer.source, tiles: portableTiles } }\n : layer;\n }\n\n if (layer.type !== \"xyz\") return layer;\n\n const originalUrl =\n typeof layer.metadata.originalUrl === \"string\" && layer.metadata.originalUrl.trim()\n ? layer.metadata.originalUrl\n : typeof layer.source.url === \"string\" && layer.source.url.trim()\n ? layer.source.url\n : null;\n if (!originalUrl) return layer;\n\n const metadata = { ...layer.metadata };\n delete metadata.resolvedUrl;\n\n // The collapse below rewinds a resolved short URL (or a desktop protocol URL)\n // back to what the user typed, because those tile URLs are not portable. A\n // TileJSON layer is the exception: `tiles` holds the document's own https\n // templates, which are portable, while its `originalUrl` is the *document*\n // URL and carries no {z}/{x}/{y}. Collapsing onto it would leave the saved\n // layer unable to request a tile until a re-fetch succeeds — and\n // `resolveProjectXyzLayers` keeps the on-disk layer when the document is\n // unreachable, so an offline reopen would strand it. Rewind only `url`.\n const tiles = typeof layer.metadata.tilejsonUrl === \"string\" ? {} : { tiles: [originalUrl] };\n\n return {\n ...layer,\n source: {\n ...layer.source,\n ...tiles,\n url: originalUrl,\n },\n metadata,\n };\n}\n\n/**\n * The plain HTTP(S) WMS template inside the desktop app's `geolibre-wms://`\n * wrapper (its CORS-exempt native fetcher), or the tile unchanged when it is\n * not wrapped or the wrapped URL is not HTTP(S). Shared by project export and\n * the ArcGIS engine's story-export templates.\n *\n * @param tile - A tile template, wrapped or not.\n * @returns The unwrapped template, or `tile` itself.\n */\nexport function portableWmsTileUrl(tile: unknown): unknown {\n // Keep this protocol prefix in sync with WMS_TILE_PROTOCOL in the desktop\n // app, which packages/core cannot import without reversing dependencies.\n if (typeof tile !== \"string\" || !tile.startsWith(\"geolibre-wms://\")) return tile;\n try {\n const url = new URL(tile).searchParams.get(\"url\");\n if (!url) return tile;\n const parsed = new URL(url);\n return parsed.protocol === \"http:\" || parsed.protocol === \"https:\" ? url : tile;\n } catch {\n return tile;\n }\n}\n\n/**\n * The store form of one project layer: its style completed from\n * {@link DEFAULT_LAYER_STYLE} and the project's top-level `styles` entry.\n *\n * Legacy and externally-authored projects can carry a partial top-level style\n * alongside newer fields on the layer itself. Those layer fields are kept,\n * while the top-level copy stays authoritative where it explicitly supplies a\n * value.\n *\n * @param project - The project the layer belongs to (for its `styles` map).\n * @param layer - One of `project.layers`.\n * @returns A new layer record ready for the store.\n */\nexport function hydrateProjectLayer(\n project: Pick<GeoLibreProject, \"styles\">,\n layer: GeoLibreLayer,\n): GeoLibreLayer {\n layer = withRasterSourceDefaults(layer);\n const topLevel = project.styles?.[layer.id];\n return {\n ...layer,\n style: topLevel\n ? { ...DEFAULT_LAYER_STYLE, ...layer.style, ...topLevel }\n : { ...DEFAULT_LAYER_STYLE, ...layer.style },\n };\n}\n\nexport function applyProjectToStore(project: GeoLibreProject): {\n projectName: string;\n mapView: MapViewState;\n basemapStyleUrl: string;\n basemapVisible: boolean;\n basemapOpacity: number;\n blankBackgroundColor: string | null;\n layers: GeoLibreLayer[];\n layerGroups: LayerGroup[];\n preferences: ProjectPreferences;\n projectPlugins: ProjectPluginState | null;\n legend: LegendConfig;\n printLayout: PrintLayoutConfig;\n storymap: StoryMap | null;\n models: ProcessingModel[];\n processingHistory: ProcessingRun[];\n obiaWorkbench: Record<string, unknown> | null;\n widgets: DashboardWidget[];\n bookmarks: ProjectBookmark[];\n bookmarkGroups: ProjectBookmarkGroup[];\n dashboardColumns: number;\n mapLayout: MapGridLayout;\n secondaryMapViews: SecondaryMapView[];\n primaryMapLabel: string;\n primaryRenderer: MapRendererKind;\n projectStyleLibrary: StyleLibraryEntry[];\n comments: ProjectComment[];\n projectInteraction: ProjectInteraction | null;\n metadata: Record<string, unknown>;\n} {\n const layers = project.layers.map((layer) => hydrateProjectLayer(project, layer));\n // Re-normalize here (even though `parseProject` already did) because\n // `applyProjectToStore` is a public entry point also reached directly by\n // programmatic/newProject loads that never passed through `parseProject`, so\n // this stays a hardening boundary for untrusted group data. The call is\n // idempotent on already-normalized input.\n const layerGroups = normalizeLayerGroups(project.layerGroups);\n const validGroupIds = new Set(layerGroups.map((g) => g.id));\n // Drop dangling groupIds, then restore the contiguity invariant the layer\n // panel relies on, in case the project was hand-edited or produced externally\n // with a group's members interleaved among unrelated layers.\n const normalizedLayers = normalizeGroupContiguity(\n layers.map((layer) =>\n layer.groupId && !validGroupIds.has(layer.groupId) ? { ...layer, groupId: undefined } : layer,\n ),\n );\n const basemapStyleUrl = project.basemapStyleUrl;\n const basemapVisible = project.basemapVisible ?? true;\n const basemapOpacity = project.basemapOpacity ?? 1;\n const blankBackgroundColor = normalizeBlankBackgroundColor(project.blankBackgroundColor);\n // Reconcile the (possibly hand-edited or programmatic) grid so the store's\n // invariant `secondaryMapViews.length === rows * cols - 1` always holds.\n const mapView = normalizeMapViewState(project.mapView);\n const { mapLayout, secondaryMapViews } = resolveMapGrid(\n normalizeMapLayout(project.mapLayout),\n normalizeSecondaryMapViews(project.secondaryMapViews),\n { mapView },\n );\n\n // Scrub cross-references that point at layers not present in the loaded\n // project (orphans from hand-editing or a partial project file).\n const existingLayerIds = new Set(normalizedLayers.map((l) => l.id));\n const widgets = normalizeWidgets(project.widgets) ?? [];\n const comments = normalizeProjectComments(project.comments);\n const legend = normalizeLegendConfig(project.legend) ?? {\n ...DEFAULT_LEGEND_CONFIG,\n };\n\n const allReferencedIds = new Set<string>();\n for (const w of widgets) allReferencedIds.add(w.layerId);\n for (const c of comments) {\n if (c.anchor.type === \"feature\") allReferencedIds.add(c.anchor.layerId);\n }\n for (const id of legend.order) allReferencedIds.add(id);\n for (const key of Object.keys(legend.overrides)) {\n const base = key.includes(\"::\") ? key.slice(0, key.indexOf(\"::\")) : key;\n allReferencedIds.add(base);\n }\n if (legend.customEntries) {\n for (const key of Object.keys(legend.customEntries)) {\n if (!key.startsWith(\"custom:\")) allReferencedIds.add(key);\n }\n }\n\n const orphanIds = new Set([...allReferencedIds].filter((id) => !existingLayerIds.has(id)));\n\n const scrubbedWidgets =\n orphanIds.size > 0 ? scrubWidgetsForRemovedLayers(widgets, orphanIds) : widgets;\n const scrubbedComments =\n orphanIds.size > 0 ? scrubCommentsForRemovedLayers(comments, orphanIds) : comments;\n const scrubbedLegend =\n orphanIds.size > 0 ? scrubLegendForRemovedLayers(legend, orphanIds) : legend;\n const bookmarkGroups = normalizeBookmarkGroups(project.bookmarkGroups);\n // The composer's data/atlas blocks name a layer directly rather than through\n // `allReferencedIds`, so they are scrubbed against the surviving layer set.\n const printLayout = scrubPrintLayoutForLayers(\n normalizePrintLayoutConfig(project.printLayout) ?? createDefaultPrintLayout(),\n existingLayerIds,\n );\n\n return {\n projectName: project.name,\n mapView,\n basemapStyleUrl,\n basemapVisible,\n basemapOpacity,\n blankBackgroundColor,\n layers: normalizedLayers,\n layerGroups,\n preferences: normalizeProjectPreferences(project.preferences),\n projectPlugins: normalizeProjectPlugins(project.plugins),\n legend: scrubbedLegend,\n printLayout,\n storymap: normalizeStoryMap(project.storymap),\n models: normalizeModels(project.models) ?? [],\n processingHistory: normalizeProcessingHistory(project.processingHistory) ?? [],\n obiaWorkbench: normalizeObiaWorkbench(project.obia),\n widgets: scrubbedWidgets,\n bookmarkGroups,\n bookmarks: normalizeBookmarks(project.bookmarks, bookmarkGroups),\n dashboardColumns: normalizeDashboardColumns(project.dashboardColumns),\n mapLayout,\n secondaryMapViews,\n primaryMapLabel: normalizeString(project.primaryMapLabel),\n // An unknown or absent value resolves to the 2D map, so a project written\n // before #2217 (and any hand-edited one) opens on MapLibre as before.\n primaryRenderer: normalizePrimaryRenderer(project.primaryRenderer) ?? DEFAULT_PRIMARY_RENDERER,\n projectStyleLibrary: normalizeStyleLibraryEntries(project.styleLibrary),\n comments: scrubbedComments,\n projectInteraction: normalizeProjectInteraction(project.interaction),\n metadata: project.metadata,\n };\n}\n\n/**\n * Create an unlinked copy of a project, suffixed with \"(copy)\" by default and\n * stripped of share-specific metadata (shareId, shareUrl, etc.).\n */\nexport function detachProjectCopy(\n project: GeoLibreProject,\n options: { nameSuffix?: string } = {},\n): GeoLibreProject {\n const suffix = options.nameSuffix ?? \"(copy)\";\n const rawName = project.name.trim() || DEFAULT_PROJECT_NAME;\n const name = suffix ? (rawName.endsWith(suffix) ? rawName : `${rawName} ${suffix}`) : rawName;\n\n const metadata = { ...(project.metadata ?? {}) };\n for (const key of Object.keys(metadata)) {\n if (/^share/i.test(key)) {\n delete metadata[key];\n }\n }\n\n return {\n ...project,\n id: uuidv4(),\n name,\n metadata,\n };\n}\n\n/**\n * Create a template snapshot of a project. Optionally strips data layers while\n * keeping basemap, layer groups, styles, legend config, preferences, widgets, and\n * print layout.\n */\nexport function createProjectTemplate(\n project: GeoLibreProject,\n options: { name?: string; stripDataLayers?: boolean } = {},\n): GeoLibreProject {\n const detached = detachProjectCopy(project, { nameSuffix: \"\" });\n const name = options.name?.trim() || detached.name;\n const stripDataLayers = options.stripDataLayers !== false;\n\n const layers = stripDataLayers ? [] : detached.layers;\n\n return {\n ...detached,\n name,\n layers,\n metadata: {\n ...detached.metadata,\n isTemplate: true,\n },\n };\n}\n","/**\n * Structured differ for two {@link GeoLibreProject} documents, used to show\n * what changed between autosave snapshots (GeoLibre#2858, \"Project snapshot\n * diff\").\n *\n * The result is a summary rather than a JSON patch: layers are matched by id\n * and reported as added, removed, moved or changed; per-layer changes are\n * grouped (style, labels, filter, source, other) with truncated before/after\n * previews; embedded GeoJSON is summarized as counts of added, removed and\n * modified features. Everything is pure and synchronous.\n *\n * Embedded GeoJSON can be large, so each feature is hashed at most once per\n * object identity (memoized in a `WeakMap`), and layers whose `geojson`\n * reference is shared between the two projects are skipped outright.\n */\nimport type { FeatureCollection } from \"geojson\";\nimport type { GeoLibreLayer, GeoLibreProject, ProjectPluginState } from \"./types\";\n\n/** One changed value, with bounded human-readable previews of each side. */\nexport interface ProjectValueChange {\n /** Dotted key path relative to the section, e.g. `fillColor` or `labels.field`. */\n path: string;\n /** Preview of the earlier value; `undefined` when the key was absent. */\n before?: string;\n /** Preview of the later value; `undefined` when the key is now absent. */\n after?: string;\n}\n\n/** Counts of embedded GeoJSON feature changes in one layer. */\nexport interface FeatureChangeSummary {\n added: number;\n removed: number;\n modified: number;\n unchanged: number;\n /**\n * How features were matched: `\"id\"` when every feature carries a unique\n * `id`, `\"geometry\"` when none does (matched by geometry hash), `\"mixed\"`\n * otherwise.\n */\n matchedBy: \"id\" | \"geometry\" | \"mixed\";\n}\n\n/** Changes to one layer between the two projects. */\nexport interface LayerDiff {\n id: string;\n /** Layer name in the later project (or the earlier one when removed). */\n name: string;\n /** Layer type in the later project (or the earlier one when removed). */\n type: string;\n status: \"added\" | \"removed\" | \"changed\";\n /** Set when the layer was renamed. */\n renamed?: { before: string; after: string };\n /**\n * Set when the layer changed position relative to the layers present in\n * both projects (bottom-to-top indexes in each project's `layers`).\n */\n moved?: { before: number; after: number };\n visibility?: { before: boolean; after: boolean };\n opacity?: { before: number; after: number };\n /** Style keys other than labels. */\n style: ProjectValueChange[];\n /** Label style keys (`style.labels.*`), without the `labels.` prefix. */\n labels: ProjectValueChange[];\n /** `filterExpression`, `quickFilters` and `levelFilter`. */\n filter: ProjectValueChange[];\n /** `type`, `source.*` and `sourcePath`. */\n source: ProjectValueChange[];\n /** Any other persisted layer field (popup, joins, metadata.*, ...). */\n other: ProjectValueChange[];\n /** Embedded GeoJSON feature changes; absent when the data is unchanged. */\n features?: FeatureChangeSummary;\n /** Feature count for an added or removed layer with embedded GeoJSON. */\n featureCount?: number;\n}\n\n/** Changes to one plugin's project state. */\nexport interface PluginDiff {\n id: string;\n /** `added`/`removed` mean the plugin was activated/deactivated. */\n status: \"added\" | \"removed\" | \"changed\";\n changes: ProjectValueChange[];\n}\n\n/** The full structured difference between two projects. */\nexport interface ProjectDiff {\n layers: {\n added: LayerDiff[];\n removed: LayerDiff[];\n changed: LayerDiff[];\n /** True when any layer present in both projects changed position. */\n reordered: boolean;\n };\n camera: ProjectValueChange[];\n basemap: ProjectValueChange[];\n /** Map projection (`preferences.map.projection`). */\n projection: ProjectValueChange[];\n /** Other project preferences (`preferences.*` minus the projection). */\n preferences: ProjectValueChange[];\n plugins: PluginDiff[];\n /** Plugin manifest URLs added to or removed from the project. */\n pluginManifests: { added: string[]; removed: string[] };\n /** Project name (`name`), `metadata.description` and other metadata keys. */\n metadata: ProjectValueChange[];\n /** Other top-level sections that changed (`legend`, `storymap`, ...). */\n sections: string[];\n /** Total number of reported changes; 0 means the projects are equivalent. */\n changeCount: number;\n}\n\n/** Options for {@link diffProjects}. */\nexport interface ProjectDiffOptions {\n /** Maximum characters of a before/after preview (default 80). */\n maxPreviewLength?: number;\n}\n\nconst DEFAULT_PREVIEW_LENGTH = 80;\nconst CAMERA_EPSILON = 1e-6;\n\n/** Layer fields reported in their own group, never under `other`. */\nconst LAYER_GROUPED_KEYS = new Set([\n \"id\",\n \"name\",\n \"type\",\n \"visible\",\n \"opacity\",\n \"style\",\n \"geojson\",\n \"filterExpression\",\n \"quickFilters\",\n \"levelFilter\",\n \"source\",\n \"sourcePath\",\n // Derived from the layer order on save; reordering is reported separately.\n \"beforeId\",\n]);\n\n/** Top-level project fields reported in their own group, never as a section. */\nconst PROJECT_GROUPED_KEYS = new Set([\n \"id\",\n \"version\",\n \"name\",\n \"mapView\",\n \"basemapStyleUrl\",\n \"basemapVisible\",\n \"basemapOpacity\",\n \"blankBackgroundColor\",\n \"primaryRenderer\",\n \"layers\",\n \"styles\",\n \"preferences\",\n \"plugins\",\n \"metadata\",\n // UI selection, not content.\n \"selectedLayerId\",\n]);\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n\n/**\n * Deep structural equality for JSON-like values. A key whose value is\n * `undefined` is treated as absent, so an in-memory record compares equal to\n * its JSON round trip.\n *\n * @param a - First value.\n * @param b - Second value.\n * @returns True when the two values are structurally equal.\n */\nexport function jsonValuesEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a === \"number\" && typeof b === \"number\") return Number.isNaN(a) && Number.isNaN(b);\n if (Array.isArray(a)) {\n if (!Array.isArray(b) || a.length !== b.length) return false;\n for (let i = 0; i < a.length; i++) if (!jsonValuesEqual(a[i], b[i])) return false;\n return true;\n }\n if (isPlainObject(a)) {\n if (!isPlainObject(b)) return false;\n for (const key of Object.keys(a)) {\n if (a[key] === undefined) continue;\n if (!jsonValuesEqual(a[key], b[key])) return false;\n }\n for (const key of Object.keys(b)) {\n if (b[key] !== undefined && a[key] === undefined) return false;\n }\n return true;\n }\n return false;\n}\n\nfunction isBlank(value: unknown): boolean {\n if (value === undefined || value === null) return true;\n if (Array.isArray(value)) return value.length === 0;\n return isPlainObject(value) && Object.values(value).every((item) => item === undefined);\n}\n\n/**\n * Equality for reporting: like {@link jsonValuesEqual}, but an empty array,\n * an empty object, `null` and an absent value are interchangeable, since the\n * save path omits empty sections that an in-memory project still carries.\n */\nfunction sameForReport(a: unknown, b: unknown): boolean {\n return jsonValuesEqual(a, b) || (isBlank(a) && isBlank(b));\n}\n\n/**\n * Render a bounded JSON-ish preview of a value without serializing more of it\n * than the preview can show, so a large array or object costs no more than\n * `maxLength` characters of work.\n *\n * @param value - The value to preview.\n * @param maxLength - Maximum preview length; longer output ends with `…`.\n * @returns The preview, or `undefined` for `undefined`.\n */\nexport function previewValue(\n value: unknown,\n maxLength = DEFAULT_PREVIEW_LENGTH,\n): string | undefined {\n if (value === undefined) return undefined;\n if (typeof value === \"string\") {\n return value.length > maxLength ? `${value.slice(0, Math.max(0, maxLength - 1))}…` : value;\n }\n let out = \"\";\n let truncated = false;\n const emit = (text: string): boolean => {\n if (out.length + text.length > maxLength) {\n out += text.slice(0, Math.max(0, maxLength - out.length));\n truncated = true;\n return false;\n }\n out += text;\n return true;\n };\n const walk = (v: unknown): boolean => {\n if (v === null || typeof v !== \"object\") {\n if (typeof v === \"number\" && !Number.isFinite(v)) return emit(\"null\");\n // A long string can never fit, so quote only the part that could show\n // instead of serializing a nested blob (a data URI, say) in full.\n if (typeof v === \"string\" && v.length > maxLength) {\n return emit(JSON.stringify(v.slice(0, maxLength)));\n }\n const text = typeof v === \"bigint\" ? v.toString() : JSON.stringify(v);\n return emit(text ?? \"null\");\n }\n if (Array.isArray(v)) {\n if (!emit(\"[\")) return false;\n for (let i = 0; i < v.length; i++) {\n if (i > 0 && !emit(\",\")) return false;\n if (!walk(v[i] === undefined ? null : v[i])) return false;\n }\n return emit(\"]\");\n }\n if (!emit(\"{\")) return false;\n let first = true;\n for (const [key, item] of Object.entries(v as Record<string, unknown>)) {\n if (item === undefined) continue;\n if (!first && !emit(\",\")) return false;\n first = false;\n if (!emit(`${JSON.stringify(key)}:`)) return false;\n if (!walk(item)) return false;\n }\n return emit(\"}\");\n };\n walk(value);\n if (truncated) out = `${out.slice(0, Math.max(0, maxLength - 1))}…`;\n return out;\n}\n\n/**\n * Diff two records key by key. Nested plain objects are descended into up to\n * `depth` levels (paths joined with `.`); arrays and deeper values are\n * compared whole.\n */\nfunction diffRecords(\n before: Record<string, unknown> | undefined,\n after: Record<string, unknown> | undefined,\n options: { prefix?: string; depth?: number; skip?: Set<string>; maxLength: number },\n out: ProjectValueChange[],\n): void {\n const a = before ?? {};\n const b = after ?? {};\n const prefix = options.prefix ?? \"\";\n const depth = options.depth ?? 1;\n const keys = new Set([...Object.keys(a), ...Object.keys(b)]);\n for (const key of keys) {\n if (options.skip?.has(key)) continue;\n const x = a[key];\n const y = b[key];\n if (sameForReport(x, y)) continue;\n if (depth > 0 && isPlainObject(x) && isPlainObject(y)) {\n diffRecords(\n x,\n y,\n { prefix: `${prefix}${key}.`, depth: depth - 1, maxLength: options.maxLength },\n out,\n );\n continue;\n }\n out.push({\n path: `${prefix}${key}`,\n before: previewValue(x, options.maxLength),\n after: previewValue(y, options.maxLength),\n });\n }\n}\n\n// ---------------------------------------------------------------------------\n// Feature hashing\n// ---------------------------------------------------------------------------\n\ninterface FeatureHash {\n /** Hash of the geometry alone. */\n geometry: string;\n /** Hash of geometry + properties (+ any other member). */\n full: string;\n}\n\nconst featureHashCache = new WeakMap<object, FeatureHash>();\n\n/** cyrb53: a fast, well-distributed 53-bit string hash. */\nfunction hashString(text: string, seed = 0): string {\n let h1 = 0xdeadbeef ^ seed;\n let h2 = 0x41c6ce57 ^ seed;\n for (let i = 0; i < text.length; i++) {\n const ch = text.charCodeAt(i);\n h1 = Math.imul(h1 ^ ch, 2654435761);\n h2 = Math.imul(h2 ^ ch, 1597334677);\n }\n h1 = Math.imul(h1 ^ (h1 >>> 16), 2246822507);\n h1 ^= Math.imul(h2 ^ (h2 >>> 13), 3266489909);\n h2 = Math.imul(h2 ^ (h2 >>> 16), 2246822507);\n h2 ^= Math.imul(h1 ^ (h1 >>> 13), 3266489909);\n return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36);\n}\n\nfunction hashFeature(feature: unknown): FeatureHash {\n if (typeof feature !== \"object\" || feature === null) {\n const text = JSON.stringify(feature ?? null);\n const h = hashString(text);\n return { geometry: h, full: h };\n }\n const cached = featureHashCache.get(feature);\n if (cached) return cached;\n const { geometry, properties, id: _id, ...rest } = feature as Record<string, unknown>;\n const geometryHash = hashString(JSON.stringify(geometry ?? null));\n const propertiesHash = hashString(JSON.stringify(properties ?? null), 1);\n const restKeys = Object.keys(rest).filter((key) => key !== \"type\" && rest[key] !== undefined);\n const restHash = restKeys.length > 0 ? hashString(JSON.stringify(rest), 2) : \"\";\n const result = { geometry: geometryHash, full: `${geometryHash}:${propertiesHash}:${restHash}` };\n featureHashCache.set(feature, result);\n return result;\n}\n\nfunction featureKey(feature: unknown): string | null {\n if (typeof feature !== \"object\" || feature === null) return null;\n const id = (feature as { id?: unknown }).id;\n if (typeof id === \"string\") return `s:${id}`;\n if (typeof id === \"number\" && Number.isFinite(id)) return `n:${id}`;\n return null;\n}\n\ninterface IndexedFeatures {\n byId: Map<string, string>;\n /** Geometry hash → full hashes of the id-less features with that geometry. */\n byGeometry: Map<string, string[]>;\n keyed: number;\n unkeyed: number;\n}\n\nfunction indexFeatures(features: readonly unknown[]): IndexedFeatures {\n const byId = new Map<string, string>();\n const byGeometry = new Map<string, string[]>();\n let keyed = 0;\n let unkeyed = 0;\n for (const feature of features) {\n const hash = hashFeature(feature);\n const key = featureKey(feature);\n if (key !== null && !byId.has(key)) {\n byId.set(key, hash.full);\n keyed++;\n continue;\n }\n // No id, or a duplicate id: fall back to geometry matching.\n const list = byGeometry.get(hash.geometry);\n if (list) list.push(hash.full);\n else byGeometry.set(hash.geometry, [hash.full]);\n unkeyed++;\n }\n return { byId, byGeometry, keyed, unkeyed };\n}\n\n/**\n * Count the features added, removed and modified between two feature\n * collections. Features are matched by `id` when they carry one; id-less\n * features are matched by geometry hash, so a feature whose properties\n * changed but whose geometry did not counts as modified, while a moved\n * id-less feature counts as one removal plus one addition.\n *\n * @param before - Earlier collection (absent counts as empty).\n * @param after - Later collection (absent counts as empty).\n * @returns The change counts.\n */\nexport function diffFeatureCollections(\n before: FeatureCollection | null | undefined,\n after: FeatureCollection | null | undefined,\n): FeatureChangeSummary {\n const a = indexFeatures(Array.isArray(before?.features) ? before.features : []);\n const b = indexFeatures(Array.isArray(after?.features) ? after.features : []);\n let added = 0;\n let removed = 0;\n let modified = 0;\n let unchanged = 0;\n\n for (const [key, hash] of a.byId) {\n const other = b.byId.get(key);\n if (other === undefined) removed++;\n else if (other === hash) unchanged++;\n else modified++;\n }\n for (const key of b.byId.keys()) if (!a.byId.has(key)) added++;\n\n const geometryKeys = new Set([...a.byGeometry.keys(), ...b.byGeometry.keys()]);\n for (const geometry of geometryKeys) {\n const left = a.byGeometry.get(geometry) ?? [];\n const right = b.byGeometry.get(geometry) ?? [];\n // Pair identical features first, then pair the rest as modified.\n const counts = new Map<string, number>();\n for (const full of left) counts.set(full, (counts.get(full) ?? 0) + 1);\n let same = 0;\n for (const full of right) {\n const n = counts.get(full) ?? 0;\n if (n > 0) {\n counts.set(full, n - 1);\n same++;\n }\n }\n const leftRest = left.length - same;\n const rightRest = right.length - same;\n const paired = Math.min(leftRest, rightRest);\n unchanged += same;\n modified += paired;\n removed += leftRest - paired;\n added += rightRest - paired;\n }\n\n const keyed = a.keyed + b.keyed;\n const unkeyed = a.unkeyed + b.unkeyed;\n const matchedBy = unkeyed === 0 ? \"id\" : keyed === 0 ? \"geometry\" : \"mixed\";\n return { added, removed, modified, unchanged, matchedBy };\n}\n\n// ---------------------------------------------------------------------------\n// Layers\n// ---------------------------------------------------------------------------\n\nfunction effectiveStyle(\n project: GeoLibreProject,\n layer: GeoLibreLayer,\n): Record<string, unknown> | undefined {\n const top = project.styles?.[layer.id];\n if (!top) return layer.style as unknown as Record<string, unknown> | undefined;\n return { ...(layer.style ?? {}), ...top } as unknown as Record<string, unknown>;\n}\n\nfunction featureCountOf(layer: GeoLibreLayer): number | undefined {\n return Array.isArray(layer.geojson?.features) ? layer.geojson.features.length : undefined;\n}\n\nfunction emptyLayerDiff(layer: GeoLibreLayer, status: LayerDiff[\"status\"]): LayerDiff {\n return {\n id: layer.id,\n name: layer.name,\n type: layer.type,\n status,\n style: [],\n labels: [],\n filter: [],\n source: [],\n other: [],\n };\n}\n\n/**\n * Ids (of layers present in both orders) that moved: everything outside a\n * longest increasing subsequence of their later positions, so dragging one\n * layer reports that layer rather than every layer it passed.\n */\nfunction movedLayerIds(beforeIds: string[], afterIndex: Map<string, number>): Set<string> {\n const common = beforeIds.filter((id) => afterIndex.has(id));\n const seq = common.map((id) => afterIndex.get(id)!);\n // Patience-sorting LIS with predecessor links.\n const tails: number[] = [];\n const prev = new Array<number>(seq.length).fill(-1);\n for (let i = 0; i < seq.length; i++) {\n let lo = 0;\n let hi = tails.length;\n while (lo < hi) {\n const mid = (lo + hi) >> 1;\n if (seq[tails[mid]] < seq[i]) lo = mid + 1;\n else hi = mid;\n }\n if (lo > 0) prev[i] = tails[lo - 1];\n tails[lo] = i;\n }\n const stable = new Set<string>();\n for (let i = tails.length ? tails[tails.length - 1] : -1; i >= 0; i = prev[i]) {\n stable.add(common[i]);\n }\n return new Set(common.filter((id) => !stable.has(id)));\n}\n\nfunction diffLayer(\n beforeProject: GeoLibreProject,\n afterProject: GeoLibreProject,\n a: GeoLibreLayer,\n b: GeoLibreLayer,\n maxLength: number,\n): LayerDiff {\n const diff = emptyLayerDiff(b, \"changed\");\n if (a.name !== b.name) diff.renamed = { before: a.name, after: b.name };\n if (Boolean(a.visible) !== Boolean(b.visible)) {\n diff.visibility = { before: Boolean(a.visible), after: Boolean(b.visible) };\n }\n if (!jsonValuesEqual(a.opacity, b.opacity))\n diff.opacity = { before: a.opacity, after: b.opacity };\n\n const styleA = effectiveStyle(beforeProject, a);\n const styleB = effectiveStyle(afterProject, b);\n if (!jsonValuesEqual(styleA, styleB)) {\n diffRecords(styleA, styleB, { skip: new Set([\"labels\"]), maxLength }, diff.style);\n const labelsA = styleA?.labels;\n const labelsB = styleB?.labels;\n if (!jsonValuesEqual(labelsA, labelsB)) {\n diffRecords(\n isPlainObject(labelsA) ? labelsA : undefined,\n isPlainObject(labelsB) ? labelsB : undefined,\n { maxLength },\n diff.labels,\n );\n }\n }\n\n diffRecords(\n {\n filterExpression: a.filterExpression,\n quickFilters: a.quickFilters,\n levelFilter: a.levelFilter,\n },\n {\n filterExpression: b.filterExpression,\n quickFilters: b.quickFilters,\n levelFilter: b.levelFilter,\n },\n { depth: 0, maxLength },\n diff.filter,\n );\n\n if (a.type !== b.type) {\n diff.source.push({ path: \"type\", before: a.type, after: b.type });\n }\n if (!jsonValuesEqual(a.source, b.source)) {\n diffRecords(a.source, b.source, { prefix: \"source.\", depth: 0, maxLength }, diff.source);\n }\n if (a.sourcePath !== b.sourcePath) {\n diff.source.push({\n path: \"sourcePath\",\n before: previewValue(a.sourcePath, maxLength),\n after: previewValue(b.sourcePath, maxLength),\n });\n }\n\n diffRecords(\n a as unknown as Record<string, unknown>,\n b as unknown as Record<string, unknown>,\n { skip: LAYER_GROUPED_KEYS, depth: 1, maxLength },\n diff.other,\n );\n\n if (a.geojson !== b.geojson && (a.geojson || b.geojson)) {\n const features = diffFeatureCollections(a.geojson, b.geojson);\n if (features.added || features.removed || features.modified) diff.features = features;\n }\n return diff;\n}\n\nfunction layerHasChanges(diff: LayerDiff): boolean {\n return Boolean(\n diff.renamed ||\n diff.moved ||\n diff.visibility ||\n diff.opacity ||\n diff.features ||\n diff.style.length ||\n diff.labels.length ||\n diff.filter.length ||\n diff.source.length ||\n diff.other.length,\n );\n}\n\nfunction layerChangeCount(diff: LayerDiff): number {\n return (\n (diff.renamed ? 1 : 0) +\n (diff.moved ? 1 : 0) +\n (diff.visibility ? 1 : 0) +\n (diff.opacity ? 1 : 0) +\n (diff.features ? 1 : 0) +\n diff.style.length +\n diff.labels.length +\n diff.filter.length +\n diff.source.length +\n diff.other.length\n );\n}\n\n// ---------------------------------------------------------------------------\n// Plugins\n// ---------------------------------------------------------------------------\n\nfunction diffPlugins(\n before: ProjectPluginState | undefined,\n after: ProjectPluginState | undefined,\n maxLength: number,\n): { plugins: PluginDiff[]; manifests: { added: string[]; removed: string[] } } {\n const activeA = new Set(before?.activePluginIds ?? []);\n const activeB = new Set(after?.activePluginIds ?? []);\n const settingsA = before?.settings ?? {};\n const settingsB = after?.settings ?? {};\n const positionsA = before?.mapControlPositions ?? {};\n const positionsB = after?.mapControlPositions ?? {};\n const ids = new Set([\n ...activeA,\n ...activeB,\n ...Object.keys(settingsA),\n ...Object.keys(settingsB),\n ...Object.keys(positionsA),\n ...Object.keys(positionsB),\n ]);\n const plugins: PluginDiff[] = [];\n for (const id of [...ids].sort()) {\n const changes: ProjectValueChange[] = [];\n const wasActive = activeA.has(id);\n const isActive = activeB.has(id);\n if (!jsonValuesEqual(positionsA[id], positionsB[id])) {\n changes.push({\n path: \"position\",\n before: previewValue(positionsA[id], maxLength),\n after: previewValue(positionsB[id], maxLength),\n });\n }\n const sa = settingsA[id];\n const sb = settingsB[id];\n if (!jsonValuesEqual(sa, sb)) {\n if (isPlainObject(sa) || isPlainObject(sb)) {\n diffRecords(\n isPlainObject(sa) ? sa : undefined,\n isPlainObject(sb) ? sb : undefined,\n { prefix: \"settings.\", depth: 0, maxLength },\n changes,\n );\n } else {\n changes.push({\n path: \"settings\",\n before: previewValue(sa, maxLength),\n after: previewValue(sb, maxLength),\n });\n }\n }\n if (wasActive !== isActive) {\n plugins.push({ id, status: isActive ? \"added\" : \"removed\", changes });\n } else if (changes.length > 0) {\n plugins.push({ id, status: \"changed\", changes });\n }\n }\n const urlsA = new Set(before?.manifestUrls ?? []);\n const urlsB = new Set(after?.manifestUrls ?? []);\n return {\n plugins,\n manifests: {\n added: [...urlsB].filter((url) => !urlsA.has(url)),\n removed: [...urlsA].filter((url) => !urlsB.has(url)),\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Project\n// ---------------------------------------------------------------------------\n\nfunction numbersDiffer(a: unknown, b: unknown): boolean {\n if (typeof a === \"number\" && typeof b === \"number\") return Math.abs(a - b) > CAMERA_EPSILON;\n return !jsonValuesEqual(a, b);\n}\n\n/**\n * Compute a structured summary of what changed from `before` to `after`.\n *\n * Layers are matched by id. Embedded GeoJSON is compared per feature (by id,\n * falling back to geometry hash) and each feature is hashed once per object,\n * so diffing the same parsed project against several others reuses its\n * hashes; callers keep and pass the same parsed project objects to benefit.\n *\n * @param before - The earlier project (e.g. an autosave snapshot).\n * @param after - The later project (e.g. the current project).\n * @param options - Preview length and other tuning.\n * @returns The grouped change summary.\n */\nexport function diffProjects(\n before: GeoLibreProject,\n after: GeoLibreProject,\n options: ProjectDiffOptions = {},\n): ProjectDiff {\n const maxLength = options.maxPreviewLength ?? DEFAULT_PREVIEW_LENGTH;\n const layersA = Array.isArray(before.layers) ? before.layers : [];\n const layersB = Array.isArray(after.layers) ? after.layers : [];\n const byIdA = new Map(layersA.map((layer) => [layer.id, layer]));\n const byIdB = new Map(layersB.map((layer) => [layer.id, layer]));\n const indexA = new Map(layersA.map((layer, i) => [layer.id, i]));\n const indexB = new Map(layersB.map((layer, i) => [layer.id, i]));\n const moved = movedLayerIds(\n layersA.map((layer) => layer.id),\n indexB,\n );\n\n const added: LayerDiff[] = [];\n const removed: LayerDiff[] = [];\n const changed: LayerDiff[] = [];\n for (const layer of layersB) {\n if (byIdA.has(layer.id)) continue;\n const diff = emptyLayerDiff(layer, \"added\");\n const count = featureCountOf(layer);\n if (count !== undefined) diff.featureCount = count;\n added.push(diff);\n }\n for (const layer of layersA) {\n if (byIdB.has(layer.id)) continue;\n const diff = emptyLayerDiff(layer, \"removed\");\n const count = featureCountOf(layer);\n if (count !== undefined) diff.featureCount = count;\n removed.push(diff);\n }\n // Report changed layers top-most first, matching the Layers panel.\n for (let i = layersB.length - 1; i >= 0; i--) {\n const b = layersB[i];\n const a = byIdA.get(b.id);\n if (!a) continue;\n const identical = a === b && before.styles?.[b.id] === after.styles?.[b.id];\n const diff = identical\n ? emptyLayerDiff(b, \"changed\")\n : diffLayer(before, after, a, b, maxLength);\n if (moved.has(b.id)) diff.moved = { before: indexA.get(b.id)!, after: indexB.get(b.id)! };\n if (layerHasChanges(diff)) changed.push(diff);\n }\n\n const camera: ProjectValueChange[] = [];\n const viewA = before.mapView ?? ({} as GeoLibreProject[\"mapView\"]);\n const viewB = after.mapView ?? ({} as GeoLibreProject[\"mapView\"]);\n const centerA = viewA.center ?? [];\n const centerB = viewB.center ?? [];\n if (numbersDiffer(centerA[0], centerB[0]) || numbersDiffer(centerA[1], centerB[1])) {\n const fmt = (c: unknown[]) =>\n c.length === 2 && c.every((n) => typeof n === \"number\")\n ? `${(c[0] as number).toFixed(5)}, ${(c[1] as number).toFixed(5)}`\n : undefined;\n camera.push({ path: \"center\", before: fmt(centerA), after: fmt(centerB) });\n }\n for (const key of [\"zoom\", \"bearing\", \"pitch\"] as const) {\n if (numbersDiffer(viewA[key], viewB[key])) {\n const fmt = (n: unknown) =>\n typeof n === \"number\" ? String(Math.round(n * 100) / 100) : previewValue(n, maxLength);\n camera.push({ path: key, before: fmt(viewA[key]), after: fmt(viewB[key]) });\n }\n }\n\n const basemap: ProjectValueChange[] = [];\n diffRecords(\n {\n basemapStyleUrl: before.basemapStyleUrl,\n basemapVisible: before.basemapVisible,\n basemapOpacity: before.basemapOpacity,\n blankBackgroundColor: before.blankBackgroundColor ?? undefined,\n primaryRenderer: before.primaryRenderer,\n },\n {\n basemapStyleUrl: after.basemapStyleUrl,\n basemapVisible: after.basemapVisible,\n basemapOpacity: after.basemapOpacity,\n blankBackgroundColor: after.blankBackgroundColor ?? undefined,\n primaryRenderer: after.primaryRenderer,\n },\n { depth: 0, maxLength: Math.max(maxLength, 160) },\n basemap,\n );\n\n const projection: ProjectValueChange[] = [];\n const prefsA = (before.preferences ?? {}) as unknown as Record<string, unknown>;\n const prefsB = (after.preferences ?? {}) as unknown as Record<string, unknown>;\n const mapA = isPlainObject(prefsA.map) ? prefsA.map : {};\n const mapB = isPlainObject(prefsB.map) ? prefsB.map : {};\n if (!jsonValuesEqual(mapA.projection, mapB.projection)) {\n projection.push({\n path: \"projection\",\n before: previewValue(mapA.projection, maxLength),\n after: previewValue(mapB.projection, maxLength),\n });\n }\n const preferences: ProjectValueChange[] = [];\n if (!jsonValuesEqual(prefsA, prefsB)) {\n diffRecords(\n { ...prefsA, map: { ...mapA, projection: undefined } },\n { ...prefsB, map: { ...mapB, projection: undefined } },\n { depth: 1, maxLength },\n preferences,\n );\n }\n\n const { plugins, manifests } = diffPlugins(before.plugins, after.plugins, maxLength);\n\n const metadata: ProjectValueChange[] = [];\n if (before.name !== after.name) {\n metadata.push({\n path: \"name\",\n before: previewValue(before.name, maxLength),\n after: previewValue(after.name, maxLength),\n });\n }\n diffRecords(before.metadata, after.metadata, { depth: 0, maxLength }, metadata);\n // Lead with the description, the field users edit, ahead of other keys.\n metadata.sort((x, y) => metadataRank(x.path) - metadataRank(y.path));\n\n const sections: string[] = [];\n const projectA = before as unknown as Record<string, unknown>;\n const projectB = after as unknown as Record<string, unknown>;\n for (const key of new Set([...Object.keys(projectA), ...Object.keys(projectB)])) {\n if (PROJECT_GROUPED_KEYS.has(key)) continue;\n if (!sameForReport(projectA[key], projectB[key])) sections.push(key);\n }\n sections.sort();\n\n const changeCount =\n added.length +\n removed.length +\n changed.reduce((sum, diff) => sum + layerChangeCount(diff), 0) +\n camera.length +\n basemap.length +\n projection.length +\n preferences.length +\n // A plugin counts each changed setting, like a layer counts each key; a\n // bare enable/disable counts once.\n plugins.reduce((sum, plugin) => sum + Math.max(1, plugin.changes.length), 0) +\n manifests.added.length +\n manifests.removed.length +\n metadata.length +\n sections.length;\n\n return {\n layers: { added, removed, changed, reordered: moved.size > 0 },\n camera,\n basemap,\n projection,\n preferences,\n plugins,\n pluginManifests: manifests,\n metadata,\n sections,\n changeCount,\n };\n}\n\nfunction metadataRank(path: string): number {\n if (path === \"name\") return 0;\n if (path === \"description\") return 1;\n return 2;\n}\n"],"mappings":";;;;AAKA,MAAa,uBAAuB;CAClC;EACE,IAAI;EACJ,MAAM;EACN,UAAU;CACZ;CACA;EACE,IAAI;EACJ,MAAM;EACN,UAAU;CACZ;CACA;EACE,IAAI;EACJ,MAAM;EACN,UAAU;CACZ;CACA;EACE,IAAI;EACJ,MAAM;EACN,UAAU;CACZ;CACA;EACE,IAAI;EACJ,MAAM;EACN,UAAU;CACZ;CACA;EACE,IAAI;EACJ,MAAM;EACN,UAAU;CACZ;AACF;;;;;;;;AASA,MAAa,qBAAqB;CAChC;EAAE,IAAI;EAAmB,MAAM;EAAS,QAAQ;CAAQ;CACxD;EAAE,IAAI;EAAkB,MAAM;EAAQ,QAAQ;CAAO;CACrD;EAAE,IAAI;EAAmB,MAAM;EAAS,QAAQ;CAAQ;CACxD;EAAE,IAAI;EAAuB,MAAM;EAAa,QAAQ;CAAY;CACpE;EAAE,IAAI;EAAmB,MAAM;EAAS,QAAQ;CAAQ;AAC1D;AAEA,MAAa,kBAAkB;AAE/B,MAAa,gBAAgB;AAE7B,MAAa,kBAAkB;;;;;;AAO/B,MAAa,cAAc;CACzB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;AAmJA,MAAa,sBAAsB;CACjC,OAAO,CAAC;CACR,QAAQ,CAAC,GAAG,CAAC;CACb,QAAQ,CAAC,GAAG,CAAC;CACb,YAAY;EAAC;EAAG;EAAG;EAAG;CAAC;CACvB,aAAa,CAAC,GAAG,CAAC;AACpB;;;;;;;;;;;;;;;;;;;;;;;;AA2LA,MAAa,cAAc;CAAC;CAAU;CAAY;CAAU;CAAW;AAAK;;;;;;;;AAgB5E,MAAa,qBAAgC;AAmQ7C,MAAa,sBAAkC;CAC7C,SAAS;CACT,SAAS;CACT,WAAW;CACX,aAAa;CACb,aAAa;CACb,iBAAiB;CACjB,eAAe;CACf,YAAY,CAAC;CACb,YAAY;CACZ,aAAa;CACb,cAAc;CACd,WAAW;CACX,eAAe;CACf,eAAe;CACf,UAAU;CACV,QAAQ;EACN,SAAS;EACT,OAAO;EACP,YAAY;EACZ,WAAW;EACX,MAAM;EACN,OAAO;EACP,WAAW;EACX,WAAW;EACX,SAAS;EACT,SAAS;EACT,cAAc;EACd,QAAQ;EACR,SAAS;EACT,SAAS;EACT,UAAU;EACV,UAAU;EACV,WAAW;EACX,qBAAqB;EACrB,gBAAgB;EAChB,cAAc;EACd,QAAQ;EACR,gBAAgB;EAChB,iBAAiB;EACjB,mBAAmB;EACnB,sBAAsB;EACtB,oBAAoB;CACtB;CACA,kBAAkB;CAClB,gBAAgB;CAChB,kBAAkB;CAClB,yBAAyB;CACzB,sBAAsB;CACtB,eAAe;CACf,uBAAuB;CACvB,oBAAoB;CACpB,sBAAsB;CACtB,+BAA+B;CAC/B,0BAA0B;CAC1B,2BAA2B;CAC3B,yBAAyB;CACzB,oBAAoB;CACpB,0BAA0B;CAC1B,mBAAmB;CACnB,iBAAiB;CACjB,qBAAqB;CACrB,uBAAuB;CACvB,sBAAsB;CACtB,iCAAiC;CACjC,kBAAkB,CAChB;EAAE,OAAO;EAAG,OAAO;CAAU,GAC7B;EAAE,OAAO;EAAG,OAAO;CAAU,CAC/B;CACA,uBAAuB;CACvB,aAAa,CAAC;CACd,yBAAyB;CACzB,0BAA0B;CAC1B,0BAA0B;CAC1B,0BAA0B;CAC1B,2BAA2B;CAC3B,2BAA2B;CAC3B,aAAa;CACb,kBAAkB;CAClB,gBAAgB;CAChB,eAAe;CACf,aAAa;CACb,aAAa;CACb,YAAY;CACZ,WAAW;CACX,oBAAoB;CACpB,aAAa;CACb,eAAe,CAAC;CAChB,iBAAiB;CACjB,aAAa;CACb,qBAAqB;CACrB,gBAAgB;CAChB,kBAAkB;CAClB,eAAe;CACf,eAAe;CACf,kBAAkB;CAClB,kBAAkB;CAClB,uBAAuB;CACvB,eAAe;CACf,gBAAgB;CAChB,qBAAqB;CACrB,gBAAgB;CAChB,qBAAqB;CACrB,oBAAoB;CACpB,uBAAuB;CACvB,mBAAmB;CACnB,iCAAiC;CACjC,iCAAiC;CACjC,4BAA4B;CAC5B,8BAA8B;CAC9B,8BAA8B;CAC9B,0BAA0B;CAC1B,+BAA+B;CAC/B,+BAA+B;CAC/B,+BAA+B;CAC/B,+BAA+B;CAC/B,gCAAgC;CAChC,gCAAgC;CAChC,qBAAqB;CACrB,qBAAqB;CACrB,kBAAkB;CAClB,gBAAgB;CAChB,iBAAiB;CACjB,WAAW;AACb;;;;;;AAOA,SAAgB,WAAuC,OAAmB,KAAuB;CAC/F,OAAO,MAAM,QAAQ,oBAAoB;AAC3C;;;;;;;;AASA,MAAa,iCAAiC;;;;;;;;;AAU9C,SAAgB,wBACd,MACS;CACT,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,kBAAkB,QAAQ,OAAO,KAAK,iBAAiB,UACzD,OAAO,KAAK,eAAe;CAE7B,IAAI,cAAc,QAAQ,MAAM,QAAQ,KAAK,QAAQ,GACnD,OAAO,KAAK,SAAS,SAAS;CAEhC,OAAO;AACT;;;;;;;;AAqsBA,MAAa,wBAAwB;;;;;;;;;AAUrC,MAAa,2BAA2B;;;;;;AAOxC,SAAgB,mBACd,OACS;CACT,OACE,OAAO,SAAS,kBAChB,MAAM,SAAS,eAAe,kBAC9B,MAAM,SAAS,sBAAsB;AAEzC;;;;;;AAkDA,MAAa,2BAA4C;;AA2BzD,MAAa,0BAAyC;CACpD,MAAM;CACN,MAAM;CACN,UAAU;AACZ;;AAGA,MAAa,mBAAmB;AAyShC,MAAa,8BAAkD;CAC7D,KAAK;EACH,gBAAgB;EAChB,QAAQ;GAAC;GAAM;GAAK;GAAK;EAAE;EAC3B,SAAS;EACT,SAAS;EACT,UAAU;EACV,mBAAmB;EACnB,YAAY;EACZ,aAAa;EACb,WAAW;EAGX,sBAAsB;EACtB,gBAAgB;EAChB,kBAAkB;EAClB,iBAAiB;EACjB,gBAAgB;EAChB,eAAe;EAIf,eAAe;CACjB;CACA,sBAAsB,CAAC;CACvB,WAAW;EACT,YAAY;EACZ,SAAS,CAAC;CACZ;AACF;AA4FA,MAAa,wBAAsC,OAAO,OAAO;CAC/D,OAAO;CACP,cAAc;CACd,OAAO,OAAO,OAAO,CAAC,CAAa;CACnC,WAAW,OAAO,OAAO,CAAC,CAAuC;AAInE,CAAC;AA+FD,MAAa,oBAA8B;CACzC,OAAO;CACP,UAAU;CACV,QAAQ;CACR,QAAQ;CACR,OAAO;CACP,aAAa;CACb,aAAa;CACb,OAAO;CACP,eAAe;CACf,gBAAgB;CAChB,YAAY;CACZ,UAAU;CACV,UAAU,CAAC;AACb;;AA+IA,MAAa,yBAAyB;;AAsDtC,MAAa,wBAAwB;AACrC,MAAa,wBAAwB;AACrC,MAAa,4BAA4B;;;;;;;;;;;;;;AAiYzC,MAAa,4BAA4B;AACzC,MAAa,mCAAmC;AAYhD,MAAM,gBAAgB;AACtB,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;AAoBtB,SAAgB,+BACd,SACA,cAAc,KACmB;CACjC,IAAI,CAAC,SAAS,UAAU,QAAQ,OAAO;CACvC,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,UAAU;CACd,IAAI,YAAY;CAEhB,MAAM,SAAS,WAA0B;EACvC,IAAI,WAAW,eAAe,CAAC,MAAM,QAAQ,MAAM,GAAG;EACtD,IAAI,OAAO,OAAO,OAAO,YAAY,OAAO,OAAO,OAAO,UAAU;GAClE,MAAM,IAAI,KAAK,IAAI,OAAO,EAAE;GAC5B,MAAM,IAAI,KAAK,IAAI,OAAO,EAAE;GAC5B,WAAW;GAIX,IAAI,OAAO,SAAS,CAAC,KAAK,IAAI,SAAS,UAAU;GACjD,IAAI,OAAO,SAAS,CAAC,KAAK,IAAI,SAAS,UAAU;GAGjD,IAAI,OAAO,SAAS,CAAC,KAAK,OAAO,SAAS,CAAC,MAAM,IAAI,iBAAiB,IAAI,gBACxE,YAAY;GAEd;EACF;EACA,KAAK,MAAM,QAAQ,QAAQ;GACzB,IAAI,WAAW,aAAa;GAC5B,MAAM,IAAI;EACZ;CACF;CAEA,KAAK,MAAM,WAAW,QAAQ,UAAU;EACtC,IAAI,WAAW,aAAa;EAC5B,MAAM,WAAW,SAAS;EAM1B,IAAI,UAAU,gBAAgB,KAAA,GAAW,MAAM,SAAS,WAAW;OAC9D,IAAI,MAAM,QAAQ,UAAU,UAAU,GACzC,KAAK,MAAM,UAAU,SAAS,YAAY;GACxC,IAAI,WAAW,aAAa;GAC5B,IAAI,QAAQ,gBAAgB,KAAA,GAAW,MAAM,OAAO,WAAW;EACjE;CAEJ;CAEA,OAAO,YAAY;EAAE;EAAS;EAAS;CAAQ,IAAI;AACrD;;AC5vFA,MAAa,mCAAmC;AAEhD,MAAM,mBAAmB;AACzB,MAAM,sBAAsB;AAE5B,MAAM,YAAY,OAAe,GAAG,mCAAmC;AAMvE,MAAM,kBACJ;AACF,MAAM,qBAAqB;AAC3B,MAAM,kBAAkB;AAExB,MAAM,cAAc,YAClB,4DAA4D,QAAQ;;;;;;;;;AAUtE,MAAa,iBAA6C;CACxD;EACE,IAAI;EACJ,QAAQ;EACR,MAAM;EACN,UAAU;EACV,UAAU,SAAS,aAAa;EAChC,SAAS;EACT,SAAS;EACT,aAAa;EACb,OAAO;CACT;CACA;EACE,IAAI;EACJ,QAAQ;EACR,MAAM;EACN,UAAU;EACV,UAAU,SAAS,gBAAgB;EACnC,SAAS;EACT,SAAS;EACT,aAAa;EACb,OAAO;CACT;CACA;EACE,IAAI;EACJ,QAAQ;EACR,MAAM;EACN,UAAU;EACV,UAAU,SAAS,aAAa;EAChC,SAAS;EACT,gBAAgB;EAChB,SAAS;EACT,aAAa;EACb,OAAO;CACT;CACA;EACE,IAAI;EACJ,QAAQ;EACR,MAAM;EACN,UAAU;EACV,UAAU,SAAS,gBAAgB;EACnC,SAAS,WAAW,CAAC;EACrB,QAAQ;EACR,SAAS;EACT,aAAa;EACb,OAAO;CACT;CACA;EACE,IAAI;EACJ,QAAQ;EACR,MAAM;EACN,UAAU;EACV,UAAU,SAAS,cAAc;EACjC,SAAS,WAAW,CAAC;EACrB,QAAQ;EACR,SAAS;EACT,aAAa;EACb,OAAO;CACT;AACF;;AAGA,MAAa,oBAAgD;;;;;;;AAQ7D,MAAa,0BAGP,CAAC;CAAE,IAAI;CAAS,UAAU;AAAe,CAAC;;AAGhD,SAAgB,6BACd,UAC6B;CAC7B,IAAI,CAAC,UAAU,OAAO,KAAA;CACtB,OAAO,kBAAkB,MAAM,YAAY,QAAQ,aAAa,QAAQ;AAC1E;;AAGA,SAAgB,uBAAuB,IAAqD;CAC1F,IAAI,CAAC,IAAI,OAAO,KAAA;CAChB,OAAO,kBAAkB,MAAM,YAAY,QAAQ,OAAO,EAAE;AAC9D;;AAGA,SAAgB,0BAA0B,UAAuC;CAC/E,OAAO,QAAQ,UAAU,WAAW,gCAAgC,CAAC;AACvE;;;;;;;;;;;;;;;;AA4CA,SAAgB,oBACd,UACA,QACA,kBACA,UAAU,OACkD;CAC5D,IAAI,CAAC,6BAA6B,QAAQ,GAAG,OAAO,EAAE,SAAS;CAC/D,IAAI,WAAW,SAAS,OAAO;EAAE,UAAU;EAAkB,MAAM;CAAU;CAC7E,IAAI,WAAW,YAAY,CAAC,SAAS,OAAO;EAAE,UAAU;EAAkB,MAAM;CAAS;CACzF,OAAO,EAAE,SAAS;AACpB;;;;;;;;;;;;;;;;;;;;;;;;;AC3NA,MAAa,kBAAkB;CAC7B;EAAE,IAAI;EAAW,MAAM;EAAmB,MAAM;CAAqB;CACrE;EAAE,IAAI;EAAiB,MAAM;EAAoB,MAAM;CAAqB;CAC5E;EAAE,IAAI;EAAe,MAAM;EAAoB,MAAM;EAAkB,SAAS;CAAE;CAClF;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,SAAS;CACX;CACA;EAAE,IAAI;EAAc,MAAM;EAAmB,MAAM;EAAiB,SAAS;CAAE;CAC/E;EAAE,IAAI;EAAc,MAAM;EAAc,MAAM;EAAkB,SAAS;CAAK;CAC9E;EAAE,IAAI;EAAe,MAAM;EAAe,MAAM;EAAkB,SAAS;CAAK;CAChF;EAAE,IAAI;EAAkB,MAAM;EAAkB,MAAM;EAAoB,SAAS;CAAK;CACxF;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,SAAS;CACX;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,SAAS;CACX;CACA;EAAE,IAAI;EAAgB,MAAM;EAAuB,MAAM;EAAqB,SAAS;CAAQ;CAC/F;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,SAAS;CACX;CACA;EAAE,IAAI;EAAgB,MAAM;EAAqB,MAAM;EAAmB,SAAS;CAAQ;CAC3F;EAAE,IAAI;EAAe,MAAM;EAAoB,MAAM;EAAkB,SAAS;CAAQ;CACxF;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,UAAU;EACV,SAAS;CACX;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,UAAU;EACV,SAAS;CACX;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,UAAU;EACV,SAAS;CACX;CACA;EAAE,IAAI;EAAO,MAAM;EAAiB,MAAM;EAAqB,UAAU;CAAQ;AACnF;AAIA,SAAgB,uBAAuB,OAAiC;CACtE,OAAO,gBAAgB,MAAM,UAAU,MAAM,OAAO,KAAK,CAAC,EAAE,MAAM;AACpE;;;;;;;;;;;AAYA,SAAgB,uBAAuB,OAAgB,aAAuC;CAC5F,MAAM,UAAU;CAChB,MAAM,YAAY,UAAU,cAAc,gBAAgB;CAC1D,MAAM,QAAQ,gBAAgB,MAAM,UAAU,MAAM,OAAO,SAAS;CACpE,OAAO,UAAU,EAAE,aAAa,UAAU,eAAe,MAAM,KAAK;AACtE;AA2DA,MAAM,aACJ;;AAEF,MAAM,aAAa;AAEnB,MAAM,YAAY,OAAe,cAC/B,wCAAwC,MAAM,yBAAyB;;AAGzE,MAAM,UAA0B;CAC9B,UAAU;CACV,aAAa;CACb,cAAc;AAChB;;AAGA,MAAM,QAAwB;CAC5B,UAAU,SAAS,sBAAsB,KAAK;CAC9C,aAAa,mBAAmB,WAAW,KAAK;CAChD,cAAc;AAChB;;;;;;;AAQA,MAAM,OAAuB;CAC3B,UAAU,SAAS,oBAAoB,KAAK;CAC5C,aAAa,2CAA2C;CAExD,cAAc;AAChB;;AAGA,MAAM,wBAAwD;CAC5D,SAAS;CACT,QAAQ;CACR,UAAU;CACV,MAAM;CACN,OAAO;AACT;;AAGA,MAAM,sBAAsD;CAC1D,OAAO;CACP,OAAO;CACP,WAAW;CACX,MAAM;CACN,OAAO;AACT;AAEA,SAAS,UAAU,UAAgD;CACjE,OAAO;EACL,MAAM;EACN,UAAU,SAAS;EACnB,aAAa,SAAS;EACtB,cAAc,SAAS;CACzB;AACF;;;;;;;;;;;;AAaA,SAAgB,kBAAkB,GAAyB,GAAkC;CAC3F,IAAI,EAAE,SAAS,EAAE,MAAM,OAAO;CAC9B,IAAI,EAAE,SAAS,YAAY,EAAE,SAAS,UAAU,OAAO,EAAE,QAAQ,EAAE;CACnE,IAAI,EAAE,SAAS,SAAS,EAAE,SAAS,OAAO,OAAO,EAAE,YAAY,EAAE;CAEjE,IAAI,EAAE,SAAS,SAAS,EAAE,SAAS,OAAO,OAAO;CACjD,OACE,EAAE,aAAa,EAAE,YACjB,EAAE,gBAAgB,EAAE,eACpB,EAAE,iBAAiB,EAAE,gBACrB,EAAE,WAAW,EAAE,UACf,EAAE,oBAAoB,EAAE;AAE5B;;;;;;;AAQA,SAAS,WAAW,OAAmC;CACrD,IAAI;EACF,OAAO,mBAAmB,KAAK;CACjC,QAAQ;EACN;CACF;AACF;;;;;;;;AASA,SAAS,oBAAoB,UAA8C;CACzE,IAAI;CACJ,IAAI;EACF,SAAS,IAAI,IAAI,QAAQ;CAC3B,QAAQ;EACN;CACF;CACA,IAAI,OAAO,aAAa,yBAAyB;EAE/C,MAAM,OAAO,OAAO,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,GAAG,EAAE;EAC7D,OAAO,OAAO,sBAAsB,QAAQ,KAAA;CAC9C;CACA,IAAI,OAAO,aAAa,qBAAqB;EAE3C,MAAM,WAAW,OAAO,SAAS,MAAM,GAAG,CAAC,CAAC,OAAO,OAAO;EAC1D,MAAM,SAAS,SAAS,OAAO,WAAW,SAAS,KAAK,KAAA;EACxD,MAAM,UAAU,SAAS,WAAW,MAAM,IAAI,KAAA;EAC9C,OAAO,UAAU,oBAAoB,WAAW,KAAA;CAClD;AAEF;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,uBACd,UACA,gBAAiC,WACX;CACtB,MAAM,QAAQ,gBAAgB,MAAM,UAAU,MAAM,OAAO,aAAa;CACxE,IAAI,SAAS,aAAa,OACxB,OAAO;EACL,MAAM;EACN,KAAK,0DAA0D,MAAM,QAAQ;CAC/E;CAEF,IAAI,SAAS,aAAa,OAAO,OAAO;EAAE,MAAM;EAAO,SAAS,MAAM;CAAQ;CAC9E,IAAI,kBAAkB,iBAAiB,OAAO,EAAE,MAAM,gBAAgB;CACtE,IAAI,kBAAkB,OAAO,OAAO,UAAU,OAAO;CACrD,IAAI,aAAA,IAA4B,OAAO,EAAE,MAAM,OAAO;CACtD,MAAM,MAAM,YAAA;CAEZ,MAAM,YAAY,8BAA8B,GAAG;CACnD,IAAI,WACF,OAAO;EACL,MAAM;EACN,UAAU,UAAU;EACpB,aAAa,UAAU;EACvB,cAAc,UAAU;EACxB,GAAI,UAAU,SAAS,EAAE,QAAQ,UAAU,OAAO,IAAI,CAAC;CACzD;CAGF,MAAM,WAAW,6BAA6B,GAAG;CACjD,IAAI,UACF,OAAO;EACL,MAAM;EACN,UAAU,SAAS;EACnB,aAAa,SAAS;EACtB,cAAc,SAAS;EACvB,GAAI,SAAS,SAAS,EAAE,QAAQ,SAAS,OAAO,IAAI,CAAC;EACrD,GAAI,SAAS,iBAAiB,EAAE,iBAAiB,SAAS,eAAe,IAAI,CAAC;CAChF;CAQF,MAAM,WAAW,oBAAoB,IAAI,WAAW,aAAa,IAAI,kBAAkB,GAAG;CAC1F,OAAO,WAAW,UAAU,QAAQ,IAAI,EAAE,MAAM,UAAU;AAC5D;;;ACrVA,MAAMA,uCAA0C,IAAI,IAAI;CAAC;CAAM;CAAQ;CAAQ;AAAM,CAAC;;AAGtF,MAAa,4BAA+D;CAC1E;EAAE,IAAI;EAAY,MAAM;EAAO,MAAM;CAAyB;CAC9D;EAAE,IAAI;EAAc,MAAM;EAAM,MAAM;CAAc;CACpD;EAAE,IAAI;EAAY,MAAM;EAAO,MAAM;CAAW;CAChD;EAAE,IAAI;EAAgB,MAAM;EAAO,MAAM;CAAgB;CACzD;EAAE,IAAI;EAAa,MAAM;EAAO,MAAM;CAAY;CAClD;EAAE,IAAI;EAAe,MAAM;EAAM,MAAM;CAAwB;CAC/D;EAAE,IAAI;EAAc,MAAM;EAAM,MAAM;CAA6B;CACnE;EAAE,IAAI;EAAc,MAAM;EAAM,MAAM;CAAgC;AACxE;;;;;;;;;;;AAYA,SAAgB,oBAAoB,OAAoC;CAEtE,MAAM,OACJ,OAAO,UAAU,WAAY,cAAc,KAAK,KAAK,IAAI,OAAO,KAAK,IAAI,KAAA,IAAa;CACxF,IAAI,OAAO,SAAS,YAAY,CAAC,OAAO,cAAc,IAAI,KAAK,QAAQ,GAAG,OAAO,KAAA;CACjF,OAAOA,qBAAmB,IAAI,IAAI,IAAI,KAAA,IAAY;AACpD;;;;;;;ACgCA,MAAa,uBAAuB;;AAGpC,MAAM,qCAA0C,IAAI,IAAI;CAAC;CAAM;CAAQ;CAAQ;AAAM,CAAC;AAEtF,MAAM,WAAW;AACjB,MAAM,mBAAmB;AACzB,MAAM,mBACJ;;;;;;AAOF,SAAS,eAAe,MAAc,KAAa,OAAe,QAAwB;CACxF,OACE,GAAG,OAAO,KAAK,SAAS,GAAG,IAAI,MAAM,IAAI,+CACjC,IAAI,kBAAkB,MAAM,+BAA+B,OAAA;AAGvE;;AAGA,SAAS,YAAY,SAAiB,KAAa,OAAe,SAAgC;CAChG,OAAO;EACL,OAAO,eAAe,GAAG,SAAS,OAAO,WAAW,KAAK,OAAO,WAAW;EAC3E,aAAa;EACb;CACF;AACF;;AAGA,SAAS,YAAY,MAA6B;CAChD,OAAO;EACL,OAAO,eACL,2CAA2C,KAAK,gBAChD,QAAQ,QACR,sCACA,YACF;EACA,aAAa;EACb,SAAS;CACX;AACF;;AAGA,MAAM,kBAAuC;CAAE,QAAQ,CAAC,MAAM,GAAG;CAAG,eAAe;AAAI;;AAGvF,SAAS,UAAU,WAAwC;CACzD,OAAO;EAAE,QAAQ,CAAC,CAAC,WAAW,SAAS;EAAG,eAAe,IAAI;CAAU;AACzE;AAEA,MAAM,UAAU,WAAmB,MAAM;AACzC,MAAM,WAAW,WAAmB,iBAAiB,OAAO,MAAM,EAAE;AACpE,MAAM,sBAAsB,OAAgB,WAC1C,sBAAsB,QAAQ,KAAK,IAAI,+BAA+B,OAAO,MAAM,EAAE;AAEvF,MAAM,cAAc;AACpB,MAAM,cAAc;;;;;;;;;;;;;;;AAgBpB,SAAS,YACP,MACA,MACA,UACA,QACA,SACA,OACc;CACd,OAAO;EACL,IAAI,GAAG,KAAK;EACZ;EACA,MAAM,YAAY;EAElB,QAAQ;EACR,YAAY,QAAQ,MAAM;EAC1B,aAAa;EACb,eAAe;EACf,QAAQ;GAAC;GAAM;GAAK;GAAK;EAAE;EAI3B,SAAS,YAAY,SAAS,aAAa,OAAO,CAAC;CACrD;AACF;;;;;;;;;AAUA,MAAa,kBAA2C;CACtD;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,YACE;EACF,aAAa;EAEb,eAAe,UAAU,OAAO;EAChC,QAAQ;GAAC;GAAM;GAAI;GAAK;EAAE;EAC1B,SAAS,YAAY,IAAI;CAC3B;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,YACE;EACF,aAAa;EACb,eAAe,UAAU,OAAO;EAChC,QAAQ;GAAC;GAAM;GAAK;GAAK;EAAG;EAC5B,SAAS,YAAY,IAAI;CAC3B;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,YAAY;EACZ,aAAa;EACb,eAAe;EACf,QAAQ;GAAC;GAAM;GAAK;GAAK;EAAE;EAC3B,SAAS,YAAY,IAAI;CAC3B;CACA,YACE,QACA,2BACA,OACA,aACA,iCACA,UACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,YAAY,mBAAmB,MAAM,WAAW;EAChD,aAAa;EAEb,eAAe,UAAU,KAAM;EAC/B,QAAQ;GAAC;GAAM;GAAI;GAAK;EAAE;EAC1B,SAAS,YAAY,8BAA8B,cAAc,iBAAiB,CAAC;CACrF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,YAAY,mBAAmB,OAAO,WAAW;EACjD,aAAa;EACb,eAAe,UAAU,KAAM;EAC/B,QAAQ;GAAC;GAAM;GAAK;GAAK;EAAG;EAC5B,SAAS,YAAY,8BAA8B,cAAc,iBAAiB,CAAC;CACrF;CACA,YACE,QACA,2BACA,OACA,aACA,gCACA,YACF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,YAAY,mBAAmB,MAAM,WAAW;EAChD,aAAa;EAEb,eAAe,UAAU,OAAO;EAChC,QAAQ;GAAC;GAAM;GAAI;GAAK;EAAE;EAC1B,SAAS,YAAY,6BAA6B,cAAc,oBAAoB,CAAC;CACvF;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,QAAQ;EACR,YAAY,mBAAmB,OAAO,WAAW;EACjD,aAAa;EACb,eAAe,UAAU,OAAO;EAChC,QAAQ;GAAC;GAAM;GAAK;GAAK;EAAG;EAC5B,SAAS,YAAY,6BAA6B,cAAc,oBAAoB,CAAC;CACvF;CACA,YACE,WACA,8BACA,OACA,SACA,sCACA,WACF;CACA,YACE,SACA,4BACA,OACA,SACA,kCACA,UACF;CACA,YACE,MACA,yBACA,OACA,SACA,iCACA,WACF;CACA,YACE,UACA,6BACA,OACA,SACA,qCACA,iBACF;CACA,YACE,YACA,+BACA,OACA,SACA,uCACA,iBACF;CACA,YACE,YACA,+BACA,OACA,SACA,uCACA,iBACF;CACA,YACE,SACA,4BACA,OACA,SACA,mCACA,6BACF;CACA,YACE,SACA,4BACA,OACA,SACA,kCACA,0BACF;CACA,YACE,UACA,6BACA,OACA,OACA,mCACA,2BACF;AACF;;;;;;;;;;AAWA,SAAgB,gBAAgB,IAAkD;CAChF,OAAO,OAAO,KAAA,IAAY,KAAA,IAAY,gBAAgB,MAAM,WAAW,OAAO,OAAO,EAAE;AACzF;;;;;;;;;;AAWA,SAAgB,oBAAoB,OAA0C;CAC5E,MAAM,QAAQ,sBAAsB,KAAK,SAAS,EAAE;CACpD,IAAI,CAAC,OAAO,OAAO;CACnB,MAAM,OAAO,OAAO,MAAM,EAAE;CAC5B,OAAO,OAAO,cAAc,IAAI,KAAK,OAAO,IAAI,OAAO;AACzD;;;;;;;;;;;AAYA,SAAgB,gBAAgB,OAAoC;CAClE,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,IAAI,gBAAgB,KAAK,GAAG,OAAO;CACnC,MAAM,OAAO,oBAAoB,KAAK;CACtC,IAAI,SAAS,QAAQ,mBAAmB,IAAI,IAAI,GAAG,OAAO,KAAA;CAI1D,MAAM,OAAO,QAAQ;CACrB,OAAO,gBAAgB,MAAM,WAAW,OAAO,SAAS,IAAI,CAAC,EAAE,MAAM;AACvE;;;;;;;;;;;;AAaA,SAAgB,sBAAsB,QAGpC;CACA,IAAI,WAAW,MAAM,OAAO,EAAE,QAAQ,KAAA,EAAU;CAChD,OAAO;EAAE;EAAQ,aAAa,gBAAgB,MAAM,CAAC,EAAE,eAAe;CAAQ;AAChF;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,uBAAuB,UAAkB,MAA6B;CACpF,IAAI,SAAS,SAAS,QAAQ,GAAG,OAAO;CACxC,IAAI,CAAC,SAAS,SAAS,kBAAkB,GAAG,OAAO;CACnD,IAAI,MAAM,SAAS,QAAQ,uBAAuB,QAAQ;CAC1D,MAAM,OAAO,gBAAgB,KAAK,IAAI,CAAC,GAAG;CAI1C,MAAM,YAAY,MAAc,UAAkB;EAChD,MAAM,UAAU,IAAI,OAAO,SAAS,KAAK,SAAS,GAAG;EACrD,MAAM,QAAQ,KAAK,GAAG,IAClB,IAAI,QAAQ,SAAS,KAAK,KAAK,GAAG,OAAO,IACzC,GAAG,MAAM,IAAI,SAAS,GAAG,IAAI,MAAM,MAAM,KAAK,GAAG;CACvD;CACA,MAAM,eAAe,SAAiB;EACpC,MAAM,IAAI,QAAQ,IAAI,OAAO,SAAS,KAAK,aAAa,GAAG,IAAI,GAAG,MAAM,UACtE,QAAQ,OAAO,EACjB;CACF;CACA,IAAI,eAAe,KAAK,GAAG,GAAG;EAE5B,IAAI,CAAC,MAAM,OAAO;EAClB,SAAS,UAAU,IAAI;EACvB,SAAS,WAAW,IAAI;EACxB,OAAO;CACT;CACA,YAAY,KAAK;CACjB,SAAS,OAAO,IAAI;CACpB,SAAS,WAAW,OAAO;CAC3B,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,oBACd,YAIA,YAC4B;CAC5B,IAAI,YAAY,OAAO;CACvB,MAAM,WAAW,KAAa,QAAyC;EACrE,IAAI;GACF,MAAM,CAAC,GAAG,KAAK,WAAW,QAAQ,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC;GAElD,OAAO,OAAO,SAAS,CAAC,KAAK,OAAO,SAAS,CAAC,KAAK,KAAK,IAAI,CAAC,KAAK,OAAO,KAAK,IAAI,CAAC,KAAK,MACpF,CAAC,GAAG,CAAC,IACL;EACN,QAAQ;GACN,OAAO;EACT;CACF;CACA,MAAM,cAAc,OAAyB,KAAa,QAAyB;EACjF,IAAI;EACJ,IAAI;GACF,OAAO,WAAW,QAAQ,KAAK;EACjC,QAAQ;GACN,OAAO;EACT;EACA,IAAI,CAAC,QAAQ,CAAC,OAAO,SAAS,KAAK,EAAE,KAAK,CAAC,OAAO,SAAS,KAAK,EAAE,GAAG,OAAO;EAC5E,MAAM,OAAO,KAAK,MAAQ,KAAK,KAAK,OAAO,MAAO,OAAO,MAAO,GAAG;EACnE,OAAO,KAAK,IAAI,KAAK,KAAK,GAAG,KAAK,QAAQ,QAAQ;CACpD;CAIA,MAAM,OAAO;CACb,MAAM,UAAW,OAAO,KAAK,KAAM;CACnC,MAAM,UAAqD,CAAC;CAC5D,KAAK,IAAI,MAAM,MAAM,OAAO,KAAK,OAAO,GACtC,KAAK,IAAI,MAAM,KAAK,OAAO,IAAI,OAAO,GAAG;EACvC,MAAM,QAAQ,QAAQ,KAAK,GAAG;EAC9B,IAAI,CAAC,SAAS,CAAC,WAAW,OAAO,KAAK,GAAG,GAAG;EAC5C,MAAM,QAAQ,QAAQ,KAAK,MAAM,IAAI;EACrC,MAAM,OAAO,QAAQ,MAAM,MAAM,GAAG;EACpC,IAAI,CAAC,SAAS,CAAC,MAAM;EACrB,MAAM,QAAQ,KAAK,IACjB,KAAK,MAAM,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,MAAM,EAAE,IAAI,SACvD,KAAK,MAAM,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,MAAM,EAAE,KAC9C,UAAU,KAAK,IAAK,MAAM,KAAK,KAAM,GAAG,EAC7C;EACA,IAAI,QAAQ,KAAK,OAAO,SAAS,KAAK,GAAG,QAAQ,KAAK;GAAE,GAAG,MAAM;GAAI,GAAG,MAAM;GAAI;EAAM,CAAC;CAC3F;CAEF,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,MAAM,QAAQ,qBAAqB,KAAK,IAAI,GAAG,QAAQ,KAAK,WAAW,OAAO,KAAK,CAAC;CACpF,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;CACX,KAAK,MAAM,EAAE,GAAG,GAAG,WAAW,SAAS;EACrC,IAAI,QAAQ,OAAO;EACnB,OAAO,KAAK,IAAI,MAAM,CAAC;EACvB,OAAO,KAAK,IAAI,MAAM,CAAC;EACvB,OAAO,KAAK,IAAI,MAAM,CAAC;EACvB,OAAO,KAAK,IAAI,MAAM,CAAC;CACzB;CACA,MAAM,OAAO,KAAK,IAAI,OAAO,MAAM,OAAO,IAAI,IAAI;CAClD,IAAI,EAAE,OAAO,IAAI,OAAO;CACxB,OAAO;EACL,QAAQ,EAAE,OAAO,OAAO,QAAQ,IAAI,OAAO,OAAO,QAAQ,CAAC;EAC3D,eAAe;CACjB;AACF;;;;;;AAOA,MAAM,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;ACxiB3B,SAAgB,eACd,KACyC;CACzC,IAAI,CAAC,KAAK,OAAO;CACjB,IAAI,IAAI,WAAW,KAAK,IAAI,WAAW,GAAG,OAAO;CACjD,IAAI,CAAC,IAAI,OAAO,UAAU,OAAO,SAAS,KAAK,CAAC,GAAG,OAAO;CAC1D,OAAO,IAAI,WAAW,IAAI;EAAC,IAAI;EAAI,IAAI;EAAI,IAAI;EAAI,IAAI;CAAE,IAAI;EAAC,IAAI;EAAI,IAAI;EAAI,IAAI;EAAI,IAAI;CAAE;AAC9F;AAKA,MAAM,4BAAY,IAAI,QAAyB;AAC/C,MAAM,kCAAkB,IAAI,QAAyB;;;;;;;;;;AAWrD,SAAgB,uBACd,SACA,cAAc,OACL;CACT,IAAI,CAAC,SAAS,OAAO;CACrB,MAAM,QAAQ,cAAc,kBAAkB;CAC9C,MAAM,SAAS,MAAM,IAAI,OAAO;CAChC,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,MAAM,SAAS,aAAa,UAAU,aACpC,aAAa,WAAW,aAAa;EACnC,MAAM,IAAI,SAAS;EACnB,OAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,MAAM,eAAe,MAAM;CAC9E,CAAC,CACH;CACA,MAAM,IAAI,SAAS,MAAM;CACzB,OAAO;AACT;;;;;;;;;;;;;AAcA,SAAgB,0BACd,SACA,eACA,QACmB;CAInB,MAAM,eAAe,aAAiC;EACpD,SAAS;EACT,SAAS;GACR,OAAO,SAAS,OAAO,YAAY,OAAO,SAAS,SAAS,EAAE,IAAI,SAAS,KAAK,KAC/E,gBACA;CACJ;CACA,OAAO;EACL,GAAG;EACH,UAAU,QAAQ,SAAS,KAAK,aAAa;GAC3C,GAAG;GACH,UAAU,qBAAqB,QAAQ,UAAU,WAAW;EAC9D,EAAE;CACJ;AACF;AAEA,SAAS,aAAa,SAAkB,WAAqD;CAC3F,QAAQ,QAAQ,MAAhB;EACE,KAAK,qBACH,OAAO,QAAQ,SAAS,MAAM,YAC5B,QAAQ,WAAW,aAAa,QAAQ,UAAU,SAAS,IAAI,KACjE;EACF,KAAK,WACH,OAAO,QAAQ,WAAW,aAAa,QAAQ,UAAU,SAAS,IAAI;EACxE,KAAK,sBACH,OAAO,QAAQ,WAAW,MAAM,aAAa,aAAa,UAAU,SAAS,CAAC;EAChF,SACE,OAAO,UAAU,OAAO;CAC5B;AACF;AAEA,SAAS,aAAa,UAAoB,WAAqD;CAC7F,QAAQ,SAAS,MAAjB;EACE,KAAK,SACH,OAAO,UAAU,SAAS,WAAW;EACvC,KAAK;EACL,KAAK,cACH,OAAO,SAAS,YAAY,KAAK,SAAS;EAC5C,KAAK;EACL,KAAK,WACH,OAAO,SAAS,YAAY,MAAM,SAAS,KAAK,KAAK,SAAS,CAAC;EACjE,KAAK,gBACH,OAAO,SAAS,YAAY,MAAM,YAAY,QAAQ,MAAM,SAAS,KAAK,KAAK,SAAS,CAAC,CAAC;EAC5F,KAAK,sBACH,OAAO,SAAS,WAAW,MAAM,UAAU,aAAa,OAAO,SAAS,CAAC;EAC3E,SACE,OAAO;CACX;AACF;AAEA,SAAS,qBACP,UACA,aACqB;CACrB,IAAI,CAAC,UAAU,OAAO;CACtB,QAAQ,SAAS,MAAjB;EACE,KAAK,SACH,OAAO;GAAE,GAAG;GAAU,aAAa,YAAY,SAAS,WAAW;EAAE;EACvE,KAAK;EACL,KAAK,cACH,OAAO;GAAE,GAAG;GAAU,aAAa,SAAS,YAAY,IAAI,WAAW;EAAE;EAC3E,KAAK;EACL,KAAK,WACH,OAAO;GACL,GAAG;GACH,aAAa,SAAS,YAAY,KAAK,SAAS,KAAK,IAAI,WAAW,CAAC;EACvE;EACF,KAAK,gBACH,OAAO;GACL,GAAG;GACH,aAAa,SAAS,YAAY,KAAK,YACrC,QAAQ,KAAK,SAAS,KAAK,IAAI,WAAW,CAAC,CAC7C;EACF;EACF,KAAK,sBACH,OAAO;GACL,GAAG;GACH,YAAY,SAAS,WAAW,KAC7B,UAAU,qBAAqB,OAAO,WAAW,CACpD;EACF;EACF,SACE,OAAO;CACX;AACF;;;AC7JA,MAAa,qBAA2C;CACtD;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;GAAW;EAAS;CAC3E;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS;CAChE;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS;CAChE;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS;CAChE;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;GAAW;EAAS;CAC3E;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS;CAChE;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS;CAChE;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS;CAChE;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;EAAS;CAChE;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;GAAW;GAAW;EAAS;CAC3E;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ;GAAC;GAAW;GAAW;GAAW;EAAS;CACrD;CACA;EACE,OAAO;EACP,OAAO;EACP,QAAQ,CAAC,WAAW,SAAS;CAC/B;AACF;;;;;;;;AASA,SAAgB,mBAAmB,OAA0B;CAC3D,OAAO,mBAAmB,MAAM,cAAc,UAAU,UAAU,KAAK,KAAK,mBAAmB;AACjG;;;;;;;;;AAUA,SAAgB,sBAAsB,WAAmB,OAAyB;CAChF,OAAO,kBAAkB,mBAAmB,SAAS,CAAC,CAAC,QAAQ,KAAK;AACtE;;;;;;;;;;AAWA,SAAgB,kBAAkB,QAA2B,OAAyB;CACpF,MAAM,UAAU,OAAO,SAAS,IAAI,SAAS,CAAC,SAAS;CACvD,IAAI,SAAS,GAAG,OAAO,CAAC;CACxB,IAAI,UAAU,GAAG,OAAO,CAAC,QAAQ,QAAQ,SAAS,EAAE;CACpD,IAAI,QAAQ,WAAW,GACrB,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,SAAS,QAAQ,EAAE;CAEvD,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,UAAU;EACjD,MAAM,SAAU,SAAS,QAAQ,MAAO,QAAQ,SAAS;EACzD,MAAM,aAAa,KAAK,MAAM,MAAM;EACpC,MAAM,aAAa,KAAK,IAAI,QAAQ,SAAS,GAAG,KAAK,KAAK,MAAM,CAAC;EACjE,MAAM,QAAQ,SAAS;EACvB,OAAO,oBAAoB,QAAQ,aAAa,QAAQ,aAAa,KAAK;CAC5E,CAAC;AACH;;;;;;;;;;AAWA,SAAgB,kBAAkB,OAA8B;CAC9D,IAAI,QAAQ,MAAM,KAAK,CAAC,CAAC,YAAY;CACrC,IAAI,UAAU,IAAI,OAAO;CACzB,IAAI,CAAC,MAAM,WAAW,GAAG,GAAG,QAAQ,IAAI;CACxC,IAAI,iBAAiB,KAAK,KAAK,GAC7B,QAAQ,IAAI,MACT,MAAM,CAAC,CAAC,CACR,MAAM,EAAE,CAAC,CACT,KAAK,YAAY,UAAU,OAAO,CAAC,CACnC,KAAK,EAAE;CAEZ,OAAO,iBAAiB,KAAK,KAAK,IAAI,QAAQ;AAChD;;;;;;;;;;AAWA,SAAgB,kBAAkB,OAAyB;CACzD,OAAO,MACJ,MAAM,SAAS,CAAC,CAChB,IAAI,iBAAiB,CAAC,CACtB,QAAQ,UAA2B,UAAU,IAAI;AACtD;;;;;;;;;AAUA,SAAgB,oBAAoB,MAAc,IAAY,OAAuB;CACnF,MAAM,QAAQ,cAAc,IAAI;CAChC,MAAM,MAAM,cAAc,EAAE;CAC5B,OAAO,SAAS;EACd,GAAG,KAAK,MAAM,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,KAAK;EACjD,GAAG,KAAK,MAAM,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,KAAK;EACjD,GAAG,KAAK,MAAM,MAAM,KAAK,IAAI,IAAI,MAAM,KAAK,KAAK;CACnD,CAAC;AACH;;;;;;;AAQA,SAAgB,cAAc,OAAoD;CAGhF,MAAM,UAAU,OAAO,SAAS,MAAM,MAAM,CAAC,GAAG,EAAE;CAClD,OAAO;EACL,GAAI,WAAW,KAAM;EACrB,GAAI,WAAW,IAAK;EACpB,GAAG,UAAU;CACf;AACF;;;;;;;AAQA,SAAgB,SAAS,OAAoD;CAC3E,OAAO,IAAI;EAAC,MAAM;EAAG,MAAM;EAAG,MAAM;CAAC,CAAC,CACnC,KAAK,YAAY,QAAQ,SAAS,EAAE,CAAC,CAAC,SAAS,GAAG,GAAG,CAAC,CAAC,CACvD,KAAK,EAAE;AACZ;;;;;;;;;AAUA,SAAgB,0BAA0B,KAAa,KAAa,OAAyB;CAC3F,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,UAAU;EACjD,MAAM,QAAQ,UAAU,IAAI,IAAI,SAAS,QAAQ;EACjD,OAAO,OAAO,MAAM,OAAO;CAC7B,CAAC;AACH;;;;;;;;;;;;;AAcA,SAAgB,qBAAqB,QAAkB,OAAyB;CAC9E,IAAI,SAAS,GAAG,OAAO,CAAC;CACxB,MAAM,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAG/C,IAAI,OAAO,WAAW,GAAG,OAAO,CAAC;CACjC,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,UAAU;EACjD,MAAM,WAAW,UAAU,IAAI,IAAK,SAAS,QAAQ,MAAO,OAAO,SAAS;EAC5E,OAAO,mBAAmB,QAAQ,QAAQ;CAC5C,CAAC;AACH;;AAGA,SAAS,mBAAmB,QAAkB,UAA0B;CACtE,MAAM,aAAa,KAAK,MAAM,QAAQ;CACtC,MAAM,aAAa,KAAK,IAAI,OAAO,SAAS,GAAG,KAAK,KAAK,QAAQ,CAAC;CAClE,MAAM,QAAQ,WAAW;CACzB,OAAO,OAAO,eAAe,OAAO,cAAc,OAAO,eAAe;AAC1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAuCA,SAAgB,2BACd,QACA,OACA,QACU;CACV,IAAI,SAAS,KAAK,OAAO,WAAW,GAAG,OAAO,CAAC;CAC/C,IAAI,WAAW,kBAAkB,OAAO,gBAAgB,mBAAmB,QAAQ,KAAK,CAAC;CACzF,MAAM,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAC/C,IAAI,WAAW,YACb,OAAO,gBACL,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,UAChC,mBAAmB,QAAS,QAAQ,SAAU,OAAO,SAAS,EAAE,CAClE,CACF;CAEF,IAAI,WAAW,sBACb,OAAO,gBAAgB,6BAA6B,QAAQ,KAAK,CAAC;CAEpE,IAAI,WAAW,sBAAsB;EACnC,MAAM,SAAS,6BAA6B,QAAQ,KAAK;EACzD,IAAI,QAAQ,OAAO,gBAAgB,MAAM;CAC3C;CACA,OAAO,gBAAgB,yBAAyB,QAAQ,KAAK,CAAC;AAChE;;AAGA,SAAS,yBAAyB,QAAkB,OAAyB;CAC3E,MAAM,MAAM,OAAO;CACnB,MAAM,MAAM,OAAO,OAAO,SAAS;CACnC,OAAO,MAAM,KAAK,EAAE,QAAQ,MAAM,IAAI,GAAG,UAAU,OAAQ,MAAM,OAAO,QAAS,KAAK;AACxF;;;;;;;;;;;;AAaA,SAAS,6BAA6B,QAAkB,OAAyB;CAC/E,MAAM,MAAM,OAAO;CACnB,MAAM,MAAM,OAAO,OAAO,SAAS;CACnC,MAAM,OAAO,OAAO,QAAQ,KAAK,UAAU,MAAM,OAAO,CAAC,IAAI,OAAO;CACpE,MAAM,WACJ,OAAO,QAAQ,KAAK,UAAU,OAAO,QAAQ,SAAS,QAAQ,OAAO,CAAC,IAAI,OAAO;CACnF,MAAM,YAAY,KAAK,KAAK,QAAQ;CACpC,IAAI,EAAE,YAAY,IAAI,OAAO,CAAC,GAAG;CACjC,MAAM,WAAqB,CAAC;CAC5B,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG;EAC7C,MAAM,QAAQ,QAAQ,QAAQ,QAAQ,KAAK;EAC3C,IAAI,QAAQ,OAAO,SAAS,KAAK,SAAS,KAAK,KAAK;CACtD;CACA,OAAO,CAAC,KAAK,GAAG,QAAQ;AAC1B;;;;;;;;;;;;AAaA,SAAS,6BAA6B,QAAkB,OAAgC;CACtF,MAAM,MAAM,OAAO,OAAO,SAAS;CACnC,MAAM,SAAS,OAAO,MAAM,UAAU,QAAQ,CAAC;CAC/C,IAAI,WAAW,KAAA,GAAW,OAAO;CAGjC,MAAM,YAAY,KAAK,IAAI,MAAM;CACjC,MAAM,WAAW,KAAK,IAAI,GAAG,IAAI,aAAa;CAC9C,OAAO,CACL,OAAO,IACP,GAAG,MAAM,KAAK,EAAE,QAAQ,QAAQ,EAAE,IAAI,GAAG,UAAU,KAAK,IAAI,YAAY,WAAW,QAAQ,EAAE,CAAC,CAChG;AACF;;AAGA,SAAS,gBAAgB,QAA4B;CACnD,MAAM,MAAgB,CAAC;CACvB,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,CAAC,OAAO,SAAS,KAAK,GAAG;EAC7B,IAAI,IAAI,SAAS,KAAK,SAAS,IAAI,IAAI,SAAS,IAAI;EACpD,IAAI,KAAK,KAAK;CAChB;CACA,OAAO;AACT;;AAGA,MAAM,4BAA4B;;AAGlC,SAAS,uBAAuB,QAAkB,YAA8B;CAC9E,IAAI,OAAO,UAAU,YAAY,OAAO;CACxC,MAAM,SAAmB,CAAC;CAC1B,MAAM,QAAQ,OAAO,SAAS,MAAM,aAAa;CACjD,KAAK,IAAI,QAAQ,GAAG,QAAQ,YAAY,SAAS,GAC/C,OAAO,KAAK,OAAO,KAAK,MAAM,QAAQ,IAAI,EAAE;CAE9C,OAAO;AACT;;;;;;AAOA,SAAS,mBAAmB,QAAkB,OAAyB;CAGrE,MAAM,SAAS,uBAFA,MAAM,KAAK,IAAI,IAAI,MAAM,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAEnB,GAAG,yBAAyB;CACvE,IAAI,OAAO,UAAU,OAAO,OAAO;CAEnC,MAAM,mBAAmB,MAAM,KAAK,EAAE,QAAQ,OAAO,SAAS,EAAE,SAC9D,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CACzB;CACA,MAAM,uBAAuB,MAAM,KAAK,EAAE,QAAQ,OAAO,SAAS,EAAE,SAClE,MAAM,QAAQ,CAAC,CAAC,CAAC,KAAK,OAAO,iBAAiB,CAChD;CAEA,KAAK,IAAI,aAAa,GAAG,cAAc,OAAO,cAAc,GAAG;EAC7D,iBAAiB,EAAE,CAAC,cAAc;EAClC,qBAAqB,EAAE,CAAC,cAAc;CACxC;CAEA,KAAK,IAAI,aAAa,GAAG,cAAc,OAAO,QAAQ,cAAc,GAAG;EACrE,IAAI,MAAM;EACV,IAAI,aAAa;EACjB,IAAI,SAAS;EAEb,KAAK,IAAI,aAAa,GAAG,cAAc,YAAY,cAAc,GAAG;GAClE,MAAM,eAAe,aAAa,aAAa;GAC/C,MAAM,QAAQ,OAAO,eAAe;GACpC,UAAU;GACV,OAAO;GACP,cAAc,QAAQ;GACtB,MAAM,WAAW,aAAc,MAAM,MAAO;GAC5C,MAAM,gBAAgB,eAAe;GACrC,IAAI,kBAAkB,GAAG;GAEzB,KAAK,IAAI,aAAa,GAAG,cAAc,OAAO,cAAc,GAAG;IAC7D,MAAM,YAAY,WAAW,qBAAqB,cAAc,CAAC,aAAa;IAC9E,IAAI,qBAAqB,WAAW,CAAC,eAAe,WAAW;KAC7D,iBAAiB,WAAW,CAAC,cAAc;KAC3C,qBAAqB,WAAW,CAAC,cAAc;IACjD;GACF;EACF;EAEA,iBAAiB,WAAW,CAAC,KAAK;EAClC,qBAAqB,WAAW,CAAC,KAAK,aAAc,MAAM,MAAO,KAAK,IAAI,GAAG,MAAM;CACrF;CAKA,MAAM,SAAS,MAAM,KAAK,CAAC,CAAC,KAAK,OAAO,EAAE;CAC1C,IAAI,aAAa,OAAO;CACxB,KAAK,IAAI,aAAa,OAAO,cAAc,GAAG,cAAc,GAAG;EAC7D,MAAM,kBAAkB,KAAK,IAAI,GAAG,iBAAiB,WAAW,CAAC,WAAW;EAC5E,OAAO,aAAa,KAAK,OAAO,kBAAkB;EAClD,aAAa,kBAAkB;CACjC;CACA,OAAO,KAAK,OAAO;CACnB,OAAO;AACT;;;;;;;;;;;;;;;;;;;;ACneA,SAAgB,iBAAiB,KAAqB;CACpD,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU,OAAO;CAC5C,OAAO,IAAI,QAAQ,SAAS,IAAI,CAAC,CAAC,QAAQ,QAAQ,IAAG,CAAC,CAAC,QAAQ,QAAQ,GAAG;AAC5E;;;;;;;;;;;AAiBA,SAAgB,uBACd,SACA,YAAY,GACZ,WAAW,OACW;CACtB,IAAI,CAAC,WAAW,OAAO,YAAY,UACjC,OAAO;EAAE,aAAa,CAAC;EAAG,UAAU;CAAK;CAE3C,MAAM,eAAe,WAAW,iBAAiB,OAAO,IAAI;CAC5D,MAAM,SAAS,MAAM;CACrB,MAAM,MAAM,aAAa;CACzB,MAAM,cAAkC,CAAC;CACzC,IAAI,QAAQ;CACZ,IAAI,MAAM;CACV,IAAI,MAAM;CAEV,OAAO,QAAQ,KAAK;EAClB,IAAI,QAAQ;EACZ,IAAI,SAAS;EACb,IAAI;EAGJ,IAAI,cAAc;EAClB,GAAG;GACD,IAAI,SAAS,KAAK,OAAO;IAAE;IAAa,UAAU;GAAM;GACxD,OAAO,aAAa,WAAW,OAAO,IAAI;GAC1C,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO;IAAE;IAAa,UAAU;GAAM;GACjE,IAAI,QAAQ,MAAO,UAAU,OAAO,OAAO,MAAQ,GACjD,OAAO;IAAE;IAAa,UAAU;GAAM;GAExC,WAAW,OAAO,OAAS;GAC3B,SAAS;GACT,IAAI,OAAO,IAAM;IACf,cAAc;IACd;GACF;EACF,SAAS,QAAQ;EAEjB,IAAI,CAAC,aAAa,OAAO;GAAE;GAAa,UAAU;EAAM;EAGxD,OAAO,SAAS,IAAI,EAAE,WAAW,KAAK,WAAW;EAEjD,QAAQ;EACR,SAAS;EAGT,IAAI,cAAc;EAClB,GAAG;GACD,IAAI,SAAS,KAAK,OAAO;IAAE;IAAa,UAAU;GAAM;GACxD,OAAO,aAAa,WAAW,OAAO,IAAI;GAC1C,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO;IAAE;IAAa,UAAU;GAAM;GACjE,IAAI,QAAQ,MAAO,UAAU,OAAO,OAAO,MAAQ,GACjD,OAAO;IAAE;IAAa,UAAU;GAAM;GAExC,WAAW,OAAO,OAAS;GAC3B,SAAS;GACT,IAAI,OAAO,IAAM;IACf,cAAc;IACd;GACF;EACF,SAAS,QAAQ;EAEjB,IAAI,CAAC,aAAa,OAAO;GAAE;GAAa,UAAU;EAAM;EAExD,OAAO,SAAS,IAAI,EAAE,WAAW,KAAK,WAAW;EAEjD,YAAY,KAAK,CAAC,MAAM,QAAQ,MAAM,MAAM,CAAC;CAC/C;CAEA,OAAO;EAAE;EAAa,UAAU;CAAK;AACvC;;;;;;;;;AAUA,SAAgB,eACd,SACA,YAAY,GACZ,WAAW,OACS;CACpB,OAAO,uBAAuB,SAAS,WAAW,QAAQ,CAAC,CAAC;AAC9D;;;;AAKA,SAAS,mBAAmB,KAAqB;CAC/C,IAAI,SAAS,MAAM,IAAI,EAAE,OAAO,KAAK,OAAO;CAC5C,IAAI,MAAM;CACV,OAAO,UAAU,IAAM;EACrB,OAAO,OAAO,cAAc,KAAQ,SAAS,MAAS,EAAE;EACxD,YAAY;CACd;CACA,OAAO,OAAO,aAAa,SAAS,EAAE;CACtC,OAAO;AACT;;;;;;;;AASA,SAAgB,eAAe,aAAiC,YAAY,GAAW;CACrF,IAAI,CAAC,MAAM,QAAQ,WAAW,KAAK,YAAY,WAAW,GAAG,OAAO;CACpE,MAAM,SAAS,MAAM;CACrB,IAAI,SAAS;CACb,IAAI,UAAU;CACd,IAAI,UAAU;CAEd,KAAK,MAAM,CAAC,KAAK,QAAQ,aAAa;EACpC,IAAI,CAAC,OAAO,SAAS,GAAG,KAAK,CAAC,OAAO,SAAS,GAAG,GAAG;EACpD,MAAM,WAAW,KAAK,MAAM,MAAM,MAAM;EACxC,MAAM,WAAW,KAAK,MAAM,MAAM,MAAM;EAExC,MAAM,WAAW,WAAW;EAC5B,MAAM,WAAW,WAAW;EAE5B,UAAU,mBAAmB,QAAQ;EACrC,UAAU,mBAAmB,QAAQ;EAErC,UAAU;EACV,UAAU;CACZ;CAEA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,qBACd,SACA,YAAY,GACZ,aAAgC,CAAC,GACZ;CACrB,OAAO;EACL,MAAM;EACN,YAAY,EAAE,GAAG,WAAW;EAC5B,UAAU;GACR,MAAM;GACN,aAAa,eAAe,SAAS,SAAS;EAChD;CACF;AACF;;;;;;;;AASA,SAAgB,qBACd,SAKA,YAAY,GACO;CACnB,IAAI,CAAC,SAAS,OAAO;CAGrB,IAAI,QAAQ,SAAS,WACnB,OAAO,qBAAqB,QAAQ,UAAU,SAAS;CAIzD,IAAI,QAAQ,SAAS,qBAAqB;EACxC,MAAM,UAAoB,CAAC;EAC3B,KAAK,MAAM,WAAW,QAAQ,UAAU;GACtC,MAAM,MAAM,qBAAqB,QAAQ,UAAU,SAAS;GAC5D,IAAI,MAAM,QAAQ,GAAG,GAAG,QAAQ,KAAK,GAAG,GAAG;QACtC,IAAI,KAAK,QAAQ,KAAK,GAAG;EAChC;EACA,OAAO;CACT;CAGA,IAAI,QAAQ,SAAS,cACnB,OAAO,eAAe,QAAQ,aAAmC,SAAS;CAI5E,IAAI,QAAQ,SAAS,mBACnB,OAAO,QAAQ,YAAY,KAAK,eAC9B,eAAe,YAAkC,SAAS,CAC5D;CAGF,OAAO;AACT;;;;;;;;AAsBA,SAAgB,qBACd,OACA,UAAgC,CAAC,GACgB;CACjD,IAAI,CAAC,SAAS,OAAO,UAAU,UAC7B,OAAO;EAAE,MAAM;EAAqB,UAAU,CAAC;CAAE;CAGnD,MAAM,EACJ,YAAY,GACZ,WAAW,MACX,YAAY,SACZ,cAAc,OACd,iBAAiB,CAAC,MAChB;CAEJ,MAAM,YAAY,MAAM,MAAM,SAAS;CACvC,MAAM,kBAAwC,CAAC;CAC/C,MAAM,WAAkC,CAAC;CAEzC,KAAK,IAAI,IAAI,GAAG,IAAI,UAAU,QAAQ,KAAK;EACzC,MAAM,MAAM,UAAU,EAAE,CAAC,KAAK;EAC9B,IAAI,CAAC,KAAK;EAEV,MAAM,UAAU,IAAI,QAAQ,gBAAgB,EAAE,CAAC,CAAC,KAAK;EACrD,IAAI,CAAC,SAAS;EAEd,MAAM,eAAe,uBAAuB,SAAS,WAAW,QAAQ;EACxE,IAAI,aAAa,YAAY,aAAa,YAAY,UAAU,GAAG;GACjE,MAAM,SAAS,aAAa;GAC5B,gBAAgB,KAAK,MAAM;GAC3B,SAAS,KAAK;IACZ,MAAM;IACN,YAAY;KACV,GAAG;KACH,YAAY,SAAS,SAAS;KAC9B,aAAa,OAAO;IACtB;IACA,UAAU;KACR,MAAM;KACN,aAAa;IACf;GACF,CAAC;EACH;CACF;CAEA,IAAI,eAAe,gBAAgB,SAAS,GAa1C,OAAO;EACL,MAAM;EACN,UAAU,CAAC;GAbX,MAAM;GACN,YAAY;IACV,GAAG;IACH,YAAY,gBAAgB;IAC5B,aAAa,gBAAgB,QAAQ,KAAK,MAAM,MAAM,EAAE,QAAQ,CAAC;GACnE;GACA,UAAU;IACR,MAAM;IACN,aAAa;GACf;EAIsB,CAAC;CACzB;CAGF,OAAO;EACL,MAAM;EACN;CACF;AACF;;AChUA,MAAM,WACJ,OAAO,2BAA2B,cAAc,CAAC,IAAI;;;;;;;;;;;;;;;AAgBvD,SAAgB,sBAA0D;CACxE,OAAO;AACT;;;;;;;AAQA,SAAgB,wBAA4D;CAC1E,IAAI,OAAO,WAAW,aAAa,OAAO;CAG1C,OAAO;EACL,GAAG;EACH,GAAI,OAAO,4BAA4B,CAAC;CAC1C;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,wBACd,KACoB;CAGpB,QAFmB,OAAO,sBAAsB,EAAA,CACrB,oCAAoC,KAAK,KAClD,KAAA;AACpB;;;;;;;;;;;;;AAcA,SAAgB,mBAAmB,KAA8D;CAG/F,QAFmB,OAAO,sBAAsB,EAAA,CACrB,wBAAwB,KAAK,KACtC,KAAA;AACpB;;;;;;;;;;;;;;;;AAiBA,SAAgB,oBAAoB,KAA8D;CAChG,MAAM,aAAa,OAAO,sBAAsB;CAGhD,OADE,WAAW,0BAA0B,KAAK,KAAK,WAAW,qBAAqB,KAAK,KACpE,KAAA;AACpB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,qBAAqB,KAA8D;CACjG,MAAM,aAAa,OAAO,sBAAsB;CAEhD,OADgB,WAAW,0BAA0B,KAAK,KAAK,WAAW,cAAc,KAAK,KAC3E,KAAA;AACpB;;;;;;;;;;;;;;;;;AAkBA,SAAgB,gBAAgB,KAA8D;CAC5F,MAAM,aAAa,OAAO,sBAAsB;CAEhD,OADgB,WAAW,qBAAqB,KAAK,KAAK,WAAW,gBAAgB,KAAK,KACxE,KAAA;AACpB;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,kBAAkB,KAA8D;CAC9F,MAAM,aAAa,OAAO,sBAAsB;CAEhD,OADgB,WAAW,mBAAmB,KAAK,KAAK,WAAW,cAAc,KAAK,KACpE,KAAA;AACpB;;;;;;;;;;AAWA,SAAgB,qBACd,QACA,KACoB;CACpB,MAAM,MAAM,mBAAmB,GAAG;CAClC,IAAI,CAAC,KAAK,OAAO,KAAA;CACjB,OAAO,uCAAuC,mBAC5C,MACF,EAAE,eAAe,mBAAmB,GAAG;AACzC;;;AC/MA,MAAa,kBAA4C;CAAC;CAAO;CAAO;CAAU;AAAW;;;;;;;AAQ7F,SAAgB,mBAAmB,SAAkB,OAAuB;CAC1E,OAAO,OAAO,QAAQ,MAAM,KAAK;AACnC;;;;;;;AAQA,SAAgB,mBACd,SACA,SACA,MACU;CACV,MAAM,aAAa,IAAI,IAAI,OAAO;CAClC,QAAQ,MAAR;EACE,KAAK,OACH,OAAO,MAAM,KAAK,UAAU;EAC9B,KAAK,OAAO;GACV,MAAM,SAAS,IAAI,IAAI,OAAO;GAC9B,KAAK,MAAM,MAAM,SAAS,OAAO,IAAI,EAAE;GACvC,OAAO,MAAM,KAAK,MAAM;EAC1B;EACA,KAAK,UACH,OAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,QAAQ,OAAO,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC;EACxE,KAAK,aACH,OAAO,MAAM,KAAK,IAAI,IAAI,QAAQ,QAAQ,OAAO,WAAW,IAAI,EAAE,CAAC,CAAC,CAAC;CACzE;AACF;;;;;;AAOA,SAAgB,gBAAgB,QAA2B,SAAsC;CAC/F,MAAM,WAAW,IAAI,IAAI,OAAO;CAChC,OAAO,OAAO,QAAQ,OAAO,CAAC,SAAS,IAAI,EAAE,CAAC;AAChD;;ACrBA,MAAM,MAAM,MAAc,SAAiB,YAA2C;CACpF;CACA;CACA;AACF;;;;;;AAOA,MAAa,iCAA+D;CAC1E;EACE,KAAK;EACL,WAAW;GACT,GAAG,OAAO,wBAAoB,KAAK;GACnC,GAAG,OAAO,wBAAoB,KAAK;GACnC,GAAG,MAAM,YAAU,IAAI;GACvB,GAAG,cAAc,oBAAkB,YAAY;GAC/C,GAAG,iBAAiB,uBAAqB,cAAc;GACvD,GAAG,MAAM,yCAAqC,IAAI;GAClD,GAAG,MAAM,6CAAqC,IAAI;GAClD,GAAG,YAAY,mDAA2C,SAAS;GACnE,GAAG,UAAU,sCAAgC,QAAQ;GACrD,GAAG,WAAW,iCAA2B,SAAS;GAClD,GAAG,UAAU,sCAAgC,UAAU;EACzD;CACF;CACA;EACE,KAAK;EACL,WAAW;GACT,GAAG,QAAQ,mFAAqE,QAAQ;GACxF,GACE,SACA,0FACA,OACF;GACA,GAAG,YAAY,sDAA8C,UAAU;GACvE,GAAG,MAAM,6CAAqC,IAAI;GAClD,GAAG,MAAM,6CAAqC,KAAK;GACnD,GAAG,KAAK,oCAA8B,IAAI;GAC1C,GAAG,MAAM,qCAA+B,KAAK;GAC7C,GAAG,KAAK,oCAA8B,IAAI;GAC1C,GAAG,MAAM,qCAA+B,KAAK;GAC7C,GAAG,OAAO,mFAAqE,KAAK;GACpF,GAAG,OAAO,2FAAyE,KAAK;GACxF,GAAG,KAAK,iCAA2B,KAAK;EAC1C;CACF;CACA;EACE,KAAK;EACL,WAAW;GACT,GAAG,KAAK,oCAA8B,KAAK;GAC3C,GAAG,KAAK,oCAA8B,UAAU;GAChD,GAAG,KAAK,oCAA8B,UAAU;GAChD,GAAG,KAAK,oCAA8B,QAAQ;GAC9C,GAAG,KAAK,oCAA8B,QAAQ;GAC9C,GAAG,KAAK,oCAA8B,OAAO;GAC7C,GAAG,OAAO,mCAA6B,KAAK;GAC5C,GAAG,QAAQ,oCAA8B,MAAM;GAC/C,GAAG,SAAS,qCAA+B,OAAO;GAClD,GAAG,SAAS,qCAA+B,OAAO;GAClD,GAAG,QAAQ,oCAA8B,MAAM;GAC/C,GAAG,MAAM,kCAA4B,IAAI;GACzC,GAAG,SAAS,qCAA+B,OAAO;GAClD,GAAG,OAAO,sCAAgC,KAAK;GAC/C,GAAG,OAAO,sCAAgC,KAAK;EACjD;CACF;CACA;EACE,KAAK;EACL,WAAW;GACT,GAAG,UAAU,mDAA2C,QAAQ;GAChE,GAAG,UAAU,sCAAgC,QAAQ;GACrD,GAAG,YAAY,wCAAkC,UAAU;GAC3D,GAAG,SAAS,2CAAqC,OAAO;GACxD,GACE,iBACA,kGACA,cACF;EACF;CACF;CACA;EACE,KAAK;EACL,WAAW;GACT,GAAG,aAAa,yCAAmC,UAAU;GAC7D,GAAG,aAAa,yCAAmC,UAAU;GAC7D,GAAG,cAAc,0CAAoC,WAAW;GAChE,GAAG,YAAY,+BAA2B,SAAS;EACrD;CACF;CACA;EACE,KAAK;EACL,WAAW;GACT,GAAG,QAAQ,cAAY,MAAM;GAC7B,GACE,eACA,gHACA,aACF;GACA,GAAG,QAAQ,wDAAgD,MAAM;GACjE,GAAG,OAAO,0BAAwB,KAAK;GACvC,GAAG,QAAQ,gCAA8B,MAAM;EACjD;CACF;AACF;;;;;;AAOA,SAAgB,yBAAyB,OAAuB;CAC9D,IAAI,SAAS;CACb,IAAI,WAAW;CACf,IAAI,UAAU;CAEd,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GAAG;EACpD,MAAM,OAAO,MAAM;EAEnB,IAAI,UAAU;GACZ,UAAU;GACV,IAAI,SACF,UAAU;QACL,IAAI,SAAS,MAClB,UAAU;QACL,IAAI,SAAS,MAClB,WAAW;GAEb;EACF;EAEA,IAAI,SAAS,MAAK;GAChB,WAAW;GACX,UAAU;GACV;EACF;EAEA,IAAI,SAAS,KAAK;GAChB,MAAM,kBAAkB,MAAM,MAAM,QAAQ,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,GAAG;GAC7D,IAAI,oBAAoB,OAAO,oBAAoB,KAAK;EAC1D;EAEA,UAAU;CACZ;CAEA,OAAO;AACT;;;;;;;;AA2BA,MAAM,sBAAsB;;;;;;;;;;;;;;AAe5B,SAAS,gBAAgB,cAAsC;CAC7D,OAAO;EACL,MAAM;EACN,iBAAiB;EACjB,YAAY,EAAE,YAAY,CAAC,QAAQ,SAAS,EAAE;CAChD;AACF;;;;;;;;;;;AA2BA,SAAgB,sBACd,QACA,UAAwC,CAAC,GACnB;CACtB,OAAO,qBAAqB,QAAQ,OAAO,CAAC,CAAC;AAC/C;;;;;;;AAQA,SAAS,qBACP,QACA,SACoE;CACpE,MAAM,UAAU,OAAO,KAAK;CAC5B,IAAI,CAAC,SAAS,OAAO,EAAE,YAAY;EAAE,IAAI;EAAM,QAAQ,CAAC;CAAE,EAAE;CAE5D,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,yBAAyB,OAAO,CAAC;CACvD,SAAS,OAAO;EACd,OAAO,EACL,YAAY;GACV,IAAI;GACJ,MAAM;GACN,QAAQ,CAAC,iBAAiB,QAAQ,MAAM,UAAU,cAAc;EAClE,EACF;CACF;CACA,IAAI,CAAC,MAAM,QAAQ,MAAM,GACvB,OAAO,EAAE,YAAY;EAAE,IAAI;EAAO,MAAM;EAAa,QAAQ,CAAC;CAAE,EAAE;CAEpE,IAAI,OAAO,OAAO,OAAO,UACvB,OAAO,EACL,YAAY;EAAE,IAAI;EAAO,MAAM;EAAgB,QAAQ,CAAC;EAAG;CAAO,EACpE;CAGF,MAAM,cAAc,8BAA8B,QAAQ,QAAQ,aAAa,CAAC,CAAC;CACjF,MAAM,WAAW,iBACf,aACA,qBACA,QAAQ,eAAe,gBAAgB,QAAQ,YAAY,IAAI,KAAA,CACjE;CACA,IAAI,SAAS,WAAW,SACtB,OAAO,EACL,YAAY;EACV,IAAI;EACJ,MAAM;EACN,QAAQ,SAAS,MAAM,KAAK,UAC1B,MAAM,MAAM,GAAG,MAAM,IAAI,IAAI,MAAM,YAAY,MAAM,OACvD;EACA;CACF,EACF;CAEF,OAAO;EAAE,YAAY;GAAE,IAAI;GAAM,QAAQ,CAAC;GAAG;EAAO;EAAG,YAAY,SAAS;CAAM;AACpF;;;;;;;AAeA,SAAgB,8BACd,MACA,WACS;CACT,IAAI,OAAO,SAAS,YAAY,KAAK,WAAW,GAAG,GAAG;EACpD,MAAM,QAAQ,UAAU,MAAM,aAAa,SAAS,UAAU,IAAI;EAClE,OAAO,QAAQ,MAAM,QAAQ;CAC/B;CACA,IAAI,MAAM,QAAQ,IAAI,GACpB,OAAO,KAAK,KAAK,UAAU,8BAA8B,OAAO,SAAS,CAAC;CAE5E,IAAI,QAAQ,OAAO,SAAS,UAC1B,OAAO,OAAO,YACZ,OAAO,QAAQ,IAA+B,CAAC,CAAC,KAAK,CAAC,KAAK,WAAW,CACpE,KACA,8BAA8B,OAAO,SAAS,CAChD,CAAC,CACH;CAEF,OAAO;AACT;;;;;;;;AA8BA,SAAgB,sBACd,QACA,UAAwC,CAAC,GACtB;CACnB,MAAM,UAAU,OAAO,KAAK;CAC5B,IAAI,CAAC,SAAS,OAAO,EAAE,MAAM,QAAQ;CAErC,MAAM,EAAE,YAAY,eAAe,qBAAqB,SAAS;EAC/D,WAAW,QAAQ;EACnB,cAAc,QAAQ;CACxB,CAAC;CACD,IAAI,CAAC,WAAW,MAAM,CAAC,YACrB,OAAO;EAAE,MAAM;EAAS,QAAQ,WAAW;CAAO;CAGpD,MAAM,UAAU,QAAQ,WAAW;CACnC,MAAM,eAAe,SAAS,UAAU,QAAQ;CAChD,IAAI;EAYF,OAAO;GAAE,MAAM;GAAS,OANV,WAAW,6BAA6B,EAAE,MAAM,QAAQ,QAAQ,EAAE,GAAG;IACjF,MAAM;IACN,YAAY,SAAS,cAAc,CAAC;IACpC,GAAI,SAAS,OAAO,KAAA,IAAY,EAAE,IAAI,QAAQ,GAAG,IAAI,CAAC;IACtD,UAAU,SAAS;GACrB,CAC4B;EAAE;CAChC,SAAS,OAAO;EACd,OAAO;GACL,MAAM;GACN,QAAQ,CAAC,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,CAAC;EACjE;CACF;AACF;;;;;;;;AA4BA,SAAgB,yBACd,QACA,UAII,CAAC,GACsB;CAC3B,MAAM,UAAU,OAAO,KAAK;CAC5B,IAAI,CAAC,SAAS,OAAO;EAAE,IAAI;EAAO,QAAQ,CAAC;CAAE;CAE7C,MAAM,EAAE,YAAY,eAAe,qBAAqB,SAAS;EAC/D,WAAW,QAAQ;EACnB,cAAc,QAAQ;CACxB,CAAC;CACD,IAAI,CAAC,WAAW,MAAM,CAAC,YACrB,OAAO;EAAE,IAAI;EAAO,QAAQ,WAAW;CAAO;CAEhD,MAAM,eAAe,QAAQ,QAAQ;CACrC,OAAO;EACL,IAAI;EACJ,QAAQ,CAAC;EACT,WAAW,SAAS,OAAO,iBACzB,WAAW,6BAA6B,EAAE,KAAK,GAAG;GAChD,MAAM,QAAQ,UAAU,QAAQ;GAChC,YAAY,QAAQ,cAAc,CAAC;GACnC,GAAI,QAAQ,OAAO,KAAA,IAAY,EAAE,IAAI,QAAQ,GAAG,IAAI,CAAC;GACrD,UAAU,QAAQ;EACpB,CAAU;CACd;AACF;;;;;;;;;AA0BA,SAAgB,0BACd,UACA,QACA,UAA+D,CAAC,GACtC;CAC1B,MAAM,UAAU,OAAO,KAAK;CAC5B,IAAI,CAAC,SAAS,OAAO;EAAE,IAAI;EAAO,QAAQ,CAAC;EAAG,KAAK,CAAC;EAAG,YAAY;CAAE;CAErE,MAAM,EAAE,YAAY,eAAe,qBAAqB,SAAS;EAC/D,WAAW,QAAQ;EACnB,cAAc;CAChB,CAAC;CACD,IAAI,CAAC,WAAW,MAAM,CAAC,YACrB,OAAO;EAAE,IAAI;EAAO,QAAQ,WAAW;EAAQ,KAAK,CAAC;EAAG,YAAY;CAAE;CAGxE,MAAM,MAAgB,CAAC;CACvB,IAAI,aAAa;CACjB,SAAS,SAAS,SAAS,UAAU;EACnC,IAAI;GASF,IANc,WAAW,6BAA6B,EAAE,MAAM,QAAQ,QAAQ,EAAE,GAAG;IACjF,MAAM,QAAQ,UAAU,QAAQ;IAChC,YAAY,QAAQ,cAAc,CAAC;IACnC,GAAI,QAAQ,OAAO,KAAA,IAAY,EAAE,IAAI,QAAQ,GAAG,IAAI,CAAC;IACrD,UAAU,QAAQ;GACpB,CACQ,MAAM,MAAM,IAAI,KAAK,mBAAmB,SAAS,KAAK,CAAC;EACjE,QAAQ;GACN,cAAc;EAChB;CACF,CAAC;CACD,OAAO;EAAE,IAAI;EAAM,QAAQ,CAAC;EAAG;EAAK;CAAW;AACjD;;;;;;;;;AAUA,SAAgB,iBACd,OACyD;CACzD,OAAO,iBAAiB;AAC1B;;;;;;AAOA,SAAgB,6BAA6B,OAAwB;CACnE,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW,OAAO;CAClD,IAAI,iBAAiB,KAAK,GAAG;EAC3B,MAAM,QAAQ;EAGd,MAAM,QAAQ,MAAM;EACpB,MAAM,WAAW,cAAsB,KAAK,OAAO,UAAU,IAAI,IAAI,YAAY,SAAS,GAAG;EAC7F,OAAO,QAAQ,QAAQ,MAAM,CAAC,EAAE,IAAI,QAAQ,MAAM,CAAC,EAAE,IAAI,QAAQ,MAAM,CAAC,EAAE,IAAI,OAAO,MAAM,QAAQ,CAAC,CAAC,EAAE;CACzG;CACA,IAAI;EACF,OAAO,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;CAC9C,QAAQ;EACN,OAAO,OAAO,KAAK;CACrB;AACF;;;;;;AAUA,SAAgB,gBACd,UACA,OACqC;CACrC,MAAM,SAA8C,CAAC;CACrD,KAAK,MAAM,QAAQ,OAAO,OAAO,QAAQ;CAEzC,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,aAAa,QAAQ;EAC3B,IAAI,CAAC,YAAY;EACjB,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,QAAQ,WAAW;GACzB,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW;GAC3C,MAAM,YAAY,OAAO;GACzB,MAAM,YACJ,cAAc,YAAY,cAAc,YAAY,cAAc,YAC9D,YACA;GACN,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ;QAC1C,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ;EACtD;CACF;CACA,OAAO;AACT;;;ACzkBA,SAAgB,wBAAwB,OAA6C;CACnF,OAAO,MAAM,QAAQ,KAAK;AAC5B;;;;;;;AAQA,SAAgB,WAAW,OAAyB;CAIlD,OAAO,OAAO,UAAU,YAAY,kBAAkB,KAAK,MAAM,KAAK,CAAC;AACzE;;AAGA,SAAS,mBAAmB,OAAwB;CAClD,OAAO,gCAAgC,KAAK,MAAM,KAAK,CAAC;AAC1D;;;;;;AAOA,MAAM,0BAA0B;CAAC;CAAQ;CAAU;AAAc;AACjE,MAAM,2BAA2B;CAC/B;CACA;CACA;CAEA;AACF;AAEA,SAAS,qBAAqB,OAA4B;CACxD,OAAO,WAAW,OAAO,oBAAoB,MAAM;AACrD;;;;;;;;;;;AAYA,SAAS,qBACP,OACA,UACA,MACkB;CAClB,IAAI,CAAC,qBAAqB,KAAK,GAAG,OAAO;CAKzC,OAAO,mBAAmB,OAAO,UAAU;EACzC;EACA,CAAC,OAAO,QAAQ;EAChB;CACF,CAAC;AACH;;;;;;;;;;AAWA,SAAgB,mBACd,OACA,WACS;CACT,IACE,MAAM,QAAQ,KAAK,KACnB,MAAM,OAAO,UACb,MAAM,QAAQ,MAAM,EAAE,KACtB,MAAM,EAAE,CAAC,OAAO,QAChB;EACA,MAAM,MAAiB;GAAC;GAAQ,MAAM;GAAI,UAAU,MAAM,EAAE;EAAC;EAC7D,KAAK,IAAI,QAAQ,GAAG,QAAQ,MAAM,QAAQ,SAAS,GACjD,IAAI,KAAK,MAAM,QAAQ,UAAU,MAAM,QAAQ,EAAE,CAAC;EAEpD,OAAO;CACT;CACA,OAAO,UAAU,KAAK;AACxB;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,uBACd,OACA,UACA,MACoB;CACpB,IAAI,CAAC,qBAAqB,KAAK,GAAG,OAAO;CACzC,OAAO;EAAC;EAAa;GAAC;GAAY,CAAC,OAAO,QAAQ;GAAG;EAAI;EAAG;CAAI;AAClE;;AAGA,SAAS,eAAiC,MAAe,MAA8B;CACrF,IAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW,KAAK,KAAK,OAAO,OAAO,OAAO;CAC3E,OAAQ,KAA4B,SAAS,KAAK,EAAE,IAAK,KAAK,KAAW;AAC3E;;;;;;;;;;AAWA,SAAS,2BACP,MAC8E;CAC9E,IAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW,KAAK,KAAK,OAAO,YAAY,OAAO;CAChF,MAAM,WAAW,eAAe,KAAK,IAAI,uBAAuB;CAChE,OAAO,WAAW;EAAE;EAAU,MAAM,KAAK;CAAG,IAAI;AAClD;;;;;;;;;;AAWA,SAAgB,4BACd,MAC8E;CAC9E,IAAI,CAAC,MAAM,QAAQ,IAAI,KAAK,KAAK,WAAW,KAAK,KAAK,OAAO,aAAa,OAAO;CACjF,MAAM,QAAQ,KAAK;CACnB,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,KAAK,MAAM,OAAO,YAAY,OAAO;CACnF,MAAM,WAAW,eAAe,MAAM,IAAI,wBAAwB;CAClE,MAAM,OAAO,KAAK;CAClB,IAAI,CAAC,YAAY,OAAO,SAAS,YAAY,CAAC,OAAO,SAAS,IAAI,KAAK,MAAM,OAAO,MAClF,OAAO;CAET,OAAO;EAAE;EAAU;CAAK;AAC1B;;;;;;;;;AAUA,SAAgB,2BAA2B,OAAyB;CAClE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAClC,IAAI,2BAA2B,KAAK,KAAK,4BAA4B,KAAK,GAAG,OAAO;CACpF,OAAO,MAAM,KAAK,0BAA0B;AAC9C;;;;;;;;AASA,MAAa,sCAAuC,IAAI,KAAK,KAAK,UAAW;;;;;;;;;;AAW7E,SAAgB,gCAAwC;CACtD,OAAQ,IAAI,KAAK,KAAK,6BAA6B,IAAK;AAC1D;AAGA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;;;AAoB1B,SAAgB,sBAAsB,QAA2B;CAC/D,MAAM,eAAe,SAAS,8BAA8B;CAC5D,OAAO;EACL;EACA,CAAC,eAAe,CAAC;EACjB,CAAC,MAAM;EACP;EACA;EACA;EACA,eAAe,KAAK;CACtB;AACF;;;;;;;;;;;;;;;;;;AAmBA,SAAgB,eAAe,OAAuC;CAGpE,MAAM,oBAAoB,6BAA6B,KAAK;CAC5D,IAAI,mBAIF,OAAO,uBAAuB,OAAO,iBAAiB;CAExD,IAAI,WAAW,OAAO,iBAAiB,MAAM,UAK3C,OAAO,sBAAsB,WAAW,OAAO,aAAa,CAAC;CAI/D,OAAO,uBACL,OACA,uBAAuB,OAAO,gBAAgB,WAAW,OAAO,aAAa,CAAC,CAChF;AACF;;;;;;;;AASA,SAAgB,mBAAmB,OAA2B;CAC5D,MAAM,QAAQ,WAAW,OAAO,eAAe;CAC/C,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,CAAC,IAAI;AACjG;;;;;;;;;AAUA,SAAgB,wBAAwB,OAAuC;CAC7E,OAAO,uBAAuB,OAAO,kBAAkB,mBAAmB,KAAK,CAAC;AAClF;;;;;;;;;AAUA,SAAgB,oBAAoB,OAAmC;CACrE,OACE,MAAM,QAAQ,KAAK,KACnB,MAAM,SAAS,KACf,MAAM,OAAO,WAAW,OAAO,WAAW,YAAY,OAAO,SAAS,MAAM,KAAK,UAAU,CAAC,KAC5F,MAAM,MAAM,WAAW,SAAS,CAAC;AAErC;;;;;;;;;;AAWA,SAAgB,gBAAgB,OAAoC;CAClE,MAAM,OAAO,WAAW,OAAO,YAAY;CAC3C,OAAO,oBAAoB,IAAI,IAAI,CAAC,GAAG,IAAI,IAAI;AACjD;;;;;;;;AASA,SAAgB,qBACd,MAC6B;CAC7B,IAAI,CAAC,oBAAoB,IAAI,GAAG,OAAO;CACvC,KAAK,MAAM,CAAC,MAAM,YAAY,OAAO,QAAQ,mBAAmB,GAC9D,IACE,QAAQ,WAAW,KAAK,UACxB,QAAQ,OAAO,QAAQ,UAAU,WAAW,KAAK,MAAM,GAEvD,OAAO;CAGX,OAAO;AACT;;;;;;;AAQA,SAAgB,gBAAgB,OAA2B;CACzD,MAAM,QAAQ,WAAW,OAAO,YAAY;CAC5C,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ;AACpF;;;;;;;;;;;AAYA,SAAgB,yBACd,SACS;CACT,MAAM,WAAW,SAAS;CAC1B,IAAI,CAAC,UAAU,QAAQ,OAAO;CAC9B,MAAM,QAAQ,KAAK,IAAI,SAAS,QAAQ,GAAI;CAC5C,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,SAAS,GAAG;EAC7C,MAAM,aAAa,SAAS,MAAM,EAAE;EACpC,IAAI,CAAC,YAAY;EACjB,KAAK,MAAM,OAAO,yBAAyB;GACzC,MAAM,QAAQ,WAAW;GACzB,IAAI,OAAO,UAAU,YAAY,mBAAmB,KAAK,GAAG,OAAO;EACrE;EACA,KAAK,MAAM,OAAO,0BAA0B;GAC1C,MAAM,QAAQ,WAAW;GACzB,IAAI,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAAG,OAAO;EAClE;CACF;CACA,OAAO;AACT;;;;;;AAOA,SAAgB,oBAAoB,YAAsC;CACxE,MAAM,UAAU,WAAW,KAAK;CAChC,IAAI,CAAC,SAAS,OAAO;CAErB,IAAI;EACF,MAAM,SAAS,KAAK,MAAM,yBAAyB,OAAO,CAAC;EAC3D,IAAI,CAAC,MAAM,QAAQ,MAAM,GAAG,OAAO;EACnC,OAAO;CACT,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,sBAAsB,OAAmB,eAAyC;CAChG,MAAM,OAAO,WAAW,OAAO,iBAAiB;CAChD,IAAI,SAAS,UAAU,OAAO;CAE9B,IAAI,SAAS,cACX,OAAO,oBAAoB,WAAW,OAAO,uBAAuB,CAAC,KAAK;CAG5E,IAAI,SAAS,cACX,OAAO,yBAAyB,OAAO,aAAa;CAGtD,MAAM,WAAW,WAAW,OAAO,qBAAqB,CAAC,CAAC,KAAK;CAC/D,IAAI,CAAC,UAAU,OAAO;CAEtB,IAAI,SAAS,eAAe;EAC1B,MAAM,QAAQ,WAAW,OAAO,kBAAkB,CAAC,CAAC,QACjD,SAAS,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,KAAK,WAAW,KAAK,KAAK,CACzE;EACA,IAAI,MAAM,WAAW,GAAG,OAAO;EAE/B,OAAO;GACL;GACA,CAAC,aAAa,CAAC,OAAO,QAAQ,CAAC;GAC/B,GAAG,MAAM,SAAS,SAAS,CAAC,OAAO,KAAK,KAAK,CAAC,CAAC,KAAK,GAAG,KAAK,KAAK,CAAC;GAClE;EACF;CACF;CAEA,MAAM,QAAQ,eAAe,KAAK;CAClC,IAAI,MAAM,SAAS,GAAG,OAAO;CAM7B,OAAO;EACL;EACA;GAAC;GAAa,CAAC,OAAO,QAAQ;GAAG,MAAM,EAAE,CAAC;EAAK;EAC/C,MAAM,EAAE,CAAC;EACT,GAAG,MAAM,MAAM,CAAC,CAAC,CAAC,SAAS,SAAS,CAAC,KAAK,OAAO,KAAK,KAAK,CAAC;CAC9D;AACF;;;;;;;;AASA,SAAS,eAAe,OAAuD;CAC7E,MAAM,SAAS,WAAW,OAAO,kBAAkB,CAAC,CACjD,KAAK,UAAU;EACd,OAAO,KAAK;EACZ,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ,OAAO,WAAW,KAAK,KAAK;CACnF,EAAE,CAAC,CACF,QAAQ,SAAS,OAAO,SAAS,KAAK,KAAK,KAAK,WAAW,KAAK,KAAK,CAAC,CAAC,CACvE,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;CACnC,OAAO,OAAO,QAAQ,MAAM,UAAU,UAAU,KAAK,KAAK,QAAQ,OAAO,QAAQ,EAAE,CAAC,KAAK;AAC3F;;AA8BA,SAAS,SAAS,OAA+C;CAC/D,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACvE;;;;;;;;;;;;AAaA,SAAgB,qBAAqB,OAGnC;CACA,MAAM,MAAM,WAAW,OAAO,aAAa;CAC3C,MAAM,WAAW,IAAI,MAAM,SAAS,KAAK,UAAU,KAAK,YAAY,KAAK,KAAK;CAC9E,MAAM,uBAAO,IAAI,IAAwB;CACzC,KAAK,MAAM,QAAQ,KACjB,IAAI,CAAC,KAAK,QAAQ,KAAK,IAAI,KAAK,IAAI,IAAI;CAK1C,MAAM,2BAAW,IAAI,IAAY;CACjC,KAAK,MAAM,QAAQ,KAAK,OAAO,GAC7B,IAAI,KAAK,YAAY,KAAK,aAAa,KAAK,MAAM,KAAK,IAAI,KAAK,QAAQ,GACtE,SAAS,IAAI,KAAK,QAAQ;CAI9B,MAAM,QAA+B,CAAC;CACtC,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,UAAU,SAAS,IAAI,KAAK,EAAE,GAAG;EAC1C,IAAI,KAAK,YAAY,SAAS,CAAC,WAAW,KAAK,KAAK,GAAG;EACvD,MAAM,MAAM,oBAAoB,KAAK,MAAM;EAI3C,IAAI,CAAC,OAAO,OAAO,IAAI,OAAO,UAAU;EAExC,MAAM,UAAuB,CAAC,GAAG;EACjC,IAAI,UAAU,SAAS,KAAK,OAAO;EACnC,IAAI,UAAU,SAAS,KAAK,OAAO;EACnC,IAAI,UAAU;EACd,MAAM,uBAAO,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;EAG9B,IAAI,SAAS,KAAK,YAAY,KAAK,aAAa,KAAK,KAAK,KAAK,IAAI,KAAK,QAAQ,IAAI,KAAA;EACpF,OAAO,QAAQ;GACb,IAAI,KAAK,IAAI,OAAO,EAAE,KAAK,OAAO,YAAY,OAAO;IACnD,UAAU;IACV;GACF;GACA,KAAK,IAAI,OAAO,EAAE;GAClB,IAAI,OAAO,OAAO,KAAK,GAAG;IACxB,MAAM,eAAe,oBAAoB,OAAO,MAAM;IAGtD,IAAI,CAAC,gBAAgB,OAAO,aAAa,OAAO,UAAU;KACxD,UAAU;KACV;IACF;IACA,QAAQ,QAAQ,YAAY;GAC9B;GACA,MAAM,YAAY,SAAS,OAAO,OAAO;GACzC,MAAM,YAAY,SAAS,OAAO,OAAO;GACzC,IAAI,cAAc,KAAA,GAChB,UAAU,YAAY,KAAA,IAAY,YAAY,KAAK,IAAI,SAAS,SAAS;GAE3E,IAAI,cAAc,KAAA,GAChB,UAAU,YAAY,KAAA,IAAY,YAAY,KAAK,IAAI,SAAS,SAAS;GAK3E,SACE,OAAO,YAAY,OAAO,aAAa,OAAO,KAAK,KAAK,IAAI,OAAO,QAAQ,IAAI,KAAA;EACnF;EACA,IAAI,SAAS;EACb,IAAI,YAAY,KAAA,KAAa,YAAY,KAAA,KAAa,WAAW,SAC/D;EAGF,MAAM,KAAK;GACT,IAAI,KAAK;GACT,OAAO,KAAK;GACZ,OAAO,KAAK;GACZ,QAAQ,QAAQ,WAAW,IAAI,QAAQ,KAAK,CAAC,OAAO,GAAG,OAAO;GAC9D,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;GAC3C,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;GAC3C,GAAI,WAAW,KAAK,WAAW,IAAI,EAAE,aAAa,KAAK,YAAY,IAAI,CAAC;GACxE,GAAI,OAAO,KAAK,gBAAgB,YAAY,OAAO,SAAS,KAAK,WAAW,IACxE,EAAE,aAAa,KAAK,YAAY,IAChC,CAAC;GACL,GAAI,OAAO,KAAK,gBAAgB,YAAY,OAAO,SAAS,KAAK,WAAW,IACxE,EAAE,aAAa,KAAK,YAAY,IAChC,CAAC;GACL,GAAI,OAAO,KAAK,iBAAiB,YAAY,OAAO,SAAS,KAAK,YAAY,IAC1E,EAAE,cAAc,KAAK,aAAa,IAClC,CAAC;EACP,CAAC;CACH;CACA,OAAO;EAAE;EAAO;CAAS;AAC3B;;;;;;;;;;AAWA,SAAS,gBAAgB,MAAsC;CAC7D,MAAM,aAAwB,CAAC,KAAK,MAAM;CAC1C,IAAI,KAAK,YAAY,KAAA,GACnB,WAAW,KAAK;EAAC;EAAM,CAAC,MAAM;EAAG,KAAK;CAAO,CAAC;CAEhD,IAAI,KAAK,YAAY,KAAA,GACnB,WAAW,KAAK;EAAC;EAAK,CAAC,MAAM;EAAG,KAAK;CAAO,CAAC;CAE/C,OAAO,WAAW,WAAW,IAAI,KAAK,SAAS,CAAC,OAAO,GAAG,UAAU;AACtE;AAQA,MAAM,wCAAwB,IAAI,QAAwC;;;;;;;;;;;;;;;;;;;AAoB1E,SAAgB,0BAA0B,OAAqC;CAC7E,IAAI,WAAW,OAAO,iBAAiB,MAAM,cAAc,OAAO;CAClE,MAAM,MAAM,WAAW,OAAO,aAAa;CAC3C,MAAM,SAAS,sBAAsB,IAAI,GAAG;CAC5C,IAAI,WAAW,KAAA,GAAW,OAAO;CACjC,MAAM,aAAa,IAAI,MAAM,SAAS,KAAK,MAAM;CACjD,MAAM,SACJ,CAAC,cAAc,WAAW,YAAY,QAClC,OACA,CAAC,OAAO,GAAG,qBAAqB,KAAK,CAAC,CAAC,MAAM,IAAI,eAAe,CAAC;CACvE,sBAAsB,IAAI,KAAK,MAAM;CACrC,OAAO;AACT;;;;;;;;;;;AAYA,SAAS,qBACP,OACA,OACS;CACT,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,MAAM,QAAQ,OAAO;EACxB,IAAI,KAAK,YAAY,KAAA,GAAW,OAAO,IAAI,KAAK,OAAO;EACvD,IAAI,KAAK,YAAY,KAAA,GAAW,OAAO,IAAI,KAAK,OAAO;CACzD;CACA,IAAI,OAAO,SAAS,GAAG,OAAO,MAAM,KAAK;CACzC,MAAM,SAAS,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAC/C,MAAM,YAAY,iBAChB,MAAM,QACH,UACE,KAAK,WAAW,cAAc,gBAAgB,gBAAgB,KAAK,WAAW,SACnF;CACF,MAAM,aAAwB;EAAC;EAAQ,CAAC,MAAM;EAAG,MAAM,SAAS,SAAS,CAAC;CAAC;CAC3E,KAAK,MAAM,QAAQ,QACjB,WAAW,KAAK,MAAM,MAAM,SAAS,IAAI,CAAC,CAAC;CAE7C,OAAO;AACT;;;;;;;;;;;;;;;;;AAkBA,SAAgB,yBACd,OACA,eACkB;CAClB,MAAM,EAAE,OAAO,aAAa,qBAAqB,KAAK;CACtD,MAAM,YAAY,YAAY,WAAW,SAAS,KAAK,IAAI,SAAS,QAAQ;CAC5E,IAAI,MAAM,WAAW,GAAG,OAAO;CAC/B,OAAO,qBAAqB,QAAQ,WAAW;EAC7C,IAAI,OAAO,WAAW,GAAG,OAAO;EAChC,OAAO;GAAC;GAAQ,GAAG,OAAO,SAAS,SAAS,CAAC,KAAK,QAAQ,KAAK,KAAK,CAAC;GAAG;EAAS;CACnF,CAAC;AACH;;;;;;;;;;;;;AAiBA,SAAS,kBACP,OACA,KACA,UACS;CACT,IAAI,WAAW,OAAO,iBAAiB,MAAM,cAAc,OAAO;CAClE,MAAM,EAAE,OAAO,aAAa,qBAAqB,KAAK;CACtD,MAAM,eAAe,iBAAiB,YAAY,KAAA,GAAW,GAAG;CAChE,IAAI,iBAAiB,KAAA,KAAa,CAAC,MAAM,MAAM,SAAS,KAAK,SAAS,KAAA,CAAS,GAC7E,OAAO;CAET,MAAM,YAAY,gBAAgB;CAClC,OAAO,qBAAqB,QAAQ,WAAW;EAC7C,IAAI,OAAO,WAAW,GAAG,OAAO;EAChC,OAAO;GAAC;GAAQ,GAAG,OAAO,SAAS,SAAS,CAAC,KAAK,QAAQ,KAAK,QAAQ,QAAQ,CAAC;GAAG;EAAS;CAC9F,CAAC;AACH;;AAGA,SAAS,iBACP,MACA,KAC6B;CAC7B,MAAM,QAAQ,OAAO;CACrB,IAAI,QAAQ,eACV,OAAO,WAAW,KAAK,IAAK,QAAmB,KAAA;CAEjD,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;AACvE;;;;;;AAOA,SAAgB,wBAAwB,OAAqC;CAC3E,OAAO,kBACL,OACA,eACA,WAAW,OAAO,aAAa,CACjC;AACF;;;;;;AAOA,SAAgB,uBACd,OACA,UACoB;CACpB,OAAO,kBAAkB,OAAO,eAAe,QAAQ;AACzD;;;;;;AAOA,SAAgB,uBACd,OACA,UACoB;CACpB,OAAO,kBAAkB,OAAO,eAAe,QAAQ;AACzD;;;;;;;;;;;;;AAcA,SAAgB,kBAAkB,OAAsC;CACtE,MAAM,QAAQ,WAAW,OAAO,kBAAkB;CAClD,MAAM,OAAO,mBAAmB,KAAK;CACrC,QAAQ,KAAK,UAAU,QAAQ,OAAO,mBAAmB,oBAAoB,gBAAgB,EAAA,CAC1F;AACL;;;;;;;;;;;;AAsBA,SAAgB,sBAAsB,OAAiD;CACrF,IAAI,CAAC,WAAW,OAAO,yBAAyB,GAAG,OAAO;CAC1D,OAAO,mBACL,WAAW,OAAO,0BAA0B,GAC5C,WAAW,OAAO,0BAA0B,GAC5C,WAAW,OAAO,0BAA0B,GAC5C,WAAW,OAAO,2BAA2B,GAC7C,WAAW,OAAO,2BAA2B,CAC/C;AACF;;;;;;;;AASA,SAAS,mBACP,aACA,UACA,UACA,WACA,WAC8B;CAC9B,MAAM,WAAW,YAAY,KAAK;CAClC,IAAI,CAAC,UAAU,OAAO;CACtB,IAAI,EAAE,OAAO,SAAS,QAAQ,KAAK,OAAO,SAAS,QAAQ,IAAI,OAAO;CACtE,IAAI,YAAY,UAAU,OAAO;CACjC,IAAI,EAAE,OAAO,SAAS,SAAS,KAAK,OAAO,SAAS,SAAS,IAAI,OAAO;CACxE,OAAO;EAAE;EAAU;EAAU;EAAU;EAAW;CAAU;AAC9D;;;;;;;;;;AAWA,SAAgB,6BAA6B,OAAqC;CAChF,MAAM,QAAQ,sBAAsB,KAAK;CACzC,OAAO,QAAQ,4BAA4B,KAAK,IAAI;AACtD;;;;;;;AAQA,SAAS,4BAA4B,OAAyC;CAC5E,OAAO;EACL;EACA,CAAC,QAAQ;EACT;GAAC;GAAa,CAAC,OAAO,MAAM,QAAQ;GAAG,MAAM;EAAQ;EACrD,MAAM;EACN,KAAK,IAAI,GAAG,MAAM,SAAS;EAC3B,MAAM;EACN,KAAK,IAAI,GAAG,MAAM,SAAS;CAC7B;AACF;;;;;;;;;;;AAYA,SAAgB,mBAAmB,OAAiD;CAClF,OAAO,mBACL,WAAW,OAAO,+BAA+B,GACjD,WAAW,OAAO,+BAA+B,GACjD,WAAW,OAAO,+BAA+B,GACjD,WAAW,OAAO,gCAAgC,GAClD,WAAW,OAAO,gCAAgC,CACpD;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,2BAA2B,OAAuC;CAChF,MAAM,QAAQ,mBAAmB,KAAK;CACtC,IAAI,OAAO,OAAO,4BAA4B,KAAK;CACnD,OAAO,KAAK,IAAI,GAAG,WAAW,OAAO,+BAA+B,CAAC;AACvE;;;;;;;;;;;;AAaA,SAAgB,kBAAkB,OAAuC;CAGvE,OAAO,kBAAkB,OAAO,gBAAgB,sBAAsB,KAAK,CAAC;AAG9E;;AAGA,SAAS,sBAAsB,OAAuC;CACpE,OAAO,6BAA6B,KAAK,KAAK,WAAW,OAAO,cAAc;AAChF;;AAGA,SAAgB,qBAAqB,OAAqC;CACxE,OAAO,qBACL,OACA,QACA,sBAAsB,OAAO,WAAW,OAAO,WAAW,CAAC,CAC7D;AACF;;;;;;;;AASA,SAAgB,uBAAuB,OAAqC;CAC1E,OAAO,qBACL,OACA,gBACA,sBAAsB,OAAO,WAAW,OAAO,WAAW,CAAC,CAC7D;AACF;;;;;;;;AASA,SAAgB,qBAAqB,OAAqC;CACxE,MAAM,cAAc,WAAW,OAAO,aAAa;CACnD,IAAI,WAAW,OAAO,iBAAiB,MAAM,cAC3C,OAAO,qBAAqB,OAAO,UAAU,6BAA6B,KAAK,CAAC;CAElF,MAAM,cAAc,sBAAsB,OAAO,WAAW;CAO5D,OAAO,qBAAqB,OAAO,UALjC,gBAAgB,cACZ,cACA,WAAW,OAAO,iBAAiB,MAAM,eACvC,cACA;EAAC;EAAQ;GAAC;GAAM,CAAC,eAAe;GAAG;EAAS;EAAG;EAAa;CAAW,CAC1B;AACvD;;;;;;;;;AAUA,SAAS,6BAA6B,OAAqC;CACzE,MAAM,cAAc,WAAW,OAAO,aAAa;CACnD,MAAM,EAAE,OAAO,aAAa,qBAAqB,KAAK;CACtD,MAAM,YAAY,YAAY,WAAW,SAAS,KAAK,IAAI,SAAS,QAAQ;CAC5E,MAAM,cACH,iBAAiB,YAAY,KAAA,GAAW,aAAa,KAA4B;CACpF,MAAM,qBACJ,MAAM,MAAM,SAAS,KAAK,gBAAgB,KAAA,CAAS,KAAK,gBAAgB;CAC1E,IAAI,MAAM,WAAW,KAAK,cAAc,eAAe,CAAC,oBACtD,OAAO;CAET,OAAO,qBAAqB,QAAQ,WAAW;EAC7C,MAAM,aACJ,OAAO,WAAW,IACd,YACA;GAAC;GAAQ,GAAG,OAAO,SAAS,SAAS,CAAC,KAAK,QAAQ,KAAK,KAAK,CAAC;GAAG;EAAS;EAChF,MAAM,eAAwB,CAAC,qBAC3B,cACA,OAAO,WAAW,IAChB,cACA;GACE;GACA,GAAG,OAAO,SAAS,SAAS,CAAC,KAAK,QAAQ,KAAK,eAAe,WAAW,CAAC;GAC1E;EACF;EACN,IAAI,eAAe,eAAe,iBAAiB,aACjD,OAAO;EAET,OAAO;GAAC;GAAQ;IAAC;IAAM,CAAC,eAAe;IAAG;GAAS;GAAG;GAAc;EAAU;CAChF,CAAC;AACH;;;;;;;;;;;;;;;;;AAkBA,SAAgB,qBAAqB,OAAuC;CAC1E,MAAM,cAAc,0BAA0B,KAAK;CACnD,IAAI,cAAc,GAAG;EACnB,MAAM,OAAO,mBAAmB,KAAK;EACrC,OAAO,OAAO,SAAS,WACnB,KAAK,IAAI,GAAG,IAAI,IAAI,cACpB;GAAC;GAAK;IAAC;IAAO;IAAG;GAAI;GAAG;EAAW;CACzC;CACA,MAAM,qBAAqB,oBACzB,WAAW,OAAO,+BAA+B,IAC7C,WAAW,OAAO,2BAA2B,IAC7C,EACN;CACA,IAAI,oBAAoB,OAAO;CAC/B,MAAM,WAAW,WAAW,OAAO,yBAAyB,CAAC,CAAC,KAAK;CACnE,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,QAAQ,WAAW,OAAO,sBAAsB;CACtD,OAAO;EAAC;EAAK;GAAC;GAAa,CAAC,OAAO,QAAQ;GAAG;EAAC;EAAG;CAAK;AACzD;;;;;;;;;AAUA,SAAgB,0BAA0B,OAA2B;CACnE,MAAM,QAAQ,WAAW,OAAO,sBAAsB;CACtD,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,QAAQ,IAAI,QAAQ;AACpF;;;;;;;;;;;;;AAcA,SAAgB,mBAAmB,OAAuC;CACxE,MAAM,qBAAqB,oBACzB,WAAW,OAAO,+BAA+B,IAC7C,WAAW,OAAO,yBAAyB,IAC3C,EACN;CACA,IAAI,oBAAoB,OAAO;CAC/B,MAAM,YAAY,WAAW,OAAO,eAAe;CACnD,MAAM,SAAS,OAAO,SAAS,SAAS,IAAI,YAAY;CACxD,MAAM,WAAW,WAAW,OAAO,uBAAuB,CAAC,CAAC,KAAK;CACjE,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,QAAQ,WAAW,OAAO,oBAAoB;CACpD,MAAM,SAAS;EAAC;EAAK;GAAC;GAAa,CAAC,OAAO,QAAQ;GAAG;EAAC;EAAG,OAAO,SAAS,KAAK,IAAI,QAAQ;CAAC;CAC5F,OAAO,WAAW,IAAI,SAAS;EAAC;EAAK;EAAQ;CAAM;AACrD;;;;;;;;;;;AAYA,SAAgB,oBAAoB,OAAqC;CACvE,MAAM,OAAO,WAAW,OAAO,gBAAgB;CAC/C,MAAM,mBAAmB,sBAAsB,OAAO,IAAI;CAC1D,IAAI,qBAAqB,MAAM,OAAO;CAMtC,OAL2B,oBACzB,WAAW,OAAO,+BAA+B,IAC7C,WAAW,OAAO,0BAA0B,IAC5C,EAEmB,KAAiC;AAC5D;;;AC7qCA,MAAa,eAAe;;AAG5B,MAAa,4BACX;;AAGF,MAAM,yCAAyB,IAAI,IAAI;CAAC;CAAS;CAAU;CAAQ;CAAO;CAAO;AAAS,CAAC;AAE3F,MAAM,gBAAgB;AACtB,MAAM,oBAAoB;AAC1B,MAAM,oBACJ;;AAEF,MAAM,uBAAuB;;;;;;;AAQ7B,SAAS,YAAY,OAAoC;CACvD,IAAI,OAAO,UAAU,UAAU,OAAO,KAAA;CACtC,MAAM,UAAU,MAAM,KAAK;CAC3B,OAAO,YAAY,KAAK,KAAA,IAAY;AACtC;;;;;;;AAQA,SAAgB,sBAAsB,MAAwB;CAC5D,OAAO,kBAAkB,KAAK,MAAM,GAAG,CAAC;AAC1C;AAEA,SAAS,kBAAkB,OAA0B;CACnD,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,WAAqB,CAAC;CAC5B,KAAK,MAAM,SAAS,OAAO;EACzB,MAAM,UAAU,YAAY,KAAK;EACjC,IAAI,CAAC,SAAS;EACd,MAAM,MAAM,QAAQ,YAAY;EAChC,IAAI,KAAK,IAAI,GAAG,GAAG;EACnB,KAAK,IAAI,GAAG;EACZ,SAAS,KAAK,OAAO;CACvB;CACA,OAAO;AACT;AAEA,SAAS,iBAAiB,OAAkD;CAC1E,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO,KAAA;CACxE,MAAM,MAAM;CACZ,MAAM,UAAgC,CAAC;CACvC,MAAM,OAAO,YAAY,IAAI,IAAI;CACjC,MAAM,QAAQ,YAAY,IAAI,KAAK;CACnC,MAAM,eAAe,YAAY,IAAI,YAAY;CACjD,IAAI,MAAM,QAAQ,OAAO;CACzB,IAAI,OAAO,QAAQ,QAAQ;CAC3B,IAAI,cAAc,QAAQ,eAAe;CACzC,OAAO,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,IAAI,UAAU,KAAA;AACrD;AAEA,SAAS,wBAAwB,OAAyD;CACxF,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO,KAAA;CACxE,MAAM,MAAM;CACZ,MAAM,SAAsC,CAAC;CAC7C,MAAM,QAAQ,YAAY,IAAI,KAAK;CACnC,MAAM,MAAM,YAAY,IAAI,GAAG;CAC/B,IAAI,OAAO,OAAO,QAAQ;CAC1B,IAAI,KAAK,OAAO,MAAM;CACtB,OAAO,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,IAAI,SAAS,KAAA;AACnD;AAEA,SAAS,eAAe,OAAqC;CAC3D,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,QAA6B,CAAC;CACpC,KAAK,MAAM,SAAS,OAAO;EACzB,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;EACjE,MAAM,MAAM;EACZ,MAAM,OAAO,YAAY,IAAI,IAAI;EAEjC,IAAI,CAAC,MAAM;EACX,MAAM,OAA0B,EAAE,KAAK;EACvC,MAAM,MAAM,YAAY,IAAI,GAAG;EAC/B,MAAM,QAAQ,YAAY,IAAI,KAAK;EACnC,IAAI,KAAK,KAAK,MAAM;EACpB,IAAI,OAAO,KAAK,QAAQ;EACxB,MAAM,KAAK,IAAI;CACjB;CACA,OAAO;AACT;;;;;;;;;;;;;AAcA,SAAgB,kCACd,OACsC;CACtC,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO,KAAA;CACxE,MAAM,MAAM;CACZ,MAAM,WAAqC,CAAC;CAC5C,KAAK,MAAM,OAAO;EAAC;EAAS;EAAY;EAAW;EAAe;CAAS,GAAY;EACrF,MAAM,OAAO,YAAY,IAAI,IAAI;EACjC,IAAI,MAAM,SAAS,OAAO;CAC5B;CACA,MAAM,WAAW,kBAAkB,IAAI,QAAQ;CAC/C,IAAI,SAAS,SAAS,GAAG,SAAS,WAAW;CAC7C,MAAM,UAAU,iBAAiB,IAAI,OAAO;CAC5C,IAAI,SAAS,SAAS,UAAU;CAChC,MAAM,iBAAiB,wBAAwB,IAAI,cAAc;CACjE,IAAI,gBAAgB,SAAS,iBAAiB;CAC9C,MAAM,QAAQ,eAAe,IAAI,KAAK;CACtC,IAAI,MAAM,SAAS,GAAG,SAAS,QAAQ;CACvC,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,IAAI,WAAW,KAAA;AACvD;;;;;;;AAQA,SAAgB,qBAAqB,OAAwB;CAC3D,OAAO,cAAc,KAAK,MAAM,KAAK,CAAC;AACxC;;;;;;;;AASA,SAAgB,mBAAmB,OAAwB;CACzD,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,MAAM,KAAK,CAAC;EAChC,OAAO,uBAAuB,IAAI,IAAI,QAAQ;CAChD,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;AAUA,SAAS,WAAW,MAAc,OAAe,KAAsB;CAErE,MAAM,uBAAO,IAAI,KAAK,CAAC;CACvB,KAAK,eAAe,MAAM,QAAQ,GAAG,GAAG;CACxC,OACE,KAAK,eAAe,MAAM,QAAQ,KAAK,YAAY,MAAM,QAAQ,KAAK,KAAK,WAAW,MAAM;AAEhG;;;;;;;;AASA,SAAgB,oBAAoB,OAAwB;CAC1D,MAAM,OAAO,MAAM,KAAK;CACxB,MAAM,WAAW,kBAAkB,KAAK,IAAI;CAC5C,IAAI,UAAU,OAAO,WAAW,OAAO,SAAS,EAAE,GAAG,OAAO,SAAS,EAAE,GAAG,OAAO,SAAS,EAAE,CAAC;CAC7F,MAAM,WAAW,kBAAkB,KAAK,IAAI;CAC5C,IAAI,CAAC,UAAU,OAAO;CACtB,IAAI,CAAC,WAAW,OAAO,SAAS,EAAE,GAAG,OAAO,SAAS,EAAE,GAAG,OAAO,SAAS,EAAE,CAAC,GAAG,OAAO;CACvF,MAAM,QAAQ,OAAO,SAAS,EAAE;CAChC,MAAM,UAAU,OAAO,SAAS,EAAE;CAClC,MAAM,UAAU,SAAS,OAAO,KAAA,IAAY,IAAI,OAAO,SAAS,EAAE;CAClE,OAAO,QAAQ,MAAM,UAAU,MAAM,UAAU;AACjD;;;;;;;;;;;AAYA,SAAgB,sBAAsB,OAAe,OAAuC;CAC1F,MAAM,OAAO,MAAM,KAAK;CACxB,IAAI,CAAC,oBAAoB,IAAI,GAAG,OAAO;CACvC,IAAI,kBAAkB,KAAK,IAAI,GAC7B,OAAO,GAAG,KAAK,GAAG,UAAU,UAAU,aAAa,WAAW;CAMhE,MAAM,aAAa,KAChB,YAAY,CAAC,CACb,QAAQ,KAAK,GAAG,CAAC,CACjB,QAAQ,uBAAuB,OAAO;CACzC,MAAM,YAAY,uBAAuB,KAAK,UAAU;CACxD,MAAM,SAAS,IAAI,KAAK,YAAY,aAAa,GAAG,WAAW,EAAE;CACjE,OAAO,OAAO,MAAM,OAAO,QAAQ,CAAC,IAAI,OAAO,OAAO,YAAY;AACpE;;;;;;;;;AAuBA,SAAgB,iCACd,UACsB;CACtB,MAAM,SAA+B,CAAC;CACtC,IAAI,CAAC,UAAU,OAAO;CACtB,MAAM,QAAQ,YAAY,SAAS,SAAS,KAAK;CACjD,IAAI,SAAS,CAAC,qBAAqB,KAAK,GAAG,OAAO,KAAK;EAAE,OAAO;EAAiB,MAAM;CAAQ,CAAC;CAChG,MAAM,QAAQ,YAAY,SAAS,gBAAgB,KAAK;CACxD,MAAM,MAAM,YAAY,SAAS,gBAAgB,GAAG;CACpD,MAAM,aAAa,QAAQ,oBAAoB,KAAK,IAAI;CACxD,MAAM,WAAW,MAAM,oBAAoB,GAAG,IAAI;CAClD,IAAI,CAAC,YAAY,OAAO,KAAK;EAAE,OAAO;EAAwB,MAAM;CAAO,CAAC;CAC5E,IAAI,CAAC,UAAU,OAAO,KAAK;EAAE,OAAO;EAAsB,MAAM;CAAO,CAAC;CACxE,IAAI,SAAS,OAAO,cAAc,UACd;MAAA,KAAK,MAAM,sBAAsB,OAAO,OAAO,KAAK,EAE1D,IADI,KAAK,MAAM,sBAAsB,KAAK,KAAK,KAAK,EAC1C,GAAG,OAAO,KAAK;GAAE,OAAO;GAAsB,MAAM;EAAY,CAAC;CAAA;CAEzF,CAAC,SAAS,SAAS,CAAC,EAAA,CAAG,SAAS,MAAM,UAAU;EAC9C,MAAM,OAAO,YAAY,MAAM,IAAI;EACnC,IAAI,QAAQ,CAAC,mBAAmB,IAAI,GAClC,OAAO,KAAK;GAAE,OAAO,SAAS,MAAM;GAAQ,MAAM;EAAM,CAAC;CAE7D,CAAC;CACD,OAAO;AACT;;;;;;;AAuDA,SAAS,UACP,MACyC;CACzC,IAAI,CAAC,QAAQ,KAAK,WAAW,GAAG,OAAO;CACvC,IAAI,CAAC,KAAK,OAAO,UAAU,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,CAAC,GAAG,OAAO;CACxF,MAAM,CAAC,MAAM,OAAO,MAAM,SAAS;CACnC,IAAI,QAAQ,OAAO,QAAQ,MAAM,QAAQ,OAAO,OAAO;CACvD,IAAI,OAAO,QAAQ,OAAO,OAAO,OAAO,QAAQ,OAAO,KAAK,OAAO;CACnE,OAAO;EAAC;EAAM;EAAO;EAAM;CAAK;AAClC;;;;;;;;;AAUA,SAAS,aAAa,CAAC,MAAM,OAAO,MAAM,QAGxC;CACA,IAAI,SAAS,QAAQ,UAAU,OAAO,OAAO;EAAE,MAAM;EAAS,aAAa,CAAC,MAAM,KAAK;CAAE;CAGzF,IAAI,SAAS,QAAQ,UAAU,OAC7B,OAAO;EACL,MAAM;EACN,aAAa,CACX,CAAC,MAAM,KAAK,GACZ,CAAC,MAAM,KAAK,CACd;CACF;CAEF,MAAM,QAAQ,GAAW,MAAc;EACrC,CAAC,GAAG,KAAK;EACT,CAAC,GAAG,KAAK;EACT,CAAC,GAAG,KAAK;EACT,CAAC,GAAG,KAAK;EACT,CAAC,GAAG,KAAK;CACX;CACA,IAAI,OAAO,MACT,OAAO;EAAE,MAAM;EAAgB,aAAa,CAAC,CAAC,KAAK,MAAM,GAAG,CAAC,GAAG,CAAC,KAAK,MAAM,IAAI,CAAC,CAAC;CAAE;CAEtF,OAAO;EAAE,MAAM;EAAW,aAAa,CAAC,KAAK,MAAM,IAAI,CAAC;CAAE;AAC5D;;AAGA,MAAM,oBAA4C;CAChD,SAAS;CACT,MAAM;CACN,KAAK;CACL,MAAM;CACN,SAAS;CACT,YAAY;CACZ,KAAK;CACL,SAAS;CACT,MAAM;CACN,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,KAAK;CACL,MAAM;AACR;;;;;;;;;AAUA,SAAgB,mBACd,MACA,WACoB;CACpB,IAAI,OAAO;CACX,IAAI;EACF,OAAO,IAAI,IAAI,IAAI,CAAC,CAAC;CACvB,QAAQ,CAER;CACA,MAAM,YAAY,kBAAkB,KAAK,KAAK,QAAQ,QAAQ,EAAE,CAAC,CAAC,GAAG,EAAE,EAAE,YAAY;CACrF,IAAI,cAAc,UAAU,cAAc,SAAS,cAAc,UAAU,CAAC,YAC1E,OAAO;CAET,IAAI,aAAa,kBAAkB,YAAY,OAAO,kBAAkB;CACxE,IAAI,cAAc,aAAa,CAAC,WAAW,OAAO;AAEpD;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,mBACd,OACA,UAAgC,CAAC,GACvB;CACV,MAAM,WAAW,kCAAkC,MAAM,mBAAmB,KAAK,CAAC;CAClF,MAAM,MAAM,QAAQ,uBAAO,IAAI,KAAK;CACpC,MAAM,aAAqC,EAAE,UAAU,KAAK;CAC5D,WAAW,QAAQ,SAAS,SAAS,MAAM;CAC3C,IAAI,SAAS,UAAU,WAAW,cAAc,SAAS;CACzD,IAAI,SAAS,UAAU,QAAQ,WAAW,WAAW,CAAC,GAAG,SAAS,QAAQ;CAE1E,MAAM,QAAQ,SAAS,gBAAgB,QACnC,sBAAsB,SAAS,eAAe,OAAO,OAAO,IAC5D;CACJ,MAAM,MAAM,SAAS,gBAAgB,MACjC,sBAAsB,SAAS,eAAe,KAAK,KAAK,IACxD;CAGJ,IAAI,SAAS,OAAO,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,GAAG,GAAG;EACzD,WAAW,iBAAiB;EAC5B,WAAW,eAAe;CAC5B,OAIE,WAAW,WAAW,SAAS,OAAO,IAAI,YAAY;CAGxD,IAAI,SAAS,SAAS;EACpB,IAAI,qBAAqB,KAAK,SAAS,OAAO,GAC5C,WAAW,UAAU,SAAS;OACzB;GACL,WAAW,UAAU;GACrB,WAAW,sBAAsB,SAAS;EAC5C;CACF;CACA,MAAM,UAAU,SAAS;CACzB,MAAM,eAAe,SAAS,gBAAgB,SAAS,QAAQ,SAAS;CACxE,IAAI,WAAW,cAAc;EAC3B,MAAM,SAAS,CACb,QAAQ,gBAAgB,QAAQ,OAAO,QAAQ,OAAO,KAAA,GACtD,QAAQ,KACV,CAAC,CAAC,OAAO,OAAO;EAChB,MAAM,WAAyB;GAAE,MAAM;GAAc,OAAO,CAAC,UAAU;EAAE;EACzE,IAAI,OAAO,SAAS,GAAG,SAAS,cAAc,YAAY,OAAO,KAAK,IAAI;EAC1E,WAAW,YAAY,CAAC,QAAQ;CAClC;CACA,IAAI,SAAS,aAAa,WAAW,0BAA0B,SAAS;CAExE,MAAM,aAAuB,CAAC;CAC9B,IAAI,SAAS,SAAS;EACpB,WAAW,wBAAwB,SAAS;EAC5C,WAAW,KAAK,yBAAyB;CAC3C;CAEA,MAAM,SAAqB,SAAS,SAAS,CAAC,EAAA,CAC3C,QAAQ,SAAS,mBAAmB,KAAK,IAAI,CAAC,CAAC,CAC/C,KAAK,UAAU;EACd,MAAM,KAAK;EACX,KAAK,KAAK,OAAO;EACjB,GAAI,KAAK,QAAQ,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;CAC5C,EAAE;CAEJ,MAAM,SAAoC,CAAC;CAC3C,MAAM,YAAY,YAAY,QAAQ,SAAS;CAC/C,IAAI,WAAW;EAGb,MAAM,YAAY,cAAc,KAAK,SAAS;EAC9C,MAAM,OAAO,YAAY,KAAA,IAAY,mBAAmB,WAAW,MAAM,IAAI;EAC7E,OAAO,YAAY,UAAU,UAAU;GACrC,MAAM;GACN,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GACvB,OAAO,WAAW;GAClB,OAAO,CAAC,YAAY,UAAU,MAAM;EACtC;CACF;CAEA,MAAM,OAAO,UAAU,QAAQ,IAAI;CACnC,OAAO;EACL,MAAM;EACN,cAAc;EACd,iBAAiB;EACjB,IAAI,MAAM;EACV,UAAU,OAAO,aAAa,IAAI,IAAI;EACtC,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EACvB;EACA;EACA;CACF;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC7eA,SAAgB,yBACd,UACA,QACW;CACX,MAAM,0BAAU,IAAI,IAAY;CAChC,KAAK,MAAM,SAAS,UAAU,CAAC,GAC7B,IAAI,MAAM,YAAY,QAAQ,IAAI,MAAM,UAAU;CAEpD,IAAI,QAAQ,SAAS,GAAG,OAAO;CAC/B,OAAO,SAAS,KAAK,YAAY;EAC/B,MAAM,QAAQ,QAAQ;EACtB,IAAI,CAAC,OAAO,OAAO;EACnB,IAAI,aAAa;EACjB,KAAK,MAAM,OAAO,SAChB,IAAI,OAAO,OAAO;GAChB,aAAa;GACb;EACF;EAEF,IAAI,CAAC,YAAY,OAAO;EACxB,MAAM,OAA0B,CAAC;EACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG,KAAK,OAAO;EAErC,OAAO;GAAE,GAAG;GAAS,YAAY;EAAK;CACxC,CAAC;AACH;;;;;;;AAeA,SAAS,uBAAuB,OAAyB;CACvD,IAAI,UAAU,KAAA,GAAW,OAAO;CAChC,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;CACjE,IAAI,iBAAiB,KAAK,GAAG,OAAO,6BAA6B,KAAK;CACtE,OAAO;AACT;;;;;;;;;;;;AAaA,SAAgB,wBACd,UACA,QAC0B;CAC1B,MAAM,YAAY,UAAU,CAAC;CAC7B,MAAM,oBAAoB,WAAiD;EACzE,GAAG;EACH,YAAY,KAAA;EACZ,OAAO,KAAA;EACP,YAAY,KAAA;CACd;CAOA,IADe,UAAU,QAAQ,UAAU,MAAM,YAAY,KACpD,CAAC,CAAC,WAAW,GACpB,OAAO;EAAE;EAAU,QAAQ,UAAU,IAAI,gBAAgB;CAAE;CAK7D,MAAM,4BAAY,IAAI,IAAY;CAClC,KAAK,MAAM,WAAW,UACpB,KAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,cAAc,CAAC,CAAC,GAAG,UAAU,IAAI,GAAG;CAI5E,MAAM,MAAM,SAAS,KAAK,aAAa;EACrC,GAAG;EACH,YAAY,EAAE,GAAI,QAAQ,cAAc,CAAC,EAAG;CAC9C,EAAE;CAiCF,OAAO;EAAE,UAAU;EAAK,QA/BN,UAAU,KAAK,UAA6B;GAC5D,IAAI,MAAM,YAAY,OAAO,OAAO,iBAAiB,KAAK;GAC1D,MAAM,OAAO,MAAM,KAAK,KAAK;GAC7B,IAAI,CAAC,QAAQ,UAAU,IAAI,IAAI,GAAG,OAAO,iBAAiB,KAAK;GAE/D,MAAM,WAAW,yBAAyB,MAAM,UAAU;GAC1D,IAAI,CAAC,SAAS,MAAM,CAAC,SAAS,UAC5B,OAAO;IACL,GAAG,iBAAiB,KAAK;IACzB,OAAO,SAAS,OAAO,MAAM;GAC/B;GAGF,UAAU,IAAI,IAAI;GAClB,IAAI,aAAa;GACjB,KAAK,MAAM,WAAW,KAAK;IACzB,IAAI,QAAiB;IACrB,IAAI;KACF,QAAQ,uBAAuB,SAAS,SAAS,OAAO,CAAC;IAC3D,QAAQ;KACN,cAAc;IAChB;IACA,QAAQ,WAAW,QAAQ;GAC7B;GACA,OAAO;IACL,GAAG,iBAAiB,KAAK;IACzB,YAAY;IACZ,GAAI,aAAa,IAAI,EAAE,WAAW,IAAI,CAAC;GACzC;EACF,CAEwC;CAAE;AAC5C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACzIA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,IAAI,MAAM,IAAI,eAAe,CAAC,CAAC,KAAK,GAAG,EAAE;CAElD,IAAI,UAAU,QAAQ,OAAO,UAAU,UAIrC,OAAO,IAHS,OAAO,QAAQ,KAAgC,CAAC,CAC7D,MAAM,CAAC,IAAI,CAAC,OAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,CAAE,CAAC,CAChD,KAAK,CAAC,GAAG,OAAO,GAAG,KAAK,UAAU,CAAC,EAAE,GAAG,gBAAgB,CAAC,GAC3C,CAAC,CAAC,KAAK,GAAG,EAAE;CAE/B,OAAO,KAAK,UAAU,KAAK,KAAK;AAClC;AAEA,SAAS,cAAc,OAAwB;CAC7C,IAAI,OAAO,UAAU,WAAW,OAAO,QAAQ,SAAS;CACxD,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO,gBAAgB,KAAK;CAC7E,OAAO,OAAO,KAAK;AACrB;;;;;;;;AASA,SAAgB,aAAa,OAA+B;CAM1D,OAJE,UAAU,QACV,UAAU,KAAA,KACT,OAAO,UAAU,YAAY,OAAO,MAAM,KAAK,KAChD,cAAc,KAAK,MAAM,KACV,OAAO,cAAc,KAAK;AAC7C;;AAGA,SAAS,WAAW,YAA+B,OAAwB;CACzE,OAAO,cAAc,OAAO,OAAO,YAAY,KAAK,IAAI,WAAW,SAAS,KAAA;AAC9E;;;;;;AAOA,SAAgB,gBAAgB,UAAqB,OAA2C;CAC9F,MAAM,0BAAU,IAAI,IAAY;CAChC,KAAK,MAAM,QAAQ,SAAS,CAAC,GAC3B,KAAK,MAAM,SAAS,KAAK,eAAe,CAAC,GAAG,QAAQ,IAAI,KAAK;CAE/D,IAAI,QAAQ,SAAS,GAAG,OAAO;CAC/B,OAAO,SAAS,KAAK,YAAY;EAC/B,MAAM,QAAQ,QAAQ;EACtB,IAAI,CAAC,OAAO,OAAO;EACnB,IAAI,aAAa;EACjB,KAAK,MAAM,OAAO,SAChB,IAAI,OAAO,OAAO;GAChB,aAAa;GACb;EACF;EAEF,IAAI,CAAC,YAAY,OAAO;EACxB,MAAM,OAA0B,CAAC;EACjC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,IAAI,CAAC,QAAQ,IAAI,GAAG,GAAG,KAAK,OAAO;EAErC,OAAO;GAAE,GAAG;GAAS,YAAY;EAAK;CACxC,CAAC;AACH;;;;;;;;;;;;;;;;;;;AA2BA,SAAgB,gBACd,UACA,OACA,eACuB;CACvB,MAAM,WAAW,SAAS,CAAC;CAG3B,MAAM,2BAAW,IAAI,IAA2C;CAChE,MAAM,eAAe,gBAAuD;EAC1E,IAAI,CAAC,SAAS,IAAI,WAAW,GAC3B,SAAS,IAAI,aAAa,cAAc,WAAW,CAAC;EAEtD,OAAO,SAAS,IAAI,WAAW;CACjC;CAEA,IADe,SAAS,QAAQ,SAAS,KAAK,YAAY,SAAS,YAAY,KAAK,WAAW,CACtF,CAAC,CAAC,WAAW,GACpB,OAAO;EACL;EACA,OAAO,SAAS,KAAK,UAAU;GAC7B,GAAG;GACH,aAAa,CAAC;GACd,OAAO,KAAA;EACT,EAAE;CACJ;CAIF,MAAM,MAAM,SAAS,KAAK,aAAa;EACrC,GAAG;EACH,YAAY,EAAE,GAAI,QAAQ,cAAc,CAAC,EAAG;CAC9C,EAAE;CAIF,MAAM,4BAAY,IAAI,IAAY;CAClC,KAAK,MAAM,WAAW,UACpB,KAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,cAAc,CAAC,CAAC,GAAG,UAAU,IAAI,GAAG;CAgF5E,OAAO;EAAE,UAAU;EAAK,OA7EP,SAAS,KAAK,SAAoB;GACjD,MAAM,SAAS,KAAK,YAAY,QAAQ,KAAA,IAAY,YAAY,KAAK,WAAW;GAChF,IAAI,CAAC,QAAQ,OAAO;IAAE,GAAG;IAAM,aAAa,CAAC;IAAG,OAAO,KAAA;GAAU;GAEjE,MAAM,gBAAgB,OAAO,YAAY,CAAC,EAAA,CAAG,OAAO,OAAO;GAG3D,MAAM,gBAA0B,CAAC;GACjC,MAAM,6BAAa,IAAI,IAAY;GACnC,KAAK,MAAM,MAAM,cACf,KAAK,MAAM,OAAO,OAAO,KAAK,GAAG,cAAc,CAAC,CAAC,GAC/C,IAAI,CAAC,WAAW,IAAI,GAAG,GAAG;IACxB,WAAW,IAAI,GAAG;IAClB,cAAc,KAAK,GAAG;GACxB;GAMJ,MAAM,YACJ,KAAK,WAAW,KAAA,IACZ,KAAK,OAAO,QAAQ,UAAU,WAAW,IAAI,KAAK,CAAC,IACnD,cAAc,QAAQ,QAAQ,QAAQ,KAAK,SAAS;GAC1D,MAAM,SAAS,KAAK,UAAU;GAC9B,MAAM,aAAsD,CAAC;GAC7D,KAAK,MAAM,SAAS,WAAW;IAC7B,MAAM,aAAa,SAAS;IAC5B,IAAI,UAAU,IAAI,UAAU,GAAG;IAC/B,UAAU,IAAI,UAAU;IACxB,WAAW,KAAK,CAAC,OAAO,UAAU,CAAC;GACrC;GAGA,MAAM,yBAAS,IAAI,IAA+B;GAClD,KAAK,MAAM,MAAM,cAAc;IAC7B,MAAM,MAAM,aAAa,WAAW,GAAG,YAAY,KAAK,SAAS,CAAC;IAClE,IAAI,QAAQ,QAAQ,OAAO,IAAI,GAAG,GAAG;IACrC,OAAO,IAAI,KAAK,GAAG,cAAc,CAAC,CAAC;GACrC;GAEA,IAAI,UAAU;GACd,MAAM,6BAAa,IAAI,IAAY;GACnC,KAAK,MAAM,WAAW,KAAK;IACzB,MAAM,QAAQ,QAAQ;IACtB,MAAM,MAAM,aAAa,WAAW,OAAO,KAAK,WAAW,CAAC;IAC5D,IAAI,QAAQ,MAAM,WAAW,IAAI,GAAG;IACpC,MAAM,MAAM,QAAQ,OAAO,KAAA,IAAY,OAAO,IAAI,GAAG;IACrD,IAAI,KAAK;KACP,WAAW;KACX,KAAK,MAAM,CAAC,OAAO,eAAe,YAChC,MAAM,cAAc,WAAW,KAAK,KAAK,KAAK;IAElD,OACE,KAAK,MAAM,GAAG,eAAe,YAAY,MAAM,cAAc;GAEjE;GAIA,IAAI,gBAAgB;GACpB,KAAK,MAAM,MAAM,cAAc;IAC7B,MAAM,MAAM,aAAa,WAAW,GAAG,YAAY,KAAK,SAAS,CAAC;IAClE,IAAI,QAAQ,QAAQ,CAAC,WAAW,IAAI,GAAG,GAAG,iBAAiB;GAC7D;GAEA,OAAO;IACL,GAAG;IACH,aAAa,WAAW,KAAK,GAAG,gBAAgB,UAAU;IAC1D,OAAO;KACL,cAAc;KACd,sBAAsB,IAAI,SAAS;KACnC,oBAAoB;IACtB;GACF;EACF,CAEsC;CAAE;AAC1C;;;;;;;;;;AAWA,SAAgB,kBACd,OACA,WACA,WACA,mBACe;CACf,MAAM,UAAU,MAAM;CACtB,MAAM,QAAQ,aAAa,MAAM,SAAS,CAAC;CAC3C,MAAM,gBAAgB,qBAAqB,MAAM,iBAAiB,CAAC;CACnE,IAAI,CAAC,SACH,OAAO;EACL,GAAG;EACH,OAAO,MAAM,SAAS,IAAI,QAAQ,KAAA;EAClC,eAAe,cAAc,SAAS,IAAI,gBAAgB,KAAA;CAC5D;CASF,MAAM,UAAU,gBAJH,gBACX,yBAAyB,QAAQ,YAAY,CAAC,GAAG,MAAM,aAAa,GACpE,MAAM,KAE2B,GAAG,QAAQ,gBAAgB;EAC5D,IAAI,gBAAgB,MAAM,IAAI,OAAO,KAAA;EACrC,OAAO,UAAU,MAAM,cAAc,UAAU,OAAO,WAAW,CAAC,EAAE;CACtE,CAAC;CACD,MAAM,cAAc,wBAAwB,QAAQ,UAAU,aAAa;CAC3E,MAAM,EAAE,UAAU,gBAAgB,GAAG,SAAS;CAC9C,OAAO;EACL,GAAG;EACH,SAAS;GAAE,GAAG;GAAS,UAAU,YAAY;EAAS;EACtD,OAAO,QAAQ,MAAM,SAAS,IAAI,QAAQ,QAAQ,KAAA;EAClD,eAAe,YAAY,OAAO,SAAS,IAAI,YAAY,SAAS,KAAA;CACtE;AACF;;;;;;;;;;AAWA,SAAgB,+BACd,QACA,SACa;CACb,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;CAC7D,MAAM,0BAAU,IAAI,IAAY;CAChC,MAAM,QAAQ,CAAC,OAAO;CACtB,OAAO,MAAM,SAAS,GAAG;EACvB,MAAM,UAAU,KAAK,IAAI,MAAM,MAAM,CAAW;EAChD,KAAK,MAAM,QAAQ,SAAS,SAAS,CAAC,GAAG;GACvC,IAAI,QAAQ,IAAI,KAAK,WAAW,GAAG;GACnC,QAAQ,IAAI,KAAK,WAAW;GAC5B,MAAM,KAAK,KAAK,WAAW;EAC7B;CACF;CACA,OAAO;AACT;;;;;;;;;;AAWA,SAAS,qBAAqB,YAAuC;CACnE,MAAM,eAAe,IAAI,IAAI,WAAW,KAAK,UAAU,MAAM,EAAE,CAAC;CAChE,MAAM,2BAAW,IAAI,IAAoB;CACzC,MAAM,6BAAa,IAAI,IAAsB;CAC7C,MAAM,4BAAY,IAAI,IAAsB;CAC5C,KAAK,MAAM,SAAS,YAAY,SAAS,IAAI,MAAM,IAAI,CAAC;CACxD,KAAK,MAAM,SAAS,YAClB,KAAK,MAAM,QAAQ,MAAM,SAAS,CAAC,GAAG;EACpC,IACE,KAAK,YAAY,SACjB,KAAK,gBAAgB,MAAM,MAC3B,CAAC,aAAa,IAAI,KAAK,WAAW,GAElC;EAEF,SAAS,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,EAAE,KAAK,KAAK,CAAC;EACxD,MAAM,OAAO,WAAW,IAAI,KAAK,WAAW,KAAK,CAAC;EAClD,KAAK,KAAK,MAAM,EAAE;EAClB,WAAW,IAAI,KAAK,aAAa,IAAI;EACrC,MAAM,UAAU,UAAU,IAAI,MAAM,EAAE,KAAK,CAAC;EAC5C,QAAQ,KAAK,KAAK,WAAW;EAC7B,UAAU,IAAI,MAAM,IAAI,OAAO;CACjC;CAEF,MAAM,QAAkB,CAAC;CACzB,MAAM,6BAAa,IAAI,IAAY;CACnC,MAAM,QAAQ,WAAW,QAAQ,UAAU,SAAS,IAAI,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC,KAAK,UAAU,MAAM,EAAE;CAChG,OAAO,MAAM,SAAS,WAAW,QAAQ;EACvC,IAAI,MAAM,WAAW,GAAG;GAItB,MAAM,QAAQ,WAAW,MAAM,UAAU,CAAC,WAAW,IAAI,MAAM,EAAE,CAAC;GAClE,IAAI,CAAC,OAAO;GACZ,IAAI,SAAS,MAAM;GACnB,MAAM,yBAAS,IAAI,IAAY;GAC/B,OAAO,CAAC,OAAO,IAAI,MAAM,GAAG;IAC1B,OAAO,IAAI,MAAM;IACjB,MAAM,QAAQ,UAAU,IAAI,MAAM,KAAK,CAAC,EAAA,CAAG,MAAM,WAAW,CAAC,WAAW,IAAI,MAAM,CAAC;IACnF,IAAI,SAAS,KAAA,GAAW;IACxB,SAAS;GACX;GACA,MAAM,KAAK,MAAM;EACnB;EACA,MAAM,KAAK,MAAM,MAAM;EACvB,IAAI,WAAW,IAAI,EAAE,GAAG;EACxB,WAAW,IAAI,EAAE;EACjB,MAAM,KAAK,EAAE;EACb,KAAK,MAAM,aAAa,WAAW,IAAI,EAAE,KAAK,CAAC,GAAG;GAChD,MAAM,aAAa,SAAS,IAAI,SAAS,KAAK,KAAK;GACnD,SAAS,IAAI,WAAW,SAAS;GACjC,IAAI,cAAc,KAAK,CAAC,WAAW,IAAI,SAAS,GAAG,MAAM,KAAK,SAAS;EACzE;CACF;CACA,OAAO;AACT;;;;;;;;;AAUA,SAAgB,wBAAwB,QAAyB,QAAiC;CAEhG,MAAM,2BAAW,IAAI,IAAY;CACjC,MAAM,QAAQ,CAAC,MAAM;CACrB,OAAO,MAAM,SAAS,GAAG;EACvB,MAAM,WAAW,MAAM,MAAM;EAC7B,KAAK,MAAM,SAAS,QAAQ;GAC1B,IAAI,MAAM,OAAO,UAAU,SAAS,IAAI,MAAM,EAAE,GAAG;GACnD,IAAI,MAAM,OAAO,MAAM,SAAS,KAAK,YAAY,SAAS,KAAK,gBAAgB,QAAQ,GAAG;IACxF,SAAS,IAAI,MAAM,EAAE;IACrB,MAAM,KAAK,MAAM,EAAE;GACrB;EACF;CACF;CACA,IAAI,SAAS,SAAS,GAAG,OAAO;CAEhC,IAAI,UAAU;CACd,MAAM,QAAQ,qBAAqB,OAAO,QAAQ,UAAU,SAAS,IAAI,MAAM,EAAE,CAAC,CAAC;CACnF,KAAK,MAAM,MAAM,OACf,UAAU,QAAQ,KAAK,UAAW,MAAM,OAAO,KAAK,kBAAkB,OAAO,OAAO,IAAI,KAAM;CAEhG,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,kBAAkB,QAA0C;CAC1E,MAAM,SAAS,OAAO,QAAQ,UAAU,MAAM,OAAO,UAAU,MAAM,eAAe,MAAM;CAC1F,IAAI,OAAO,WAAW,GAAG,OAAO;CAEhC,IAAI,UAAU;CACd,KAAK,MAAM,MAAM,qBAAqB,MAAM,GAC1C,UAAU,QAAQ,KAAK,UAAW,MAAM,OAAO,KAAK,kBAAkB,OAAO,OAAO,IAAI,KAAM;CAEhG,OAAO;AACT;;;;;;;;;;;AC1aA,SAAgB,uBAAuB,OAAmD;CACxF,MAAM,WAAW,MAAM,SAAS;CAChC,MAAM,WACJ,MAAM,UAAU,eAAe,kBAC9B,MAAM,SAAS,aAAa,OAAO,MAAM,UAAU,UAAU;CAChE,MAAM,mBAAmB,MAAM,UAAU,wBAAwB;CACjE,MAAM,aAAa,MAAM,UAAU,eAAe;CAClD,MAAM,aAAa,MAAM,UAAU,eAAe;CAElD,MAAM,mBAAmB,oBAAoB,MAAM,UAAU,eAAe;CAC5E,MAAM,aAAa,YAAY,CAAC,YAAY,CAAC,oBAAoB,CAAC,cAAc,CAAC;CAEjF,OAAO;EACL,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;CACV;AACF;;;;;AAMA,SAAgB,yBACd,OAC6B;CAC7B,IAAI,CAAC,OACH,OAAO;EACL,OAAO;EACP,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;CACV;CAGF,MAAM,WAAW,uBAAuB,KAAK;CAC7C,IAAI,CAAC,MAAM,cACT,OAAO;CAGT,OAAO;EACL,OAAO,MAAM,aAAa,SAAS,SAAS;EAC5C,QAAQ,MAAM,aAAa,UAAU,SAAS;EAC9C,QAAQ,MAAM,aAAa,UAAU,SAAS;EAC9C,QAAQ,MAAM,aAAa,UAAU,SAAS;EAC9C,QAAQ,MAAM,aAAa,UAAU,SAAS;CAChD;AACF;;;;AAKA,SAAgB,2BAA2B,KAA6C;CACtF,IAAI,CAAC,OAAO,OAAO,QAAQ,YAAY,MAAM,QAAQ,GAAG,GAAG,OAAO,KAAA;CAClE,MAAM,MAAM;CACZ,MAAM,OAA0B,CAAC;CACjC,IAAI,SAAS;CAEb,IAAI,OAAO,IAAI,UAAU,WAAW;EAClC,KAAK,QAAQ,IAAI;EACjB,SAAS;CACX;CACA,IAAI,OAAO,IAAI,WAAW,WAAW;EACnC,KAAK,SAAS,IAAI;EAClB,SAAS;CACX;CACA,IAAI,OAAO,IAAI,WAAW,WAAW;EACnC,KAAK,SAAS,IAAI;EAClB,SAAS;CACX;CACA,IAAI,OAAO,IAAI,WAAW,WAAW;EACnC,KAAK,SAAS,IAAI;EAClB,SAAS;CACX;CACA,IAAI,OAAO,IAAI,WAAW,WAAW;EACnC,KAAK,SAAS,IAAI;EAClB,SAAS;CACX;CAEA,OAAO,SAAS,OAAO,KAAA;AACzB;;;;AAKA,MAAa,qBAA8C;CACzD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;AAKA,MAAa,kBAA+E;CAC1F,QAAQ,CAAC,gBAAgB,aAAa;CACtC,QAAQ;EACN;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,WAAW;EACT;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,eAAe;AACjB;;;;AAKA,SAAgB,sBACd,MACA,kBACgB;CAChB,IAAI,SAAS,UAAU;EACrB,IAAI,CAAC,oBAAoB,iBAAiB,WAAW,GAAG,OAAO,CAAC;EAChE,MAAM,WAAW,IAAI,IAAkB,kBAAkB;EACzD,OAAO,CAAC,GAAG,IAAI,IAAI,iBAAiB,QAAQ,MAAM,SAAS,IAAI,CAAC,CAAC,CAAC,CAAC;CACrE;CACA,OAAO,CAAC,GAAG,gBAAgB,KAAK;AAClC;;;;;AAMA,SAAgB,oBAAoB,GAAG,eAA4D;CACjG,IAAI,cAAc,WAAW,GAAG,OAAO,CAAC;CACxC,IAAI,cAAc,WAAW,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,cAAc,EAAE,CAAC;CACpE,IAAI,UAAU,IAAI,IAAkB,cAAc,EAAE;CACpD,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,QAAQ,KAAK;EAC7C,MAAM,UAAU,IAAI,IAAkB,cAAc,EAAE;EACtD,UAAU,IAAI,IAAI,CAAC,GAAG,OAAO,CAAC,CAAC,QAAQ,MAAM,QAAQ,IAAI,CAAC,CAAC,CAAC;CAC9D;CACA,OAAO,CAAC,GAAG,OAAO;AACpB;;;;AAKA,SAAgB,gBACd,cACA,WACS;CACT,IAAI,CAAC,cAAc,OAAO;CAC1B,OAAO,aAAa,WAAW,SAAS,SAAS;AACnD;;;;;;;;;AAUA,SAAgB,mBACd,cACA,WACoB;CACpB,IAAI,CAAC,cAAc,OAAO,KAAA;CAC1B,OAAO,aAAa,mBAAmB,cAAc,aAAa;AACpE;;;;AAKA,SAAgB,+BAAgD;CAC9D,OAAO;EACL,MAAM;EACN,YAAY,CAAC,GAAG,kBAAkB;CACpC;AACF;;;;AAKA,SAAgB,uBAAuB,KAA0C;CAC/E,IAAI,CAAC,MAAM,QAAQ,GAAG,GAAG,OAAO,KAAA;CAChC,MAAM,WAAW,IAAI,IAAY,kBAAkB;CACnD,MAAM,SAAyB,CAAC;CAChC,KAAK,MAAM,QAAQ,KACjB,IAAI,OAAO,SAAS,YAAY,SAAS,IAAI,IAAI,GAC/C,OAAO,KAAK,IAAoB;CAGpC,OAAO,OAAO,SAAS,IAAI,CAAC,GAAG,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC;AACrD;;;ACrOA,MAAa,8BAA8B;;;;;;;;AAgC3C,SAAS,kBACP,OACA,WACe;CACf,MAAM,WAAW,OAAO;CACxB,OAAO,YAAY,UAAU,IAAI,QAAQ,IAAI,WAAW;AAC1D;;;;;;;AAQA,SAAS,gBACP,OACA,WAC+B;CAC/B,MAAM,yBAAS,IAAI,IAA8B;CACjD,MAAM,SAAS,MAAM,UAAU;EAC7B,IAAI,KAAoB,KAAK;EAC7B,MAAM,0BAAU,IAAI,IAAY;EAChC,OAAO,MAAM,CAAC,QAAQ,IAAI,EAAE,GAAG;GAC7B,QAAQ,IAAI,EAAE;GACd,MAAM,QAAQ,OAAO,IAAI,EAAE;GAC3B,IAAI,OAAO,OAAO,IAAI,IAAI,CAAC,KAAK,IAAI,MAAM,IAAI,KAAK,GAAG,KAAK,IAAI,MAAM,IAAI,KAAK,CAAC,CAAC;QAC3E,OAAO,IAAI,IAAI,CAAC,OAAO,KAAK,CAAC;GAGlC,KAAK,kBAAkB,UAAU,IAAI,EAAE,GAAG,SAAS;EACrD;CACF,CAAC;CACD,OAAO;AACT;;;;;;;;;AAUA,SAAS,kBACP,QACA,WACU;CACV,MAAM,QAAkB,CAAC;CACzB,MAAM,yBAAS,IAAI,IAAY;CAC/B,IAAI,UAAU,OAAO,KAAK,GAAG,UAAU,KAAK;CAC5C,OAAO,QAAQ,SAAS,GAAG;EACzB,MAAM,WAAqB,CAAC;EAC5B,KAAK,MAAM,SAAS,SAAS;GAC3B,MAAM,WAAW,OAAO,MAAM,CAAC;GAC/B,IAAI,YAAY,UAAU,IAAI,QAAQ,KAAK,CAAC,OAAO,IAAI,QAAQ,GAAG,SAAS,KAAK,KAAK;QAChF;IACH,MAAM,KAAK,KAAK;IAChB,OAAO,IAAI,OAAO,MAAM,CAAC,EAAE;GAC7B;EACF;EACA,IAAI,SAAS,WAAW,QAAQ,QAAQ;GACtC,MAAM,KAAK,GAAG,QAAQ;GACtB;EACF;EACA,UAAU;CACZ;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkCA,SAAgB,qBACd,QACA,QACkB;CAClB,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACtD,MAAM,QAA0B,CAAC;CACjC,KAAK,IAAI,IAAI,OAAO,SAAS,GAAG,KAAK,GAAG,KAAK;EAC3C,MAAM,QAAQ,OAAO;EACrB,MAAM,UAAU,MAAM,WAAW,UAAU,IAAI,MAAM,OAAO,IAAI,MAAM,UAAU;EAChF,MAAM,OAAO,MAAM,MAAM,SAAS;EAClC,IAAI,WAAW,QAAQ,KAAK,YAAY,SAAS,KAAK,OAAO,KAAK,KAAK;OAClE,MAAM,KAAK;GAAE;GAAS,QAAQ,CAAC,KAAK;EAAE,CAAC;CAC9C;CAEA,IAAI,SAAS,gBAAgB,OAAO,SAAS;CAM7C,MAAM,aAAa,IAAI,IAAI,OAAO,KAAK,CAAC;CACxC,IAAI,WAAW,SAAS,OAAO,QAAQ,OAAO;CAC9C,KAAK,MAAM,MAAM,kBAAkB,QAAQ,SAAS,GAAG;EACrD,MAAM,QAAQ,OAAO;EACrB,IAAI,WAAW,IAAI,MAAM,EAAE,GAAG;EAC9B,MAAM,WAAW,kBAAkB,OAAO,SAAS;EACnD,MAAM,aAAa,UAAsB,kBAAkB,OAAO,SAAS,MAAM;EACjF,IAAI;EACJ,KAAK,IAAI,IAAI,KAAK,GAAG,aAAa,KAAA,KAAa,KAAK,GAAG,KAAK;GAC1D,IAAI,CAAC,UAAU,OAAO,EAAE,GAAG;GAC3B,MAAM,QAAQ,OAAO,IAAI,OAAO,EAAE,CAAC,EAAE;GACrC,IAAI,OAAO,WAAW,MAAM,KAAK;EACnC;EACA,KAAK,IAAI,IAAI,KAAK,GAAG,aAAa,KAAA,KAAa,IAAI,OAAO,QAAQ,KAAK;GACrE,IAAI,CAAC,UAAU,OAAO,EAAE,GAAG;GAC3B,MAAM,QAAQ,OAAO,IAAI,OAAO,EAAE,CAAC,EAAE;GACrC,IAAI,OAAO,WAAW,MAAM;EAC9B;EAGA,IAAI,aAAa,KAAA,KAAa,UAAU,YAAY,OAAO,IAAI,QAAQ,CAAC,GAAG,MAAM,MAAM;EACvF,MAAM,OAAO,YAAY,GAAG,GAAG;GAAE,SAAS,MAAM;GAAI,QAAQ,CAAC;EAAE,CAAC;EAChE,WAAW,IAAI,MAAM,EAAE;EAGvB,SAAS,gBAAgB,OAAO,SAAS;CAC3C;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;AAgCA,SAAgB,uBACd,QACA,QACwB;CACxB,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACtD,MAAM,QAAQ,qBAAqB,QAAQ,MAAM;CACjD,MAAM,6BAAa,IAAI,IAA0B;CACjD,MAAM,SAAuB,CAAC;CAC9B,MAAM,wBAAQ,IAAI,IAAY;CAC9B,KAAK,IAAI,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;EACrC,MAAM,OAAO,MAAM;EACnB,IAAI,CAAC,KAAK,SAAS;EAInB,MAAM,QAAsB,CAAC;EAC7B,IAAI,KAAoB,KAAK;EAC7B,MAAM,0BAAU,IAAI,IAAY;EAChC,OAAO,MAAM,CAAC,QAAQ,IAAI,EAAE,KAAK,CAAC,MAAM,IAAI,EAAE,GAAG;GAC/C,QAAQ,IAAI,EAAE;GACd,MAAM,QAAQ,UAAU,IAAI,EAAE;GAC9B,IAAI,CAAC,OAAO;GACZ,MAAM,QAAQ,KAAK;GACnB,KAAK,kBAAkB,OAAO,SAAS;EACzC;EACA,IAAI,MAAM,WAAW,GAAG;EAGxB,IAAI,WAAW,KAAK,OAAO,EAAE,EAAE;EAC/B,KAAK,IAAI,IAAI,IAAI,GAAG,aAAa,KAAA,KAAa,IAAI,MAAM,QAAQ,KAC9D,WAAW,MAAM,EAAE,CAAC,OAAO,EAAE,EAAE;EAEjC,KAAK,MAAM,SAAS,OAAO,MAAM,IAAI,MAAM,EAAE;EAC7C,IAAI,aAAa,KAAA,GAAW;GAC1B,OAAO,KAAK,GAAG,KAAK;GACpB;EACF;EACA,MAAM,KAAK,WAAW,IAAI,QAAQ;EAClC,IAAI,IAAI,GAAG,KAAK,GAAG,KAAK;OACnB,WAAW,IAAI,UAAU,KAAK;CACrC;CACA,OAAO;EAAE;EAAY;CAAO;AAC9B;;;;;;;;;;;;;;;;;;;;AAqBA,SAAgB,eAAe,QAAyB,QAAuC;CAC7F,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACtD,MAAM,QAAyB,CAAC;CAChC,MAAM,4BAAY,IAAI,IAA+C;CAErE,KAAK,MAAM,QAAQ,qBAAqB,QAAQ,MAAM,GAAG;EACvD,MAAM,QAAQ,KAAK,UAAU,UAAU,IAAI,KAAK,OAAO,IAAI,KAAA;EAC3D,IAAI,CAAC,OAAO;GACV,KAAK,MAAM,SAAS,KAAK,QAAQ,MAAM,KAAK;IAAE,MAAM;IAAS;GAAM,CAAC;GACpE;EACF;EAGA,MAAM,WAAW,UAAU,IAAI,MAAM,EAAE;EACvC,IAAI,UAAU;GACZ,SAAS,SAAS,KAAK,GAAG,KAAK,MAAM;GACrC;EACF;EACA,MAAM,OAAO;GAAE,MAAM;GAAkB;GAAO,UAAU,CAAC,GAAG,KAAK,MAAM;EAAE;EACzE,UAAU,IAAI,MAAM,IAAI,IAAI;EAC5B,MAAM,KAAK,IAAI;CACjB;CACA,OAAO;AACT;;;;;;;;;;;;;;;AAgBA,SAAgB,kBAAkB,QAAyB,QAAuC;CAChG,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACtD,OAAO,OAAO,KAAK,UAAU;EAC3B,IAAI,CAAC,MAAM,WAAW,CAAC,UAAU,IAAI,MAAM,OAAO,GAAG,OAAO;EAC5D,MAAM,EAAE,SAAS,YAAY,eAAe,OAAO,SAAS;EAC5D,IAAI,YAAY,MAAM,WAAW,YAAY,MAAM,SAAS,OAAO;EACnE,OAAO;GAAE,GAAG;GAAO;GAAS;EAAQ;CACtC,CAAC;AACH;;;;;;AAOA,SAAS,eACP,OACA,WACuC;CACvC,IAAI,UAAU,MAAM;CACpB,IAAI,UAAU,MAAM;CACpB,IAAI,QAAQ,MAAM,UAAU,UAAU,IAAI,MAAM,OAAO,IAAI,KAAA;CAC3D,MAAM,0BAAU,IAAI,IAAY;CAChC,OAAO,SAAS,CAAC,QAAQ,IAAI,MAAM,EAAE,GAAG;EACtC,QAAQ,IAAI,MAAM,EAAE;EACpB,UAAU,WAAW,MAAM;EAC3B,WAAW,MAAM;EACjB,QAAQ,MAAM,WAAW,UAAU,IAAI,MAAM,QAAQ,IAAI,KAAA;CAC3D;CACA,OAAO;EAAE;EAAS;CAAQ;AAC5B;;;;;;;;;;;;;;;;;;;AAoBA,SAAgB,0BACd,OACA,QACuC;CAEvC,KADa,MAAM,QAAQ,MAAM,IAAI,OAAO,SAAS,OAAO,UAC/C,KAAK,CAAC,MAAM,SACvB,OAAO;EAAE,SAAS,MAAM;EAAS,SAAS,MAAM;CAAQ;CAE1D,OAAO,eACL,OACA,MAAM,QAAQ,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,MAClE;AACF;;;;;;;;AASA,SAAgB,mBAAmB,QAAyB,SAA2B;CACrF,MAAM,UAAoB,CAAC;CAC3B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KACjC,IAAI,OAAO,EAAE,EAAE,YAAY,SAAS,QAAQ,KAAK,CAAC;CAEpD,OAAO;AACT;;;;;;;;;;;;;;AAeA,SAAgB,yBAAyB,QAA0C;CACjF,MAAM,SAA0B,CAAC;CACjC,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;EACtC,MAAM,QAAQ,OAAO;EACrB,IAAI,OAAO,IAAI,MAAM,EAAE,GAAG;EAC1B,IAAI,CAAC,MAAM,SAAS;GAClB,OAAO,KAAK,KAAK;GACjB,OAAO,IAAI,MAAM,EAAE;GACnB;EACF;EAGA,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,QAAQ,KAAK;GACtC,MAAM,YAAY,OAAO;GACzB,IAAI,UAAU,YAAY,MAAM,WAAW,CAAC,OAAO,IAAI,UAAU,EAAE,GAAG;IACpE,OAAO,KAAK,SAAS;IACrB,OAAO,IAAI,UAAU,EAAE;GACzB;EACF;CACF;CACA,OAAO;AACT;;;;;;;;;;;;;;;;AAiBA,SAAS,YACP,UACA,SACA,WAC2B;CAC3B,IAAI,YAAY,MAAM,OAAO,aAAa,OAAO,OAAO,KAAA;CACxD,IAAI,YAAY,UAAU,OAAO;CACjC,IAAI,KAAK;CACT,MAAM,0BAAU,IAAI,IAAI,CAAC,EAAE,CAAC;CAC5B,SAAS;EACP,MAAM,OAAO,kBAAkB,UAAU,IAAI,EAAE,GAAG,SAAS;EAC3D,IAAI,SAAS,UAAU,OAAO;EAC9B,IAAI,SAAS,QAAQ,QAAQ,IAAI,IAAI,GAAG,OAAO,KAAA;EAC/C,QAAQ,IAAI,IAAI;EAChB,KAAK;CACP;AACF;;;;;;;;;;;;AAaA,SAAgB,gBACd,OACA,WACQ;CACR,IAAI,QAAQ;CACZ,IAAI,WAAW,MAAM;CACrB,MAAM,0BAAU,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;CAClC,OAAO,YAAY,CAAC,QAAQ,IAAI,QAAQ,GAAG;EACzC,QAAQ,IAAI,QAAQ;EACpB,MAAM,SAAS,UAAU,IAAI,QAAQ;EACrC,IAAI,CAAC,QAAQ;EACb,SAAS;EACT,WAAW,OAAO;CACpB;CACA,OAAO;AACT;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyCA,SAAgB,yBACd,QACA,QACA,IACA,WAC0D;CAC1D,OAAO,sBAAsB,qBAAqB,QAAQ,MAAM,GAAG,QAAQ,QAAQ,IAAI,SAAS;AAClG;;;;;;AAOA,SAAS,sBACP,OACA,QACA,QACA,IACA,WAC0D;CAC1D,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACtD,MAAM,QAAQ,UAAU,IAAI,EAAE;CAC9B,IAAI,CAAC,OAAO,OAAO;CAInB,MAAM,WAAW,kBAAkB,OAAO,SAAS;CACnD,MAAM,WAAW,MAAM,KAAK,SAAS,YAAY,UAAU,KAAK,SAAS,SAAS,CAAC;CACnF,MAAM,2BAAW,IAAI,IAAY;CACjC,SAAS,SAAS,QAAQ,UAAU;EAClC,IAAI,WAAW,IAAI,SAAS,IAAI,KAAK;CACvC,CAAC;CACD,IAAI,SAAS,SAAS,GAAG,OAAO;CAChC,MAAM,UAAU,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,GAAG,MAAM,IAAI,CAAC;CAElD,MAAM,WAAW,cAAc,OAAO,QAAQ,KAAK,IAAI,QAAQ,QAAQ,SAAS,KAAK;CACrF,IAAI,WAAW,KAAK,YAAY,MAAM,QAAQ,OAAO;CACrD,MAAM,iBAAiB,SAAS;CAEhC,IAAI,mBAAmB,KAAA,GAAW,OAAO;CAGzC,MAAM,gBACJ,mBAAmB,OACf,CAAC,MAAM,SAAS,IAChB,MAAM,QAAQ,GAAG,UAAU,SAAS,WAAW,cAAc;CAInE,MAAM,QAAQ,MAAM,QAAQ,GAAG,UAAU,SAAS,IAAI,KAAK,CAAC;CAC5D,MAAM,YAAY,MAAM,QAAQ,GAAG,UAAU,CAAC,SAAS,IAAI,KAAK,CAAC;CACjE,MAAM,OACJ,cAAc,OACV,UAAU,QAAQ,cAAc,EAAE,IAClC,UAAU,QAAQ,cAAc,cAAc,SAAS,EAAE,IAAI;CACnE,MAAM,YAAY,CAAC,GAAG,SAAS;CAC/B,UAAU,OAAO,MAAM,GAAG,GAAG,KAAK;CAElC,OAAO,gBAAgB,WAAW,QAAQ,QAAQ,SAAS;AAC7D;;;;;;AAOA,SAAS,gBACP,WACA,QACA,QACA,WAC0D;CAG1D,MAAM,aAAa,UAAU,SAAS,SAAS,KAAK,MAAM,CAAC,CAAC,QAAQ;CAIpE,MAAM,SAAS,gBAAgB,WAAW,SAAS;CACnD,MAAM,aAAa,CAAC,GAAG,MAAM,CAAC,CAAC,MAAM,GAAG,MAAM;EAC5C,MAAM,cAAc,OAAO,IAAI,EAAE,EAAE,CAAC,GAAG,MAAM,MAAM,OAAO,IAAI,EAAE,EAAE,CAAC,GAAG,MAAM;EAC5E,OAAO,eAAe,IAClB,aACA,gBAAgB,GAAG,SAAS,IAAI,gBAAgB,GAAG,SAAS;CAClE,CAAC;CACD,MAAM,cAAc,WAAW,MAAM,OAAO,UAAU,MAAM,OAAO,OAAO,MAAM,EAAE,EAAE;CACpF,MAAM,cAAc,WAAW,MAAM,OAAO,UAAU,MAAM,OAAO,OAAO,MAAM,EAAE,EAAE;CACpF,IAAI,CAAC,eAAe,CAAC,aAAa,OAAO;CACzC,OAAO;EAAE,QAAQ;EAAY,QAAQ;CAAW;AAClD;;;;;;;AAWA,SAAS,aAAa,QAA2C;CAC/D,MAAM,UAAgC;EAAE,SAAS;EAAM,aAAa;CAAO;CAC3E,IAAI;EACF,OAAO,IAAI,KAAK,SAAS,QAAQ,OAAO;CAC1C,QAAQ;EACN,OAAO,IAAI,KAAK,SAAS,KAAA,GAAW,OAAO;CAC7C;AACF;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,sBACd,QACA,QACA,IACA,OACA,QAC0D;CAE1D,OAAO,sBADO,qBAAqB,QAAQ,MACV,GAAG,QAAQ,QAAQ,IAAI,OAAO,aAAa,MAAM,CAAC;AACrF;;;;;AAMA,SAAS,sBACP,OACA,QACA,QACA,IACA,OACA,UAC0D;CAC1D,MAAM,YAAY,IAAI,IAAI,OAAO,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC;CACtD,IAAI,CAAC,UAAU,IAAI,EAAE,GAAG,OAAO;CAC/B,MAAM,OAAO,UAAU,QAAQ,IAAI;CACnC,MAAM,WAAW,GAAW,MAAc,OAAO,SAAS,QAAQ,GAAG,CAAC;CAGtE,MAAM,WAAW,MAAM,KAAK,SAAS,YAAY,IAAI,KAAK,SAAS,SAAS,CAAC;CAC7E,MAAM,SAAS,SAAS,SAAS,QAAQ,UAAW,SAAS,CAAC,KAAK,IAAI,CAAC,CAAE;CAC1E,IAAI,OAAO,WAAW,GAAG,OAAO;CAIhC,MAAM,6BAAa,IAAI,IAA8B;CACrD,KAAK,MAAM,SAAS,QAAQ;EAC1B,MAAM,SAAS,SAAS;EACxB,MAAM,OAAO,WAAW,IAAI,MAAM;EAClC,IAAI,MAAM,KAAK,KAAK,MAAM,MAAM;OAC3B,WAAW,IAAI,QAAQ,CAAC,MAAM,MAAM,CAAC;CAC5C;CACA,MAAM,WAAW,CAAC,GAAG,WAAW,KAAK,CAAC;CAEtC,MAAM,iBAAiB,CAAC,GADP,SAAS,QAAQ,WAAW,WAAW,EACtB,CAAC,CAAC,CAAC,MAAM,GAAG,MAC5C,QAAQ,UAAU,IAAI,CAAC,CAAC,EAAE,QAAQ,IAAI,UAAU,IAAI,CAAC,CAAC,EAAE,QAAQ,EAAE,CACpE;CACA,IAAI,YAAY;CAChB,MAAM,eAAe,SAAS,KAAK,WAA6B;EAC9D,IAAI,WAAW,IAAI,OAAO,WAAW,IAAI,eAAe,YAAY,KAAK,CAAC;EAE1E,OAAO,CAAC;GAAE,SAAS;GAAI,QAAQ,CAAC,IADnB,WAAW,IAAI,EAAE,KAAK,CAAC,EAAA,CAAG,SAAS,SAAS,KAAK,MACzB,CAAC,CAAC,CAAC,MAAM,GAAG,MAAM,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC;EAAE,CAAC;CACnF,CAAC;CAID,MAAM,YAAY,IAAI,IAAI,MAAM;CAChC,MAAM,YAAY,MAAM,QAAQ,GAAG,UAAU,CAAC,UAAU,IAAI,KAAK,CAAC;CAClE,UAAU,OAAO,OAAO,IAAI,GAAG,GAAG,aAAa,KAAK,CAAC;CAarD,IAPE,UAAU,WAAW,MAAM,UAC3B,UAAU,OACP,MAAM,UACL,KAAK,YAAY,MAAM,MAAM,CAAC,WAC9B,KAAK,OAAO,WAAW,MAAM,MAAM,CAAC,OAAO,UAC3C,KAAK,OAAO,OAAO,OAAO,OAAO,MAAM,OAAO,MAAM,MAAM,CAAC,OAAO,GAAG,CAAC,EAAE,CAC5E,GACa,OAAO;CACtB,OAAO,gBAAgB,WAAW,QAAQ,QAAQ,SAAS;AAC7D;;;;;;;;;AAUA,SAAgB,sBACd,QACA,QAC6C;CAC7C,MAAM,QAAQ,qBAAqB,QAAQ,MAAM;CACjD,MAAM,yBAAS,IAAI,IAA4C;CAC/D,KAAK,MAAM,SAAS,QAClB,OAAO,IAAI,MAAM,IAAI;EACnB,IAAI,sBAAsB,OAAO,QAAQ,QAAQ,MAAM,IAAI,IAAI,MAAM;EACrE,MAAM,sBAAsB,OAAO,QAAQ,QAAQ,MAAM,IAAI,MAAM,MAAM;CAC3E,CAAC;CAEH,OAAO;AACT;;;;;;;;;;AAWA,SAAgB,sBACd,QACA,QACA,QACmD;CACnD,MAAM,QAAQ,qBAAqB,QAAQ,MAAM;CACjD,MAAM,WAAW,aAAa,MAAM;CACpC,MAAM,yBAAS,IAAI,IAAkD;CACrE,KAAK,MAAM,SAAS,QAClB,OAAO,IAAI,MAAM,IAAI;EACnB,KAAK,sBAAsB,OAAO,QAAQ,QAAQ,MAAM,IAAI,OAAO,QAAQ,MAAM;EACjF,MAAM,sBAAsB,OAAO,QAAQ,QAAQ,MAAM,IAAI,QAAQ,QAAQ,MAAM;CACrF,CAAC;CAEH,OAAO;AACT;;;;;ACjxBA,SAAgB,kBAAkB,UAAkD;CAClF,IAAI,OAAO,aAAa,UAAU,uBAAO,IAAI,IAAI,CAAC,QAAQ,CAAC;CAC3D,IAAI,oBAAoB,KAAK,OAAO;CACpC,OAAO,IAAI,IAAI,QAAQ;AACzB;;;;;AAMA,SAAgB,6BACd,SACA,UACmB;CACnB,MAAM,UAAU,kBAAkB,QAAQ;CAC1C,IAAI,QAAQ,SAAS,KAAK,QAAQ,WAAW,GAAG,OAAO;CACvD,MAAM,WAAW,QAAQ,QAAQ,MAAM,CAAC,QAAQ,IAAI,EAAE,OAAO,CAAC;CAC9D,OAAO,SAAS,WAAW,QAAQ,SAAS,UAAU;AACxD;;;;;;AAOA,SAAgB,8BACd,UACA,UACkB;CAClB,MAAM,UAAU,kBAAkB,QAAQ;CAC1C,IAAI,QAAQ,SAAS,KAAK,SAAS,WAAW,GAAG,OAAO;CACxD,MAAM,WAAW,SAAS,QACvB,MAAM,EAAE,EAAE,OAAO,SAAS,aAAa,QAAQ,IAAI,EAAE,OAAO,OAAO,EACtE;CACA,OAAO,SAAS,WAAW,SAAS,SAAS,WAAW;AAC1D;;;;;;;;;;AAWA,SAAgB,4BACd,QACA,UACc;CACd,MAAM,UAAU,kBAAkB,QAAQ;CAC1C,IAAI,QAAQ,SAAS,GAAG,OAAO;CAE/B,IAAI,UAAU;CAGd,MAAM,QAAQ,OAAO,MAAM,QAAQ,OAAO,CAAC,QAAQ,IAAI,EAAE,CAAC;CAC1D,IAAI,MAAM,WAAW,OAAO,MAAM,QAAQ,UAAU;CAGpD,MAAM,YAA+D,CAAC;CACtE,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,OAAO,SAAS,GAAG;EAC3D,MAAM,OAAO,IAAI,SAAS,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,QAAQ,IAAI,CAAC,IAAI;EACpE,IAAI,QAAQ,IAAI,IAAI,GAClB,UAAU;OAEV,UAAU,OAAO;CAErB;CAGA,IAAI,gBAAgB,OAAO;CAC3B,IAAI,eAAe;EACjB,IAAI,uBAAuB;EAC3B,MAAM,aAA6D,CAAC;EACpE,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,aAAa,GACrD,IAAI,IAAI,WAAW,SAAS,GAC1B,WAAW,OAAO;OACb,IAAI,QAAQ,IAAI,GAAG,GACxB,uBAAuB;OAEvB,WAAW,OAAO;EAGtB,IAAI,sBAAsB;GACxB,gBAAgB,OAAO,KAAK,UAAU,CAAC,CAAC,SAAS,IAAI,aAAa,KAAA;GAClE,UAAU;EACZ;CACF;CAEA,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO;EAAE,GAAG;EAAQ;EAAO;EAAW;CAAc;AACtD;;;;;;;;;;;;;;;;;;;;;;;;AC6DA,MAAM,UAAU;CAAC;CAAY;CAAa;CAAe;AAAc;AACvE,MAAM,eAAe;CAAC;CAAO;CAAa;AAAc;;;;;;AAOxD,MAAa,uBAA0C;CACrD,OAAO;CACP,UAAU;CACV,gBAAgB;CAChB,YAAY;CAEZ,WAAW;CACX,aAAa;CACb,aAAa;CACb,cAAc;CACd,YAAY;CACZ,YAAY;CACZ,gBAAgB;CAChB,YAAY;CACZ,gBAAgB;CAChB,iBAAiB;CACjB,iBAAiB;CAEjB,gBAAgB;CAChB,gBAAgB;CAChB,eAAe;CAEf,WAAW;CACX,cAAc;CACd,YAAY;CACZ,cAAc;CACd,gBAAgB;CAChB,mBAAmB;CACnB,YAAY;CACZ,YAAY;CACZ,UAAU;CACV,UAAU;CACV,iBAAiB;CAEjB,cAAc;CACd,cAAc;CACd,aAAa;CACb,aAAa;CACb,eAAe;CACf,qBAAqB;CACrB,gBAAgB;CAChB,kBAAkB;CAElB,kBAAkB;CAClB,mBAAmB;CACnB,qBAAqB,CACnB;EAAE,IAAI;EAAQ,OAAO;EAAW,OAAO;CAAU,GACjD;EAAE,IAAI;EAAQ,OAAO;EAAW,OAAO;CAAU,CACnD;CACA,sBAAsB;CAEtB,eAAe;CACf,cAAc;CACd,YAAY;CACZ,cAAc,CAAC;CACf,gBAAgB;CAChB,eAAe;CACf,cAAc;CACd,cAAc;CACd,eAAe;CACf,iBAAiB;CACjB,2BAA2B;CAE3B,eAAe;CACf,cAAc;CACd,YAAY;CACZ,WAAW;CACX,oBAAoB;CACpB,kBAAkB;CAClB,iBAAiB;CACjB,eAAe;CACf,iBAAiB;CAEjB,eAAe;CACf,QAAQ;CACR,eAAe;CACf,KAAK;CACL,UAAU;CAEV,aAAa;CACb,YAAY;CAEZ,cAAc;CACd,cAAc;CACd,eAAe;CACf,gBAAgB;CAChB,gBAAgB;CAChB,iBAAiB;CACjB,gBAAgB;CAChB,kBAAkB;CAClB,YAAY;CACZ,gBAAgB;CAChB,qBAAqB;CACrB,aAAa;CACb,sBAAsB;AACxB;;AAGA,SAAgB,2BAA8C;CAC5D,OAAO;EACL,GAAG;EACH,cAAc,CAAC;EACf,qBAAqB,qBAAqB,oBAAoB,KAAK,WAAW,EAAE,GAAG,MAAM,EAAE;CAC7F;AACF;AAEA,SAAS,IAAI,OAAgB,UAA0B;CACrD,OAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;;AAGA,SAAS,SAAS,OAAgB,UAA0B;CAC1D,OAAO,OAAO,UAAU,YAAY,kCAAkC,KAAK,MAAM,KAAK,CAAC,IACnF,MAAM,KAAK,IACX;AACN;AAEA,SAAS,KAAK,OAAgB,UAA4B;CACxD,OAAO,OAAO,UAAU,YAAY,QAAQ;AAC9C;AAEA,SAAS,IAAI,OAAgB,UAAkB,KAAa,KAAqB;CAC/E,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG,OAAO;CACjE,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;AAEA,SAAS,MAAwB,OAAgB,SAAuB,UAAgB;CACtF,OAAO,OAAO,UAAU,YAAa,QAA8B,SAAS,KAAK,IAC5E,QACD;AACN;AAEA,SAAS,WAAW,OAAgB,UAA8B;CAChE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAClC,OAAO,MAAM,QAAQ,SAAyB,OAAO,SAAS,QAAQ;AACxE;AAEA,SAAS,cACP,OACA,UAC0B;CAC1B,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,SAAS,KAAK,WAAW,EAAE,GAAG,MAAM,EAAE;CACxE,MAAM,UAAoC,CAAC;CAI3C,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,CAAC,OAAO,SAAS,MAAM,QAAQ,GAAG;EAC3C,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;EAC9D,MAAM,QAAQ;EAEd,IAAI,KADU,OAAO,MAAM,OAAO,WAAW,MAAM,GAAG,KAAK,IAAI;EAE/D,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,GAAG;GAGvB,IAAI,YAAY,QAAQ;GACxB,OAAO,KAAK,IAAI,MAAM,WAAW,KAAK,SAAS,OAAO,OAAO,MAAM,WAAW,GAC5E,aAAa;GAEf,KAAK,MAAM;EACb;EACA,KAAK,IAAI,EAAE;EACX,QAAQ,KAAK;GACX;GACA,OAAO,IAAI,MAAM,OAAO,EAAE;GAC1B,OAAO,IAAI,MAAM,OAAO,SAAS;EACnC,CAAC;CACH;CACA,OAAO;AACT;;AAGA,SAAS,SAAS,OAAkB,OAAe,IAAqB;CACtE,KAAK,IAAI,IAAI,QAAQ,GAAG,IAAI,MAAM,QAAQ,KAAK,GAAG;EAChD,MAAM,OAAO,MAAM;EACnB,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;EAC9D,MAAM,YAAa,KAAyC;EAC5D,IAAI,OAAO,cAAc,YAAY,UAAU,KAAK,MAAM,IAAI,OAAO;CACvE;CACA,OAAO;AACT;AAEA,SAAS,OAAO,OAAyD;CACvE,IAAI,CAAC,MAAM,QAAQ,KAAK,KAAK,MAAM,WAAW,GAAG,OAAO;CACxD,IAAI,CAAC,MAAM,OAAO,MAAM,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,CAAC,GAAG,OAAO;CAC7E,MAAM,CAAC,MAAM,OAAO,MAAM,SAAS;CAInC,IAAI,QAAQ,QAAQ,SAAS,OAAO,OAAO;CAC3C,OAAO;EAAC;EAAM;EAAO;EAAM;CAAK;AAClC;;;;;;;;;;AAWA,SAAgB,2BAA2B,OAA0C;CACnF,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO;CACxE,MAAM,MAAM;CACZ,MAAM,IAAI;CACV,OAAO;EACL,OAAO,IAAI,IAAI,OAAO,EAAE,KAAK;EAC7B,UAAU,IAAI,IAAI,UAAU,EAAE,QAAQ;EACtC,gBAAgB,MAAM,IAAI,gBAAgB,CAAC,WAAW,QAAQ,GAAY,EAAE,cAAc;EAC1F,YAAY,MAAM,IAAI,YAAY;GAAC;GAAQ;GAAU;EAAO,GAAY,EAAE,UAAU;EAEpF,WAAW,MACT,IAAI,WACJ;GAAC;GAAM;GAAM;GAAU;GAAS;GAAW;GAAU;GAAM;GAAS;GAAU;EAAQ,GACtF,EAAE,SACJ;EACA,aAAa,MAAM,IAAI,aAAa,CAAC,YAAY,WAAW,GAAY,EAAE,WAAW;EACrF,aAAa,IAAI,IAAI,aAAa,EAAE,aAAa,GAAG,GAAM;EAC1D,cAAc,IAAI,IAAI,cAAc,EAAE,cAAc,GAAG,GAAM;EAC7D,YAAY,MAAM,IAAI,YAAY,CAAC,MAAM,IAAI,GAAY,EAAE,UAAU;EACrE,YAAY,MAAM,IAAI,YAAY;GAAC;GAAU;GAAU;EAAM,GAAY,EAAE,UAAU;EAGrF,gBAAgB,SAAS,IAAI,gBAAgB,EAAE,cAAc;EAC7D,YAAY,MAAM,IAAI,YAAY,CAAC,SAAS,MAAM,GAAY,EAAE,UAAU;EAC1E,gBAAgB,KAAK,IAAI,gBAAgB,EAAE,cAAc;EACzD,iBAAiB,IAAI,IAAI,iBAAiB,EAAE,eAAe;EAG3D,iBAAiB,IAAI,IAAI,iBAAiB,EAAE,iBAAiB,GAAG,EAAE;EAElE,gBAAgB,IAAI,IAAI,gBAAgB,EAAE,cAAc;EACxD,gBAAgB,IAAI,IAAI,gBAAgB,EAAE,gBAAgB,GAAG,EAAE;EAC/D,eAAe,IAAI,IAAI,eAAe,EAAE,aAAa;EAErD,WAAW,KAAK,IAAI,WAAW,EAAE,SAAS;EAC1C,cAAc,KAAK,IAAI,cAAc,EAAE,YAAY;EACnD,YAAY,KAAK,IAAI,YAAY,EAAE,UAAU;EAC7C,cAAc,KAAK,IAAI,cAAc,EAAE,YAAY;EACnD,gBAAgB,KAAK,IAAI,gBAAgB,EAAE,cAAc;EACzD,mBAAmB,KAAK,IAAI,mBAAmB,EAAE,iBAAiB;EAClE,YAAY,KAAK,IAAI,YAAY,EAAE,UAAU;EAC7C,YAAY,IAAI,IAAI,YAAY,EAAE,UAAU;EAC5C,UAAU,KAAK,IAAI,UAAU,EAAE,QAAQ;EACvC,UAAU,IAAI,IAAI,UAAU,EAAE,QAAQ;EACtC,iBAAiB,KAAK,IAAI,iBAAiB,EAAE,eAAe;EAE5D,cAAc,KAAK,IAAI,cAAc,EAAE,YAAY;EACnD,cAAc,IAAI,IAAI,cAAc,EAAE,YAAY;EAClD,aAAa,IAAI,IAAI,aAAa,EAAE,WAAW;EAC/C,aAAa,IAAI,IAAI,aAAa,EAAE,WAAW;EAC/C,eAAe,IAAI,IAAI,eAAe,EAAE,aAAa;EACrD,qBAAqB,MACnB,IAAI,qBACJ,CAAC,YAAY,YAAY,GACzB,EAAE,mBACJ;EACA,gBAAgB,IAAI,IAAI,gBAAgB,EAAE,gBAAgB,GAAG,GAAG;EAChE,kBAAkB,MAAM,IAAI,kBAAkB,SAAS,EAAE,gBAAgB;EAEzE,kBAAkB,KAAK,IAAI,kBAAkB,EAAE,gBAAgB;EAC/D,mBAAmB,IAAI,IAAI,mBAAmB,EAAE,iBAAiB;EACjE,qBAAqB,cAAc,IAAI,qBAAqB,EAAE,mBAAmB;EACjF,sBAAsB,MAAM,IAAI,sBAAsB,SAAS,EAAE,oBAAoB;EAErF,eAAe,KAAK,IAAI,eAAe,EAAE,aAAa;EACtD,cAAc,IAAI,IAAI,cAAc,EAAE,YAAY;EAClD,YAAY,IAAI,IAAI,YAAY,EAAE,UAAU;EAC5C,cAAc,WAAW,IAAI,cAAc,EAAE,YAAY;EACzD,gBAAgB,IAAI,IAAI,gBAAgB,EAAE,cAAc;EACxD,eAAe,KAAK,IAAI,eAAe,EAAE,aAAa;EACtD,cAAc,IAAI,IAAI,cAAc,EAAE,cAAc,GAAG,GAAK;EAC5D,cAAc,KAAK,IAAI,cAAc,EAAE,YAAY;EACnD,eAAe,MAAM,IAAI,eAAe,SAAS,EAAE,aAAa;EAChE,iBAAiB,MAAM,IAAI,iBAAiB,cAAc,EAAE,eAAe;EAC3E,2BAA2B,KAAK,IAAI,2BAA2B,EAAE,yBAAyB;EAE1F,eAAe,KAAK,IAAI,eAAe,EAAE,aAAa;EACtD,cAAc,IAAI,IAAI,cAAc,EAAE,YAAY;EAClD,YAAY,IAAI,IAAI,YAAY,EAAE,UAAU;EAC5C,WAAW,MAAM,IAAI,WAAW;GAAC;GAAO;GAAO;EAAM,GAAY,EAAE,SAAS;EAC5E,oBAAoB,IAAI,IAAI,oBAAoB,EAAE,kBAAkB;EACpE,kBAAkB,MAChB,IAAI,kBACJ;GAAC;GAAS;GAAO;EAAM,GACvB,EAAE,gBACJ;EACA,iBAAiB,IAAI,IAAI,iBAAiB,EAAE,eAAe;EAC3D,eAAe,MAAM,IAAI,eAAe,SAAS,EAAE,aAAa;EAChE,iBAAiB,MAAM,IAAI,iBAAiB,cAAc,EAAE,eAAe;EAE3E,eAAe,KAAK,IAAI,eAAe,EAAE,aAAa;EACtD,QAAQ,IAAI,IAAI,QAAQ,EAAE,MAAM;EAChC,eAAe,IAAI,IAAI,eAAe,EAAE,aAAa;EACrD,KAAK,IAAI,IAAI,KAAK,EAAE,GAAG;EACvB,UAAU,IAAI,IAAI,UAAU,EAAE,QAAQ;EAEtC,aAAa,MAAM,IAAI,aAAa,CAAC,YAAY,QAAQ,GAAY,EAAE,WAAW;EAClF,YAAY,OAAO,IAAI,UAAU;EAEjC,cAAc,KAAK,IAAI,cAAc,EAAE,YAAY;EACnD,cAAc,IAAI,IAAI,cAAc,EAAE,YAAY;EAClD,eAAe,MAAM,IAAI,eAAe,CAAC,YAAY,MAAM,GAAY,EAAE,aAAa;EACtF,gBAAgB,IAAI,IAAI,gBAAgB,EAAE,cAAc;EACxD,gBAAgB,IAAI,IAAI,gBAAgB,EAAE,cAAc;EACxD,iBAAiB,MAAM,IAAI,iBAAiB,CAAC,UAAU,OAAO,GAAY,EAAE,eAAe;EAC3F,gBAAgB,IAAI,IAAI,gBAAgB,EAAE,gBAAgB,GAAG,GAAG;EAChE,kBAAkB,KAAK,IAAI,kBAAkB,EAAE,gBAAgB;EAC/D,YAAY,IAAI,IAAI,YAAY,EAAE,UAAU;EAC5C,gBAAgB,IAAI,IAAI,gBAAgB,EAAE,cAAc;EACxD,qBAAqB,KAAK,IAAI,qBAAqB,EAAE,mBAAmB;EACxE,aAAa,IAAI,IAAI,aAAa,EAAE,WAAW;EAC/C,sBAAsB,IAAI,IAAI,sBAAsB,EAAE,oBAAoB;CAC5E;AACF;;;;;;AAOA,SAAgB,qBAAqB,QAAoC;CACvE,OAAO,wBAAwB,QAAQ,oBAAoB;AAC7D;;;;;AAMA,SAAgB,wBAAwB,GAAsB,GAA+B;CAC3F,IAAI,MAAM,GAAG,OAAO;CACpB,KAAK,MAAM,OAAO,OAAO,KAAK,oBAAoB,GAAkC;EAClF,MAAM,OAAO,EAAE;EACf,MAAM,QAAQ,EAAE;EAChB,IAAI,MAAM,QAAQ,IAAI,KAAK,MAAM,QAAQ,KAAK,GACxC;OAAA,KAAK,UAAU,IAAI,MAAM,KAAK,UAAU,KAAK,GAAG,OAAO;EAAA,OACtD,IAAI,SAAS,OAClB,OAAO;CAEX;CACA,OAAO;AACT;;;;;;;AAQA,SAAS,4BACP,QACA,SACmB;CACnB,MAAM,QAAQ,OAAe,OAAO,MAAM,QAAQ,EAAE;CACpD,IAAI,CAAC,KAAK,OAAO,YAAY,KAAK,CAAC,KAAK,OAAO,YAAY,KAAK,CAAC,KAAK,OAAO,YAAY,GACvF,OAAO;CAET,OAAO;EACL,GAAG;EACH,GAAI,KAAK,OAAO,YAAY,IAAI;GAAE,cAAc;GAAI,eAAe;EAAM,IAAI,CAAC;EAC9E,GAAI,KAAK,OAAO,YAAY,IAAI;GAAE,cAAc;GAAI,eAAe;EAAM,IAAI,CAAC;EAC9E,GAAI,KAAK,OAAO,YAAY,IAAI;GAAE,cAAc;GAAI,cAAc;EAAM,IAAI,CAAC;CAC/E;AACF;;;;;;;;;;;AAYA,SAAgB,0BACd,QACA,kBACmB;CACnB,OAAO,4BAA4B,SAAS,OAAO,CAAC,iBAAiB,IAAI,EAAE,CAAC;AAC9E;;;;;;;;;;AAWA,SAAgB,iCACd,QACA,UACmB;CACnB,MAAM,UAAU,kBAAkB,QAAQ;CAC1C,IAAI,QAAQ,SAAS,GAAG,OAAO;CAC/B,OAAO,4BAA4B,SAAS,OAAO,QAAQ,IAAI,EAAE,CAAC;AACpE;;;;;;;;;;AC/hBA,SAAgB,0BAA0B,MAA0B;CAClE,OAAO,SAAS,aAAa,SAAS;AACxC;;AAGA,MAAa,4BAA4B;;AAGzC,MAAa,+BAA+B;AAE5C,MAAM,4BAA8D;CAClE;CACA;CACA;CACA;AACF;;;;;;;;AASA,MAAa,oBAAoB;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;;;;;;;AAQA,MAAa,kBAAkB;CAC7B;CACA;CACA;AACF;;;;;;;;;;AAWA,SAAgB,yBACd,OACA,MACqB;CACrB,IAAI,SAAS,UACX,OAAO,gBAAgB,EAAE,QAAQ,MAAM,UAAU,oBAAoB,OAAO,CAAC;CAE/E,IAAI,SAAS,YAAY,SAAS,QAAQ;EACxC,MAAM,OACJ,SAAS,WAAW,oBAAoB;EAC1C,MAAM,SAA8B,CAAC;EACrC,KAAK,MAAM,OAAO,MAEhB,OAAoC,OADtB,MAAM,QAAQ,oBAAoB;EAGlD,OAAO,gBAAgB,MAAM;CAC/B;CACA,OAAO,gBAAgB;EAAE,GAAG;EAAqB,GAAG;CAAM,CAAC;AAC7D;;;;;;;AAQA,MAAM,oBAA0E;CAC9E,iBAAiB,CAAC,UAAU,QAAQ;CACpC,iBAAiB;EAAC;EAAU;EAAa;EAAe;EAAc;CAAY;CAClF,aAAa;EAAC;EAAQ;EAAS;EAAe;EAAc;EAAY;EAAQ;CAAK;CACrF,aAAa;EAAC;EAAU;EAAU;EAAY;EAAW;EAAQ;EAAS;EAAO;CAAQ;CACzF,eAAe;EAAC;EAAU;EAAW;CAAS;CAE9C,iCAAiC;EAC/B;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;AACF;;AAGA,MAAM,oBAA0E;CAC9E,WAAW,CAAC,SAAS,MAAM;CAC3B,QAAQ;EACN;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,WAAW;EAAC;EAAQ;EAAa;CAAW;CAC5C,QAAQ;EAAC;EAAO;EAAU;CAAa;AACzC;;;;;;;;;;;;;AAcA,SAAgB,wBAAwB,OAAqC;CAC3E,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACzE,MAAM,SAAS;CACf,MAAM,MAA+B,CAAC;CACtC,KAAK,MAAM,OAAO,OAAO,KAAK,mBAAmB,GAA2B;EAC1E,IAAI,EAAE,OAAO,SAAS;EACtB,MAAM,QAAQ,OAAO;EACrB,MAAM,WAAW,oBAAoB;EACrC,IAAI,QAAQ,UAAU;GACpB,IAAI,SAAS,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK,GAAG;IAC/D,MAAM,cAAc;IACpB,MAAM,SAAkC,CAAC;IACzC,KAAK,MAAM,YAAY,OAAO,KAAK,oBAAoB,MAAM,GAA2B;KACtF,MAAM,gBAAgB,oBAAoB,OAAO;KACjD,MAAM,aAAa,YAAY;KAC/B,MAAM,eAAe,kBAAkB;KAKvC,OAAO,YAHL,OAAO,eAAe,OAAO,kBAC5B,OAAO,eAAe,YAAY,OAAO,SAAS,UAAU,OAC5D,CAAC,gBAAgB,aAAa,SAAS,UAAoB,KAC9B,aAAa;IAC/C;IACA,IAAI,SAAS;GACf;GACA;EACF;EACA,IAAI,MAAM,QAAQ,QAAQ,GAAG;GAC3B,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG;GAK3B,IAAI,QAAQ,oBACV,IAAI,OAAO,MAAM,SAAS,SAAS;IACjC,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO,CAAC;IAC/C,MAAM,OAAO;IACb,IACG,OAAO,KAAK,UAAU,YACrB,EAAE,OAAO,KAAK,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,MAChE,OAAO,KAAK,UAAU,UAEtB,OAAO,CAAC;IAEV,OAAO,CACL;KACE,OAAO,KAAK;KACZ,OAAO,KAAK;KACZ,GAAI,OAAO,KAAK,UAAU,WAAW,EAAE,OAAO,KAAK,MAAM,IAAI,CAAC;IAChE,CACF;GACF,CAAC;QACI,IAAI,QAAQ,eACjB,IAAI,OAAO,MAAM,SAAS,SAAS;IACjC,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO,CAAC;IAC/C,MAAM,OAAO;IACb,IACE,OAAO,KAAK,OAAO,YACnB,OAAO,KAAK,UAAU,YACtB,OAAO,KAAK,WAAW,YACvB,OAAO,KAAK,UAAU,YACtB,OAAO,KAAK,WAAW,WAEvB,OAAO,CAAC;IAKV,MAAM,kBAAkB,UACtB,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ,KAAA;IAIhE,MAAM,mBAAmB,OAAgB,KAAa,QAAgB;KACpE,MAAM,SAAS,eAAe,KAAK;KACnC,OAAO,WAAW,KAAA,KAAa,UAAU,OAAO,UAAU,MAAM,SAAS,KAAA;IAC3E;IACA,MAAM,kBAAkB,UACtB,OAAO,UAAU,WAAW,QAAQ,KAAA;IACtC,MAAM,UAAU,gBAAgB,KAAK,SAAS,GAAG,EAAE;IACnD,MAAM,UAAU,gBAAgB,KAAK,SAAS,GAAG,EAAE;IACnD,MAAM,WAAW,eAAe,KAAK,QAAQ;IAC7C,MAAM,cAAc,eAAe,KAAK,WAAW;IACnD,MAAM,cAAc,gBAAgB,KAAK,aAAa,GAAG,OAAO,iBAAiB;IACjF,MAAM,cAAc,gBAAgB,KAAK,aAAa,GAAG,CAAC;IAC1D,MAAM,eAAe,gBAAgB,KAAK,cAAc,GAAG,OAAO,iBAAiB;IACnF,OAAO,CACL;KACE,IAAI,KAAK;KACT,OAAO,KAAK;KACZ,QAAQ,KAAK;KACb,OAAO,KAAK;KACZ,QAAQ,KAAK;KACb,GAAI,KAAK,YAAY,QAAQ,EAAE,SAAS,MAAM,IAAI,CAAC;KACnD,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;KAC3C,GAAI,YAAY,KAAA,IAAY,EAAE,QAAQ,IAAI,CAAC;KAC3C,GAAI,aAAa,KAAA,IAAY,EAAE,SAAS,IAAI,CAAC;KAC7C,GAAI,gBAAgB,KAAA,IAAY,EAAE,YAAY,IAAI,CAAC;KACnD,GAAI,gBAAgB,KAAA,IAAY,EAAE,YAAY,IAAI,CAAC;KACnD,GAAI,gBAAgB,KAAA,IAAY,EAAE,YAAY,IAAI,CAAC;KACnD,GAAI,iBAAiB,KAAA,IAAY,EAAE,aAAa,IAAI,CAAC;IACvD,CACF;GACF,CAAC;QACI,IAAI,QAAQ,cAEb;QAAA,MAAM,WAAW,KAAK,oBAAoB,KAAK,GAAG,IAAI,OAAO;GAAA,OAEjE,IAAI,OAAO;GAEb;EACF;EACA,IAAI,OAAO,UAAU,OAAO,UAAU;EACtC,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG;EAC1D,MAAM,UAAU,kBAAkB;EAClC,IAAI,WAAW,CAAC,QAAQ,SAAS,KAAe,GAAG;EACnD,IAAI,OAAO;CACb;CACA,OAAO,gBAAgB,GAAG;AAC5B;;;;;;;;;AAUA,SAAS,yBACP,OACA,MACqB;CACrB,IAAI,SAAS,SAAS,OAAO;CAC7B,MAAM,UACJ,SAAS,WAAW,oBAAoB,SAAS,SAAS,kBAAkB,CAAC,QAAQ;CACvF,MAAM,MAA2B,CAAC;CAClC,KAAK,MAAM,OAAO,SAChB,IAAI,OAAO,OACT,IAAiC,OAAO,MAAM;CAGlD,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,6BAA6B,OAAqC;CAChF,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,UAA+B,CAAC;CACtC,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,OAAO,OAAO;EACvB,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;EACrC,MAAM,YAAY;EAClB,MAAM,KAAK,OAAO,UAAU,OAAO,WAAW,UAAU,GAAG,KAAK,IAAI;EACpE,MAAM,OAAO,OAAO,UAAU,SAAS,WAAW,UAAU,KAAK,KAAK,IAAI;EAC1E,IAAI,CAAC,MAAM,CAAC,QAAQ,KAAK,IAAI,EAAE,GAAG;EAClC,MAAM,OAAO,0BAA0B,SAAS,UAAU,IAA6B,IAClF,UAAU,OACX;EACJ,MAAM,QAAQ,yBAAyB,wBAAwB,UAAU,KAAK,GAAG,IAAI;EACrF,IAAI,OAAO,KAAK,KAAK,CAAC,CAAC,WAAW,GAAG;EACrC,KAAK,IAAI,EAAE;EACX,MAAM,OAAO,MAAM,QAAQ,UAAU,IAAI,IACrC,CACE,GAAG,IAAI,IACL,UAAU,KACP,QAAQ,QAAuB,OAAO,QAAQ,QAAQ,CAAC,CACvD,KAAK,QAAQ,IAAI,KAAK,CAAC,CAAC,CACxB,QAAQ,QAAQ,QAAQ,EAAE,CAC/B,CACF,IACA,CAAC;EACL,QAAQ,KAAK;GACX;GACA;GACA;GACA;GACA;GACA,WAAW,OAAO,UAAU,cAAc,WAAW,UAAU,YAAY;EAC7E,CAAC;CACH;CACA,OAAO;AACT;;;;;;;;AASA,SAAgB,sBAAsB,SAAsC;CAC1E,OAAO,KAAK,UACV;EACE,MAAM;EACN,SAAA;EACA;CACF,GACA,MACA,CACF;AACF;;;;;;;;;;;AAYA,SAAgB,kBAAkB,MAAmC;CACnE,IAAI;CACJ,IAAI;EACF,SAAS,KAAK,MAAM,IAAI;CAC1B,QAAQ;EACN,MAAM,IAAI,MAAM,gDAAgD;CAClE;CACA,IAAI,aAAsB;CAC1B,IAAI,MAAM,QAAQ,MAAM,GACtB,aAAa;MACR,IACL,UACA,OAAO,WAAW,YACjB,OAA8B,SAAA,0BAC/B;EAIA,IAAK,OAAiC,YAAA,GACpC,MAAM,IAAI,MAAM,oCAAoC;EAEtD,aAAc,OAAiC;CACjD;CACA,IAAI,eAAe,MACjB,MAAM,IAAI,MAAM,iCAAiC;CAEnD,MAAM,UAAU,6BAA6B,UAAU;CACvD,IAAI,QAAQ,WAAW,GACrB,MAAM,IAAI,MAAM,iDAAiD;CAEnE,OAAO;AACT;;;;;;AAOA,SAAgB,4BAAoC;CAClD,IAAI,OAAO,WAAW,eAAe,gBAAgB,QACnD,OAAO,OAAO,WAAW;CAE3B,OAAO,SAAS,KAAK,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,GAAG,EAAE;AACnF;;;;;;;;AASA,MAAa,yBAAuD;CAClE;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,YAAY,SAAS;EAC5B,OAAO;GACL,WAAW;GACX,aAAa;GACb,aAAa;GACb,aAAa;GACb,iBAAiB;GACjB,aAAa;EACf;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,YAAY,SAAS;EAC5B,OAAO;GACL,WAAW;GACX,aAAa;GACb,aAAa;GACb,aAAa;GACb,iBAAiB;GACjB,aAAa;EACf;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,WAAW,SAAS;EAC3B,OAAO;GACL,WAAW;GACX,aAAa;GACb,aAAa;GACb,aAAa;GACb,aAAa;EACf;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,WAAW,WAAW;EAC7B,OAAO;GACL,WAAW;GACX,aAAa;GACb,aAAa;GACb,aAAa;GACb,aAAa;EACf;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,WAAW,WAAW;EAC7B,OAAO;GACL,WAAW;GACX,aAAa;GACb,aAAa;GACb,aAAa;GACb,aAAa;EACf;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,SAAS,QAAQ;EACxB,OAAO;GACL,eAAe;GACf,aAAa;GACb,aAAa;GACb,YAAY;GACZ,cAAc;GACd,WAAW;EACb;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,QAAQ;EACf,OAAO,EACL,QAAQ;GACN,GAAG,oBAAoB;GACvB,SAAS;GACT,MAAM;GACN,OAAO;GACP,WAAW;GACX,WAAW;EACb,EACF;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,MAAM;EACb,OAAO;GACL,sBAAsB;GACtB,uBAAuB;GACvB,iCAAiC;EACnC;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,MAAM;EACb,OAAO;GACL,sBAAsB;GACtB,uBAAuB;GACvB,iCAAiC;EACnC;EACA,WAAW;CACb;CACA;EACE,IAAI;EACJ,MAAM;EACN,MAAM;EACN,MAAM,CAAC,MAAM;EACb,OAAO;GACL,sBAAsB;GACtB,uBAAuB;GACvB,iCAAiC;EACnC;EACA,WAAW;CACb;AACF;;;;;;;;;ACxjBA,SAAgB,yBAAyB,OAAqC;CAC5E,IACE,CAAC;EAAC;EAAU;EAAO;CAAM,CAAC,CAAC,SAAS,MAAM,IAAI,KAC9C,CAAC,MAAM,UACN,OAAO,MAAM,OAAO,YAAY,YAAY,OAAO,SAAS,MAAM,OAAO,OAAO,KAChF,OAAO,MAAM,OAAO,YAAY,YAAY,MAAM,OAAO,UAAU,IAEpE,OAAO;CAMT,IAAI,EAHF,MAAM,QAAQ,MAAM,OAAO,KAAK,KAAK,MAAM,OAAO,MAAM,SACpD,MAAM,OAAO,QACb,CAAC,MAAM,OAAO,GAAG,EAAA,CACZ,MAAM,gBAAgB,GAAG,OAAO;CAC3C,OAAO;EAAE,GAAG;EAAO,QAAQ;GAAE,GAAG,MAAM;GAAQ,SAAS;EAAG;CAAE;AAC9D;AAEA,SAAS,iBAAiB,OAAyB;CACjD,IAAI,OAAO,UAAU,UAAU,OAAO;CACtC,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,KAAK;EACzB,QACG,IAAI,aAAa,YAAY,IAAI,aAAa,YAC/C,+CAA+C,KAAK,IAAI,QAAQ,KAChE,+FAA+F,KAC7F,IAAI,QACN;CAEJ,QAAQ;EACN,OAAO;CACT;AACF;;;;;;ACwCA,SAAgB,gBAAgB,OAAwC;CACtE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,YAAY;CAClB,IACE,CAAC;EAAC;EAAS;EAAc;EAAc;EAAmB;EAAW;CAAc,CAAC,CAAC,SACnF,UAAU,gBAAgB,EAC5B,GAEA,OAAO;CACT,IAAI,EAAE,UAAU,uBAAuB,eAAe,OAAO;CAC7D,IAAI,UAAU,oBAAoB,KAAK,UAAU,oBAAoB,GAAG,OAAO;CAC/E,IAAI,CAAC,OAAO,cAAc,UAAU,YAAY,KAAK,UAAU,eAAgB,GAAG,OAAO;CACzF,IAAI,UAAU,YAAY,SAAS,UAAU,oBAAoB,GAAG,OAAO;CAE3E,IAAI,UAAU,gBAAgB,KAAA,KAAa,EAAE,UAAU,uBAAuB,aAC5E,OAAO;CAET,IAAI,UAAU,gBAAgB,KAAA,KAAa,EAAE,UAAU,uBAAuB,aAC5E,OAAO;CAET,IAAI,UAAU,mBAAmB,KAAA,KAAa,EAAE,UAAU,0BAA0B,aAClF,OAAO;CAGT,MAAM,cAAc,UAAU,YAAY,SAAS,UAAU;CAC7D,MAAM,gBAAgB,SAAiC,QAAgB,QAAgB;EACrF,IAAI,CAAC,WAAW,QAAQ,WAAW,UAAU,QAAQ,OAAO,GAAG,OAAO;EACtE,KAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAClC,IAAI,QAAQ,KAAK,QAAQ,IAAI,MAAM,QAAQ,KAAK,KAAK,OAAO;EAE9D,OAAO,QAAQ,QAAQ,SAAS,OAAO;CACzC;CACA,MAAM,QAAQ,UAAU;CACxB,IAAI,UAAU,iBAAiB,SACzB;MAAA,gBAAgB,OAAO,OAAO;CAAA,OAC7B,IAAI,UAAU,iBAAiB,gBAAgB,UAAU,iBAAiB,cAC3E;MAAA,CAAC,aAAa,UAAU,aAAa,QAAQ,GAAG,WAAW,GAAG,OAAO;CAAA,OACpE;EACL,MAAM,SAAS,UAAU,aAAa,UAAU,KAAK;EACrD,IAAI,CAAC,aAAa,UAAU,aAAa,QAAQ,GAAG,WAAW,GAAG,OAAO;EACzE,IAAI,UAAU,iBAAiB,gBAAgB;GAC7C,MAAM,YAAY,UAAU,aAAa,UAAU,KAAK;GACxD,IAAI,CAAC,aAAa,UAAU,aAAa,WAAW,GAAG,KAAK,GAAG,OAAO;GACtE,IAAI,CAAC,aAAa,UAAU,gBAAgB,QAAQ,GAAG,QAAQ,GAAG,OAAO;EAC3E,OAAO,IAAI,CAAC,aAAa,UAAU,aAAa,QAAQ,GAAG,KAAK,GAAG,OAAO;CAC5E;CACA,IACE,UAAU,eAAe,KAAA,MACxB,CAAC,MAAM,QAAQ,UAAU,UAAU,KAAK,UAAU,WAAW,WAAW,QAEzE,OAAO;CACT,IACE,UAAU,eAAe,KAAA,MACxB,CAAC,MAAM,QAAQ,UAAU,UAAU,KAAK,UAAU,WAAW,WAAW,QAEzE,OAAO;CAET,OAAO;AACT;;;;;;AAOA,SAAgB,oBAAoB,OAA+D;CACjG,IAAI,MAAM,MAAM,OAAO,CAAC,GAAG,MAAM,IAAI;CACrC,MAAM,EAAE,aAAa,oBAAoB;CACzC,IAAI,YAAY,WAAW,GAAG,OAAO;CAErC,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;CACX,IAAI,OAAO;CAEX,MAAM,OAAO;CACb,KAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK,MAAM;EACjD,MAAM,IAAI,YAAY;EACtB,MAAM,IAAI,YAAY,IAAI;EAC1B,IAAI,OAAO,SAAS,CAAC,KAAK,OAAO,SAAS,CAAC,GAAG;GAC5C,IAAI,IAAI,MAAM,OAAO;GACrB,IAAI,IAAI,MAAM,OAAO;GACrB,IAAI,IAAI,MAAM,OAAO;GACrB,IAAI,IAAI,MAAM,OAAO;EACvB;CACF;CAEA,OAAO,SAAS,WAAW,OAAO;EAAC;EAAM;EAAM;EAAM;CAAI;AAC3D;;;;;;AAOA,SAAgB,4BACd,OACoC;CACpC,MAAM,EACJ,cACA,aACA,iBAAiB,KACjB,aACA,aACA,gBACA,cACA,YACA,eACE;CAEJ,MAAM,WAAuC,IAAI,MAAM,YAAY;CAEnE,SAAS,aAAa,WAA6B;EACjD,MAAM,IAAI,YAAY;EACtB,OAAO,QAAQ,IACX;GAAC,YAAY;GAAI,YAAY,IAAI;GAAI,YAAY,IAAI;EAAE,IACvD,CAAC,YAAY,IAAI,YAAY,IAAI,EAAE;CACzC;CAEA,SAAS,SAAS,aAAqB,WAA+B;EACpE,MAAM,QAAQ,YAAY;EAC1B,MAAM,OAAmB,IAAI,MAAM,KAAK;EACxC,KAAK,IAAI,IAAI,GAAG,IAAI,OAAO,KACzB,KAAK,KAAK,aAAa,cAAc,CAAC;EAExC,OAAO;CACT;CAEA,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;EACrC,MAAM,QAAQ,aAAa,EAAE,GAAG,WAAW,GAAG,IAAI,CAAC;EACnD,MAAM,KAAK,aAAa,WAAW,KAAK,KAAA;EAExC,IAAI,WAA4B;EAEhC,QAAQ,cAAR;GACE,KAAK,SAAS;IACZ,MAAM,IAAI,IAAI;IACd,IACE,IAAI,IAAI,YAAY,UACpB,OAAO,SAAS,YAAY,EAAE,KAC9B,OAAO,SAAS,YAAY,IAAI,EAAE,GAElC,WAAW;KACT,MAAM;KACN,aAAa,aAAa,CAAC;IAC7B;IAEF;GACF;GACA,KAAK;IACH,IAAI,aAAa;KACf,MAAM,QAAQ,YAAY;KAC1B,MAAM,MAAM,YAAY,IAAI;KAC5B,IAAI,MAAM,OACR,WAAW;MACT,MAAM;MACN,aAAa,SAAS,OAAO,GAAG;KAClC;IAEJ;IACA;GAEF,KAAK;IACH,IAAI,aAAa;KACf,MAAM,QAAQ,YAAY;KAC1B,MAAM,MAAM,YAAY,IAAI;KAC5B,IAAI,MAAM,OACR,WAAW;MACT,MAAM;MACN,aAAa,SAAS,OAAO,GAAG;KAClC;IAEJ;IACA;GAEF,KAAK;IACH,IAAI,eAAe,aAAa;KAC9B,MAAM,YAAY,YAAY;KAC9B,MAAM,UAAU,YAAY,IAAI;KAChC,MAAM,QAAsB,CAAC;KAC7B,KAAK,IAAI,IAAI,WAAW,IAAI,SAAS,KAAK;MACxC,MAAM,SAAS,YAAY;MAC3B,MAAM,OAAO,YAAY,IAAI;MAC7B,IAAI,OAAO,QACT,MAAM,KAAK,SAAS,QAAQ,IAAI,CAAC;KAErC;KACA,IAAI,MAAM,SAAS,GACjB,WAAW;MACT,MAAM;MACN,aAAa;KACf;IAEJ;IACA;GAEF,KAAK;IACH,IAAI,eAAe,aAAa;KAC9B,MAAM,YAAY,YAAY;KAC9B,MAAM,UAAU,YAAY,IAAI;KAChC,MAAM,QAAsB,CAAC;KAC7B,KAAK,IAAI,IAAI,WAAW,IAAI,SAAS,KAAK;MACxC,MAAM,SAAS,YAAY;MAC3B,MAAM,OAAO,YAAY,IAAI;MAC7B,IAAI,OAAO,QACT,MAAM,KAAK,SAAS,QAAQ,IAAI,CAAC;KAErC;KACA,IAAI,MAAM,SAAS,GACjB,WAAW;MACT,MAAM;MACN,aAAa;KACf;IAEJ;IACA;GAEF,KAAK,gBACH,IAAI,kBAAkB,eAAe,aAAa;IAChD,MAAM,YAAY,eAAe;IACjC,MAAM,UAAU,eAAe,IAAI;IACnC,MAAM,QAAwB,CAAC;IAC/B,KAAK,IAAI,IAAI,WAAW,IAAI,SAAS,KAAK;KACxC,MAAM,YAAY,YAAY;KAC9B,MAAM,UAAU,YAAY,IAAI;KAChC,MAAM,QAAsB,CAAC;KAC7B,KAAK,IAAI,IAAI,WAAW,IAAI,SAAS,KAAK;MACxC,MAAM,SAAS,YAAY;MAC3B,MAAM,OAAO,YAAY,IAAI;MAC7B,IAAI,OAAO,QACT,MAAM,KAAK,SAAS,QAAQ,IAAI,CAAC;KAErC;KACA,IAAI,MAAM,SAAS,GAAG,MAAM,KAAK,KAAK;IACxC;IACA,IAAI,MAAM,SAAS,GACjB,WAAW;KACT,MAAM;KACN,aAAa;IACf;GAEJ;EAGJ;EAEA,SAAS,KAAK;GACZ,MAAM;GACN;GACA,YAAY;GACZ;EACF;CACF;CAEA,OAAO;EACL,MAAM;EACN;CACF;AACF;AAEA,SAAS,mCACP,UACA,aACgC;CAChC,IAAI,CAAC,UAAU,OAAO;CACtB,IAAI,gBAAgB,cAAc;EAChC,IAAI,SAAS,SAAS,gBAAgB,MAAM,QAAQ,SAAS,WAAW,GACtE,OAAO,SAAS;EAElB,OAAO;CACT;CACA,IAAI,gBAAgB,cAAc;EAChC,IAAI,SAAS,SAAS,gBAAgB,MAAM,QAAQ,SAAS,WAAW,GACtE,OAAO,SAAS;EAElB,IAAI,SAAS,SAAS,WAAW,MAAM,QAAQ,SAAS,WAAW,GAEjE,OAAO,CAAC,SAAS,WAAW;EAE9B,OAAO;CACT;CACA,OAAO;AACT;;;;;AAMA,SAAgB,4BACd,YACsB;CACtB,MAAM,EAAE,aAAa;CACrB,IAAI,CAAC,YAAY,SAAS,WAAW,GACnC,OAAO;EACL,cAAc;EACd,6BAAa,IAAI,aAAa,CAAC;EAC/B,iBAAiB;EACjB,cAAc;EACd,YAAY,CAAC;CACf;CAIF,IAAI,cAA2C;CAC/C,IAAI,OAAO;CAEX,KAAK,MAAM,KAAK,UAAU;EACxB,MAAM,IAAI,EAAE;EACZ,IAAI,CAAC,GAAG;EACR,IAAI,gBAAgB,MAAM;GACxB,IACE,EAAE,SAAS,WACX,EAAE,SAAS,gBACX,EAAE,SAAS,gBACX,EAAE,SAAS,WAEX,cAAc,EAAE;QAEhB,OAAO;EAEX,OAAO,IAAI,EAAE,SAAS,aAAa;GAEjC,IACG,gBAAgB,WAAW,EAAE,SAAS,gBACtC,gBAAgB,gBAAgB,EAAE,SAAS,SAE5C,cAAc;QAEd,OAAO;EAEX;EAGA,IAAI,CAAC,MAAM;GACT,MAAM,gBAAgB,aACpB,SAAS,SAAS,KAAK,OAAO,SAAS,SAAS,EAAE;GACpD,IAAI,EAAE,SAAS,SAAS,OAAO,aAAa,EAAE,WAAW;QACpD,IAAI,EAAE,SAAS,WAClB,OAAO,EAAE,YAAY,MAAM,SAAS,KAAK,KAAK,YAAY,CAAC;QACtD,IAAI,EAAE,SAAS,gBAAgB,EAAE,SAAS,cAC/C,OAAO,EAAE,YAAY,KAAK,YAAY;EAE1C;CACF;CAEA,IAAI,CAAC,aAAa,cAAc;CAChC,MAAM,MAAa,OAAO,IAAI;CAG9B,MAAM,eAAe,SAAS;CAC9B,MAAM,aAAwC,IAAI,MAAM,YAAY;CACpE,MAAM,aAA8C,IAAI,MAAM,YAAY;CAE1E,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;EACrC,WAAW,KAAK,SAAS,EAAE,CAAC,cAAc,CAAC;EAC3C,WAAW,KAAK,SAAS,EAAE,CAAC;CAC9B;CAGA,IAAI,gBAAgB,SAAS;EAC3B,MAAM,cAAc,IAAI,aAAa,eAAe,GAAG;EACvD,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;GACrC,MAAM,IAAI,SAAS,EAAE,CAAC;GACtB,MAAM,IAAI,IAAI;GACd,IAAI,KAAK,EAAE,SAAS,WAAW,MAAM,QAAQ,EAAE,WAAW,GAAG;IAC3D,YAAY,KAAK,OAAO,EAAE,YAAY,MAAM,GAAG;IAC/C,YAAY,IAAI,KAAK,OAAO,EAAE,YAAY,MAAM,GAAG;IACnD,IAAI,QAAQ,GAAG,YAAY,IAAI,KAAK,OAAO,EAAE,YAAY,MAAM,CAAC;GAClE,OAAO;IACL,YAAY,KAAK;IACjB,YAAY,IAAI,KAAK;IACrB,IAAI,QAAQ,GAAG,YAAY,IAAI,KAAK;GACtC;EACF;EACA,MAAM,QAAuB;GAC3B,cAAc;GACd;GACA,iBAAiB;GACjB;GACA;GACA;EACF;EACA,MAAM,OAAO,oBAAoB,KAAK;EACtC,OAAO,OAAO;GAAE,GAAG;GAAO;EAAK,IAAI;CACrC;CAGA,IAAI,gBAAgB,gBAAgB,gBAAgB,cAAc;EAChE,MAAM,cAAc,IAAI,WAAW,eAAe,CAAC;EACnD,IAAI,gBAAgB;EAEpB,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;GACrC,YAAY,KAAK;GACjB,MAAM,WAAW,mCAAmC,SAAS,EAAE,CAAC,UAAU,WAAW;GACrF,IAAI,UACF,iBAAiB,SAAS;EAE9B;EACA,YAAY,gBAAgB;EAE5B,MAAM,cAAc,IAAI,aAAa,gBAAgB,GAAG;EACxD,IAAI,OAAO;EAEX,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;GACrC,MAAM,WAAW,mCAAmC,SAAS,EAAE,CAAC,UAAU,WAAW;GACrF,IAAI,UACF,KAAK,MAAM,SAAS,UAAU;IAC5B,MAAM,IAAI,OAAO;IACjB,YAAY,KAAK,OAAO,MAAM,MAAM,GAAG;IACvC,YAAY,IAAI,KAAK,OAAO,MAAM,MAAM,GAAG;IAC3C,IAAI,QAAQ,GAAG,YAAY,IAAI,KAAK,OAAO,MAAM,MAAM,CAAC;IACxD;GACF;EAEJ;EAEA,MAAM,QAAuB;GAC3B,cAAc;GACd;GACA,iBAAiB;GACjB;GACA;GACA;GACA;EACF;EACA,MAAM,OAAO,oBAAoB,KAAK;EACtC,OAAO,OAAO;GAAE,GAAG;GAAO;EAAK,IAAI;CACrC;CAGA,IAAI,gBAAgB,WAAW;EAC7B,MAAM,cAAc,IAAI,WAAW,eAAe,CAAC;EACnD,IAAI,aAAa;EACjB,IAAI,gBAAgB;EAEpB,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;GACrC,YAAY,KAAK;GACjB,MAAM,IAAI,SAAS,EAAE,CAAC;GACtB,IAAI,KAAK,EAAE,SAAS,aAAa,MAAM,QAAQ,EAAE,WAAW,GAAG;IAC7D,cAAc,EAAE,YAAY;IAC5B,KAAK,MAAM,QAAQ,EAAE,aACnB,iBAAiB,KAAK;GAE1B;EACF;EACA,YAAY,gBAAgB;EAE5B,MAAM,cAAc,IAAI,WAAW,aAAa,CAAC;EACjD,MAAM,cAAc,IAAI,aAAa,gBAAgB,GAAG;EACxD,IAAI,OAAO;EACX,IAAI,OAAO;EAEX,KAAK,IAAI,IAAI,GAAG,IAAI,cAAc,KAAK;GACrC,MAAM,IAAI,SAAS,EAAE,CAAC;GACtB,IAAI,KAAK,EAAE,SAAS,aAAa,MAAM,QAAQ,EAAE,WAAW,GAC1D,KAAK,MAAM,QAAQ,EAAE,aAAa;IAChC,YAAY,UAAU;IACtB,KAAK,MAAM,SAAS,MAAM;KACxB,MAAM,IAAI,OAAO;KACjB,YAAY,KAAK,OAAO,MAAM,MAAM,GAAG;KACvC,YAAY,IAAI,KAAK,OAAO,MAAM,MAAM,GAAG;KAC3C,IAAI,QAAQ,GAAG,YAAY,IAAI,KAAK,OAAO,MAAM,MAAM,CAAC;KACxD;IACF;GACF;EAEJ;EACA,YAAY,cAAc;EAE1B,MAAM,QAAuB;GAC3B,cAAc;GACd;GACA,iBAAiB;GACjB;GACA;GACA;GACA;GACA;EACF;EACA,MAAM,OAAO,oBAAoB,KAAK;EACtC,OAAO,OAAO;GAAE,GAAG;GAAO;EAAK,IAAI;CACrC;CAEA,OAAO;AACT;;;;;;;;;;;;ACviBA,MAAa,mBAAmB;;AAgFhC,MAAa,mBAGR,CACH;CAAE,MAAM;CAAc,MAAM,CA1E5B;EACE,IAAI;EACJ,MAAM;EACN,SAAS;CACX,GACA;EACE,IAAI;EACJ,MAAM;EACN,UAAU,EACR,qBAAqB;GAAC;GAAW;GAAU;EAAI,EACjD;EACA,OAAO;GACL,OAAO,EACL,MAAM;IAAC;IAAK;IAAK;IAAG;GAAG,EACzB;GACA,WAAW;GACX,cAAc,EACZ,MAAM;IAAC;IAAK;IAAK;IAAK;GAAG,EAC3B;GACA,cAAc;EAChB;CACF,CAqD4B;AAAkB,GAC9C;CAAE,MAAM;CAA2B,MAAM,CAjDzC;EACE,IAAI;EACJ,MAAM;EACN,SAAS;EACT,OAAO;GACL,UAAU;GACV,aAAa;GACb,YAAY;GACZ,OAAO;EACT;CACF,GACA;EACE,IAAI;EACJ,MAAM;EACN,cAAc;EACd,MAAM;GACJ,UAAU,EACR,YAAY,EACV,OAAO,EACL,MAAM;IAAC;IAAG;IAAK;IAAK;GAAG,EACzB,EACF,EACF;GACA,OAAO;GACP,UAAU;GACV,WAAW;EACb;EACA,UAAU;GACR,OAAO;GACP,qBAAqB;IACnB;IAAG;IAAK;IAAI;IAAQ;IAAM;IAAK;IAAI;IAAQ;IAAM;IAAI;IAAG;IAAQ;IAAM;IAAI;IAAK;IAAQ;IACvF;IAAK;IAAK;GACZ;EACF;EACA,OAAO;GACL,OAAO,EACL,MAAM;IAAC;IAAK;IAAK;IAAK;GAAG,EAC3B;GACA,WAAW;EACb;CACF,CASyC;AAAoB,CAC/D;;;;;AAMA,SAAgB,UAAU,OAAqC;CAC7D,IAAI,CAAC,OAAO,OAAO;CACnB,IAAI,SAAS;CACb,IAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,UAAU,MAAM,KAAK;EAC3B,IAAI,CAAC,QAAQ,WAAW,GAAG,KAAK,CAAC,QAAQ,WAAW,GAAG,GAAG,OAAO;EACjE,IAAI;GACF,SAAS,KAAK,MAAM,OAAO;EAC7B,QAAQ;GACN,OAAO;EACT;CACF;CACA,IAAI,MAAM,QAAQ,MAAM,GACtB,OAAO,OAAO,SAAS,KAAK,OAAO,OAAO,SAAS,QAAQ,OAAO,SAAS,QAAQ,IAC9E,SACD;CAEN,IAAI,UAAU,OAAO,WAAW,UAC9B,OAAO,CAAC,MAAoB;CAE9B,OAAO;AACT;;;;AAKA,SAAgB,YAAY,OAA4D;CACtF,OAAO,MAAM,UAAU,eAAe;AACxC;;;;AAWA,SAAgB,WAAW,OAAsE;CAC/F,IAAI,CAAC,YAAY,KAAK,GAAG,OAAO;CAChC,MAAM,MAAM,MAAM,QAAQ;CAI1B,MAAM,OAAO,MAAM,QAAQ,GAAG,IAC1B,IAAI,SAAS,IACX,MACA,KAAA,IACF,OAAO,OAAO,QAAQ,WACpB,CAAC,GAAG,IACJ,OAAO,KAAA;CACb,MAAM,SAAS,MAAM,QAAQ;CAC7B,MAAM,MAAM,OAAO,WAAW,YAAY,OAAO,KAAK,IAAI,OAAO,KAAK,IAAI,KAAA;CAC1E,IAAI,CAAC,QAAQ,CAAC,KAAK,OAAO;CAC1B,OAAO;EAAE;EAAK;CAAK;AACrB;;AAGA,SAASC,eAAqB;CAC5B,OAAO,OAAO,WAAW,eAAe,gBAAgB,SACpD,OAAO,WAAW,IAClB,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;AACzD;;;;AAuBA,SAAgB,gBAAgB,SAA0C;CACxE,MAAM,KAAK,QAAQ,MAAMA,aAAW;CACpC,MAAM,OAAO,QAAQ;CACrB,MAAM,MAAM,QAAQ,KAAK,KAAK,KAAK,KAAA;CACnC,MAAM,aAAa,QAAQ,YAAY,KAAK,KAAK,KAAA;CAEjD,OAAO;EACL;EACA,MAAM,QAAQ;EACd,MAAM;EACN,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACnC,QAAQ;GACN,MAAM;GACN,UAAU;GACV,GAAI,SAAS,KAAA,IAAY,EAAE,UAAU,KAAK,IAAI,CAAC;GAC/C,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;GACrB,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;GACnC,GAAI,QAAQ,aAAa,KAAK,IAAI,EAAE,aAAa,QAAQ,YAAY,KAAK,EAAE,IAAI,CAAC;EACnF;EACA,SAAS;EACT,SAAS;EACT,OAAO,EAAE,GAAG,oBAAoB;EAChC,UAAU;GACR,YAAY;GACZ,qBAAqB;GAMrB,cAAc;GACd,UAAU;GACV,gBAAgB,CAAC,EAAE;EACrB;CACF;AACF;;;AC/NA,MAAa,yBAAyB;AAEtC,SAAgB,iBAAiB,OAAiD;CAChF,OAAO,MAAM,UAAU,eAAe;AACxC;AAYA,SAAgB,gBAAgB,OAAkE;CAChG,IAAI,CAAC,iBAAiB,KAAK,GAAG,OAAO;CACrC,KAAK,MAAM,SAAS,CAAC,MAAM,OAAO,SAAS,MAAM,OAAO,GAAG,GACzD,IAAI,OAAO,UAAU,YAAY,MAAM,KAAK,GAAG,OAAO,MAAM,KAAK;CAEnE,OAAO;AACT;AAEA,SAAgB,qBAAqB,SAA+C;CAClF,MAAM,OAAO,QAAQ,MAAM,KAAK;CAChC,MAAM,MAAM,QAAQ,KAAK,KAAK;CAC9B,IAAI,CAAC,QAAQ,CAAC,KAAK,MAAM,IAAI,MAAM,oCAAoC;CACvE,MAAM,KAAK,QAAQ,MAAM,OAAO,WAAW;CAC3C,OAAO;EACL;EACA,MAAM,QAAQ;EACd,MAAM;EACN,GAAI,QAAQ,aAAa,EAAE,YAAY,QAAQ,WAAW,IAAI,CAAC;EAC/D,QAAQ;GAAE,MAAM;GAAY,UAAU;GAAI,GAAI,OAAO,EAAE,SAAS,KAAK,IAAI,EAAE,IAAI;EAAG;EAClF,SAAS;EACT,SAAS;EACT,OAAO,EAAE,GAAG,oBAAoB;EAChC,UAAU;GACR,YAAY;GACZ,qBAAqB;GACrB,cAAc;EAChB;CACF;AACF;;;ACrCA,MAAa,yBAAyB;;AAMtC,MAAa,gCAAgC;;AAG7C,MAAa,8BAA8B;;;;;;AAO3C,MAAa,wCAAwC;;;;;;AAsBrD,MAAa,yBAA4D;CACvE;EACE,SAAS;EACT,MAAM;EACN,MAAM;EACN,OAAO;CACT;CACA;EACE,SAAA;EACA,MAAM;EACN,MAAM;EACN,OAAO;CACT;CACA;EACE,SAAS;EACT,MAAM;EACN,MAAM;EACN,OAAO;CACT;CACA;EAAE,SAAS;EAAS,MAAM;EAAY,MAAM;EAA0B,OAAO;CAAQ;CACrF;EAAE,SAAS;EAAO,MAAM;EAAY,MAAM;EAA4B,OAAO;CAAQ;CACrF;EAAE,SAAS;EAAO,MAAM;EAAY,MAAM;EAAyB,OAAO;CAAQ;CAClF;EAAE,SAAS;EAAO,MAAM;EAAY,MAAM;EAAwB,OAAO;CAAQ;CACjF;EAAE,SAAS;EAAO,MAAM;EAAY,MAAM;EAA8B,OAAO;CAAQ;CACvF;EAAE,SAAS;EAAM,MAAM;EAAW,MAAM;EAAsB,OAAO;CAAQ;AAC/E;;AAGA,SAAgB,sBAAsB,OAA+B;CACnE,MAAM,KAAK,OAAO,UAAU,WAAW,OAAO,MAAM,KAAK,CAAC,IAAI;CAC9D,OAAO,OAAO,OAAO,YAAY,OAAO,cAAc,EAAE,KAAK,KAAK,IAAI,KAAK;AAC7E;;;;;AAMA,SAAgB,iBAAiB,OAAkE;CACjG,IAAI,MAAM,UAAU,eAAA,cAAuC,OAAO;CAClE,OAAO,sBAAsB,MAAM,QAAQ,UAAU;AACvD;;AAGA,SAAgB,iBAAiB,OAA4D;CAC3F,OAAO,iBAAiB,KAAK,MAAM;AACrC;;AAGA,SAAgB,mBAAmB,OAAwD;CACzF,OAAO,MAAM,SAAS,aAAa,aAAa;AAClD;;;;;AAMA,SAAgB,kBAAkB,OAA4D;CAC5F,OAAO,iBAAiB,KAAK,KAAK,YAAY,KAAK,KAAK,iBAAiB,KAAK;AAChF;AAEA,SAAS,aAAqB;CAC5B,OAAO,OAAO,WAAW,eAAe,gBAAgB,SACpD,OAAO,WAAW,IAClB,GAAG,KAAK,IAAI,EAAE,GAAG,KAAK,OAAO,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC;AACzD;;;;;;;AAiBA,SAAgB,qBAAqB,SAA+C;CAClF,MAAM,KAAK,QAAQ,MAAM,WAAW;CACpC,MAAM,YAAY,QAAQ,SAAS;CACnC,MAAM,iBAAiB,OAAO,SAAS,QAAQ,cAAc,IAAI,QAAQ,iBAAkB;CAC3F,OAAO;EACL;EACA,MAAM,QAAQ;EACd,MAAM,YAAY,aAAa;EAC/B,QAAQ;GACN,MAAM,YAAY,aAAa;GAC/B,YAAY,QAAQ;GACpB,UAAU;GACV,GAAI,YAAY,EAAE,eAAe,IAAI,CAAC;EACxC;EACA,SAAS;EACT,SAAS;EACT,OAAO,EAAE,GAAG,oBAAoB;EAChC,UAAU;GACR,YAAY;GACZ,qBAAqB;GACrB,cAAc;GACd,UAAU;GACV,gBAAgB,CAAC,EAAE;GACnB,GAAI,YAAY;IAAE,iBAAiB;IAAY;GAAe,IAAI,CAAC;EACrE;CACF;AACF;;;;;;;;;;;;AChJA,MAAM,mBAAmB;AACzB,MAAM,uBAAuB;;;;;AAK7B,MAAM,iBAAiB;AAEvB,MAAM,gCAAgB,IAAI,IAAY;AAEtC,SAAgB,oBAAoB,SAAsD;CACxF,IAAI,CAAC,SAAS,OAAO;CACrB,OAAO,OAAO,OAAO,OAAO,CAAC,CAAC,MAC3B,UAAU,OAAO,UAAU,YAAY,qBAAqB,KAAK,KAAK,CACzE;AACF;;;;;AAMA,SAAgB,sBAAsB,OAAwB;CAC5D,OAAO,eAAe,KAAK,MAAM,KAAK,CAAC;AACzC;;;;;;;AAQA,SAAgB,wBAAwB,KAAsB;CAC5D,IAAI;EACF,MAAM,EAAE,UAAU,aAAa,IAAI,IAAI,GAAG;EAC1C,IAAI,aAAa,UAAU,OAAO;EAClC,OACE,aAAa,YACZ,aAAa,eAAe,aAAa,eAAe,aAAa;CAE1E,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;AAQA,SAAgB,wBACd,SACA,QACoC;CACpC,IAAI,CAAC,SAAS,OAAO,KAAA;CACrB,IAAI,CAAC,OAAO,OAAO,OAAO,CAAC,CAAC,MAAM,UAAU,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,CAAC,GAC3F,OAAO;CAET,MAAM,WAAmC,CAAC;CAC1C,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,OAAO,GAAG;EACnD,IAAI,OAAO,UAAU,UAAU;EAC/B,IAAI,UAAyB;EAC7B,MAAM,OAAO,MAAM,QAAQ,mBAAmB,QAAQ,aAAqB;GACzE,MAAM,cAAc,OAAO,OAAO,QAAQ,QAAQ,IAAI,OAAO,YAAY;GACzE,IAAI,CAAC,aAAa,YAAY;GAC9B,OAAO;EACT,CAAC;EACD,IAAI,YAAY,MAAM;GACpB,MAAM,UAAU,GAAG,KAAK,GAAG;GAC3B,IAAI,CAAC,cAAc,IAAI,OAAO,GAAG;IAC/B,cAAc,IAAI,OAAO;IACzB,QAAQ,KACN,8BAA8B,KAAK,0CAA0C,QAAQ,0BACvF;GACF;GACA;EACF;EACA,SAAS,QAAQ;CACnB;CACA,OAAO,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,IAAI,WAAW,KAAA;AACvD;;;;;;;ACnFA,MAAa,4BAA4B;CACvC,aAAa,CAAC,wBAAwB,mBAAmB;CACzD,oBAAoB;EAClB;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;EACA;CACF;CACA,aAAa,CAAC,kBAAkB;AAClC;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,8BAAkF;CAC7F,qBAAqB;CACrB,0BAA0B,CAAC,UAAU,UAAU;CAM/C,2BAA2B;CAM3B,iBAAiB;CAIjB,mCAAmC;CAKnC,uBAAuB;EAAC;EAAa;EAAU;CAAW;AAC5D;;;;;;;;;;;;;;;;;;AAqDA,SAAS,iBAAiB,OAA+B;CACvD,IAAI;CACJ,IAAI;EACF,aAAa,KAAK,UAAU,KAAK,KAAK,OAAO,KAAK;CACpD,QAAQ;EACN,OAAO;CACT;CACA,IAAI,MAAM;CACV,IAAI,OAAO;CACX,KAAK,IAAI,QAAQ,GAAG,QAAQ,WAAW,QAAQ,SAAS,GAAG;EACzD,MAAM,OAAO,WAAW,WAAW,KAAK;EACxC,MAAM,KAAK,KAAK,MAAM,MAAM,QAAU;EACtC,OAAO,KAAK,KAAK,OAAO,MAAM,UAAU;CAC1C;CACA,OAAO,IAAI,QAAQ,EAAA,CAAG,SAAS,EAAE,EAAE,IAAI,SAAS,EAAA,CAAG,SAAS,EAAE;AAChE;;AAGA,SAAS,gBACP,aACA,MACA,OACA,QAAQ,GACF;CACN,MAAM,SAAS,iBAAiB,KAAK;CACrC,YAAY,MAAM,KAAK,IAAI;CAI3B,IAAI,WAAW,MAAM,YAAY,oBAAoB;MAChD,YAAY,aAAa,KAAK,GAAG,KAAK,GAAG,QAAQ;CACtD,YAAY,SAAS;AACvB;;;;;;AAOA,SAAS,wBAAwB,MAAsB;CACrD,OAAO,KAAK,YAAY,CAAC,CAAC,QAAQ,SAAS,EAAE;AAC/C;AAEA,MAAM,iBAAiB,IAAI,IACzB,0BAA0B,mBAAmB,IAAI,uBAAuB,CAC1E;;;;;;;;;;AAUA,MAAM,wBAAwB,IAAI,IAChC;CACE,GAAG,0BAA0B;CAC7B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC,CAAC,IAAI,uBAAuB,CAC/B;;;;;;;AAOA,MAAa,mBAAmB;;AAGhC,SAAgB,sBAAsB,MAAuB;CAC3D,OAAO,eAAe,IAAI,wBAAwB,IAAI,CAAC;AACzD;AAEA,SAAS,kBAAkB,MAAuB;CAChD,IAAI,UAAU,KAAK,QAAQ,OAAO,GAAG;CACrC,IAAI;EACF,UAAU,mBAAmB,OAAO;CACtC,QAAQ,CAER;CACA,MAAM,UAAU,QAAQ,YAAY;CACpC,OACE,sBAAsB,IAAI,wBAAwB,OAAO,CAAC,KAAK,QAAQ,WAAW,QAAQ;AAE9F;;;;;;;;AASA,SAAgB,qBAAqB,MAAuB;CAC1D,OAAO,kBAAkB,IAAI;AAC/B;AAEA,SAAS,sBAAsB,OAAuB;CACpD,OAAO,MACJ,MAAM,GAAG,CAAC,CACV,QAAQ,SAAS,SAAS,MAAM,CAAC,kBAAkB,KAAK,MAAM,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAC1E,KAAK,GAAG;AACb;;;;;AAMA,SAAgB,qBAAqB,OAAuB;CAC1D,MAAM,YAAY,MAAM,QAAQ,GAAG;CACnC,MAAM,aAAa,cAAc,KAAK,QAAQ,MAAM,MAAM,GAAG,SAAS;CACtE,MAAM,WAAW,cAAc,KAAK,KAAA,IAAY,MAAM,MAAM,YAAY,CAAC;CACzE,MAAM,aAAa,WAAW,QAAQ,GAAG;CACzC,IAAI,OAAO,eAAe,KAAK,aAAa,WAAW,MAAM,GAAG,UAAU;CAC1E,MAAM,QAAQ,eAAe,KAAK,KAAA,IAAY,WAAW,MAAM,aAAa,CAAC;CAE7E,MAAM,cAAc,KAAK,QAAQ,KAAK;CACtC,IAAI,gBAAgB,IAAI;EACtB,MAAM,iBAAiB,cAAc;EACrC,MAAM,eAAe,KAAK,QAAQ,KAAK,cAAc;EACrD,MAAM,YAAY,KAAK,MAAM,gBAAgB,iBAAiB,KAAK,KAAA,IAAY,YAAY;EAC3F,MAAM,KAAK,UAAU,YAAY,GAAG;EACpC,IAAI,OAAO,IACT,OACE,KAAK,MAAM,GAAG,cAAc,IAC5B,UAAU,MAAM,KAAK,CAAC,KACrB,iBAAiB,KAAK,KAAK,KAAK,MAAM,YAAY;CAEzD;CAEA,MAAM,YAAY,UAAU,KAAA,IAAY,KAAA,IAAY,sBAAsB,KAAK;CAC/E,MAAM,eACJ,aAAa,KAAA,KAAa,CAAC,SAAS,SAAS,GAAG,IAAI,WAAW,sBAAsB,QAAQ;CAC/F,OAAO,QAAQ,YAAY,IAAI,cAAc,OAAO,eAAe,IAAI,iBAAiB;AAC1F;AAEA,SAASC,gBAAc,OAAkD;CACvE,OAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,KAAK;AAC5E;AAEA,SAAS,iBAAiB,OAAyC;CACjE,OACE,MAAM,SAAS,uBACf,MAAM,SAAS,aACf,MAAM,SAAS;AAEnB;AAEA,SAAS,yBACP,OACA,MACA,aACA,QAAQ,GACC;CACT,IAAI,SAAA,IAA2B;EAI7B,gBAAgB,aAAa,MAAM,KAAK;EACxC;CACF;CACA,IAAI,OAAO,UAAU,UAAU;EAC7B,MAAM,WAAW,qBAAqB,KAAK;EAC3C,IAAI,aAAa,OACf,gBAAgB,aAAa,MAAM,KAAK;EAE1C,OAAO;CACT;CACA,IAAI,MAAM,QAAQ,KAAK,GACrB,OAAO,MAAM,KAAK,MAAM,UACtB,yBAAyB,MAAM,GAAG,KAAK,GAAG,MAAM,IAAI,aAAa,QAAQ,CAAC,CAC5E;CAEF,IAAI,CAACA,gBAAc,KAAK,GAAG,OAAO;CAClC,IAAI,iBAAiB,KAAK,GAAG,OAAO,gBAAgB,KAAK;CAEzD,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,WAAW,OAAO,QAAQ,KAAK,GAAG;EACjD,MAAM,aAAa,OAAO,GAAG,KAAK,GAAG,QAAQ;EAC7C,MAAM,gBAAgB,wBAAwB,GAAG;EACjD,KACG,kBAAkB,oBAAoB,kBAAkB,cACzDA,gBAAc,MAAM,GACpB;GAIA,MAAM,OAA+B,CAAC;GACtC,MAAM,UAAmC,CAAC;GAC1C,KAAK,MAAM,CAAC,MAAM,gBAAgB,OAAO,QAAQ,MAAM,GACrD,IAAI,OAAO,gBAAgB,YAAY,sBAAsB,WAAW,GACtE,KAAK,QAAQ;QAEb,QAAQ,QAAQ;GAGpB,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAAG,gBAAgB,aAAa,YAAY,OAAO;GACrF,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,GAAG,OAAO,OAAO;GAChD;EACF;EACA,IAAI,sBAAsB,GAAG,GAAG;GAC9B,gBAAgB,aAAa,YAAY,MAAM;GAC/C;EACF;EACA,OAAO,OAAO,yBAAyB,QAAQ,YAAY,aAAa,QAAQ,CAAC;CACnF;CACA,OAAO;AACT;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,QAAQ,OAAO,SAAS,QAAQ,gBAAgB,IAAI,GAAG,CAAC;CAC/F,IAAIA,gBAAc,KAAK,GACrB,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC,QACzB,OAAe,WAAW,QAAQ,gBAAgB,MAAM,GACzD,CACF;CAEF,OAAO;AACT;;;;;;;;;AAUA,MAAM,8CAA8B,IAAI,IAAsC;;;;;;;;;;AAW9E,SAAgB,+BACd,SACM;CACN,4BAA4B,MAAM;CAClC,KAAK,MAAM,CAAC,IAAI,SAAS,SAAS;EAChC,IAAI,OAAO,UAAU,eAAe,KAAK,6BAA6B,EAAE,GAAG;EAC3E,4BAA4B,IAAI,IAAI,IAAI;CAC1C;AACF;;;;;;;;AASA,SAAS,uBAAuB,IAAkD;CAChF,IAAI,OAAO,UAAU,eAAe,KAAK,6BAA6B,EAAE,GACtE,OAAO,4BAA4B;CAErC,OAAO,4BAA4B,IAAI,EAAE;AAC3C;;;;;;;;;;;;;;;;;AAkBA,SAAgB,yBAAyB,SAAqD;CAC5F,MAAM,cAAoC;EACxC,OAAO,CAAC;EACR,cAAc,CAAC;EACf,OAAO;EACP,mBAAmB;CACrB;CACA,MAAM,kBACJ,OAAO,QAAQ,oBAAoB,WAC/B,qBAAqB,QAAQ,eAAe,IAC5C,QAAQ;CACd,IAAI,oBAAoB,QAAQ,iBAC9B,gBAAgB,aAAa,mBAAmB,QAAQ,eAAe;CAEzE,MAAM,YAAY,QAAQ,aAAa,YACnC;EAAE,GAAG,QAAQ,YAAY;EAAW,SAAS,CAAC;CAAE,IAChD,QAAQ,aAAa;CACzB,IAAI,WACF,KAAK,MAAM,SAAS,CAAC,mBAAmB,iBAAiB,GAAY;EACnE,MAAM,WAAW,UAAU;EAC3B,IAAI,OAAO,aAAa,UAAU;EAClC,MAAM,WAAW,qBAAqB,QAAQ;EAC9C,IAAI,aAAa,UAAU;GACzB,UAAU,SAAS;GACnB,gBAAgB,aAAa,yBAAyB,SAAS,QAAQ;EACzE;CACF;CAKF,MAAM,iBAAiB,QAAQ,aAAa,KAAK;CACjD,MAAM,yBACJ,OAAO,mBAAmB,WAAW,qBAAqB,cAAc,IAAI;CAC9E,IAAI,2BAA2B,gBAC7B,gBAAgB,aAAa,kCAAkC,cAAc;CAE/E,MAAM,cAAc,QAAQ,cACxB;EACE,GAAG,QAAQ;EAGX,uBAAuB,QAAQ,YAAY,wBAAwB,CAAC,EAAA,CAAG,QACpE,aAAa,SAAS,WAAW,KACpC;EACA;EACA,GAAI,2BAA2B,iBAC3B,EAAE,KAAK;GAAE,GAAG,QAAQ,YAAY;GAAK,gBAAgB;EAAuB,EAAE,IAC9E,CAAC;CACP,IACA,QAAQ;CAIZ,MAAM,gCACJ,QAAQ,aAAa,sBAAsB,QACxC,aAAa,SAAS,WAAW,SAAS,SAAS,UAAU,EAChE,KAAK,CAAC;CACR,IAAI,8BAA8B,SAAS,GACzC,gBACE,aACA,oCACA,+BACA,8BAA8B,MAChC;CAEF,MAAM,mBAAmB,QAAQ,aAAa,WAAW,WAAW,CAAC;CACrE,IAAI,OAAO,KAAK,gBAAgB,CAAC,CAAC,SAAS,GACzC,gBACE,aACA,iCACA,kBACA,OAAO,KAAK,gBAAgB,CAAC,CAAC,MAChC;CAGF,MAAM,UAAU,QAAQ,UAAU,CAAC,EAAA,CAAG,KAAK,OAAO,UAAU;EAQ1D,MAAM,aAAa,MAAM,cAAc,WAAW;EAClD,MAAM,gBAAgB,yBACpB,MAAM,UACN,UAAU,MAAM,aAChB,WACF;EACA,IAAI,cAAc,qBAAqB,eACrC,OAAO,cAAc;EAEvB,OAAO;GACL,GAAG;GACH,GAAI,aAAa,EAAE,SAAS,KAAA,EAAU,IAAI,CAAC;GAC3C,QAAQ,yBACN,MAAM,QACN,UAAU,MAAM,WAChB,WACF;GACA,UAAU;GACV,GAAI,OAAO,MAAM,eAAe,WAC5B,EACE,YAAY,yBACV,MAAM,YACN,UAAU,MAAM,eAChB,WACF,EACF,IACA,CAAC;GAKL,GAAI,MAAM,aACN,EACE,YAAY,yBACV,MAAM,YACN,UAAU,MAAM,eAChB,WACF,EACF,IACA,CAAC;EACP;CACF,CAAC;CAED,IAAI,UAAU,QAAQ;CACtB,IAAI,SAAS;EACX,MAAM,eAAe,QAAQ,aAAa,KAAK,KAAK,UAAU;GAC5D,MAAM,WAAW,qBAAqB,GAAG;GACzC,IAAI,aAAa,KACf,gBAAgB,aAAa,wBAAwB,MAAM,IAAI,GAAG;GAEpE,OAAO;EACT,CAAC;EACD,MAAM,WAAW,QAAQ,YAAY,CAAC;EACtC,MAAM,OAAgC,CAAC;EACvC,MAAM,UAAmC,CAAC;EAC1C,KAAK,MAAM,CAAC,IAAI,UAAU,OAAO,QAAQ,QAAQ,GAAG;GAClD,MAAM,UAAU,uBAAuB,EAAE;GACzC,IAAI,YAAY,KAAA,GAAW;IACzB,QAAQ,MAAM;IACd;GACF;GACA,IAAI,YAAY,MAAM;IACpB,KAAK,MAAM;IACX;GACF;GAEA,IAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,MAAM,QAAQ,KAAK,GAAG;IACvE,QAAQ,MAAM;IACd;GACF;GACA,MAAM,SAAkC,CAAC;GACzC,MAAM,OAAgC,CAAC;GACvC,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,KAAgC,GACvE,IAAI,QAAQ,SAAS,GAAG,GAAG,OAAO,OAAO;QACpC,KAAK,OAAO;GAEnB,IAAI,OAAO,KAAK,MAAM,CAAC,CAAC,SAAS,GAAG,KAAK,MAAM;GAC/C,IAAI,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,GAAG,QAAQ,MAAM;EAClD;EACA,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,SAAS,GAChC,gBAAgB,aAAa,oBAAoB,SAAS,gBAAgB,OAAO,CAAC;EAIpF,UAAU;GACR,GAAG;GACH;GACA,UAAU,yBAAyB,MAAM,oBAAoB,WAAW;EAI1E;CACF;CAEA,OAAO;EACL,SAAS;GAIP,GAAG;GACH;GACA,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;GACrC;GACA,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAC7B,GAAI,QAAQ,WACR,EACE,UAAU,yBAAyB,QAAQ,UAAU,YAAY,WAAW,EAI9E,IACA,CAAC;EACP;EACA,eAAe,CAAC,GAAG,IAAI,IAAI,YAAY,KAAK,CAAC;EAC7C,sBAAsB,CAAC,GAAG,IAAI,IAAI,YAAY,YAAY,CAAC;EAC3D,gCAAgC,YAAY;EAC5C,eAAe,YAAY;CAC7B;AACF;;;;;;;;;;;AAYA,SAAgB,+BAAkC,OAAa;CAO7D,OAAO,yBAAyB,OAAO,IAAI;EALzC,OAAO,CAAC;EACR,cAAc,CAAC;EACf,OAAO;EACP,mBAAmB;CAEgC,CAAC;AACxD;;AAGA,SAAgB,kBAAkB,SAA2C;CAC3E,OAAO,yBAAyB,OAAO,CAAC,CAAC;AAC3C;;;ACjoBA,MAAa,yCAAyC;AACtD,MAAa,qCAAqC;AAClD,MAAa,qCAAqC;;AAGlD,SAAgB,qCAAqC,OAAwB;CAC3E,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GACrD,OAAA;CAEF,OAAO,KAAK,IACV,oCACA,KAAK,IAAA,GAAwC,KAAK,CACpD;AACF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;ACoBA,MAAa,mCAAmC;AAChD,MAAa,mCAAmC;;AAGhD,MAAa,wBAAwB;;AAGrC,MAAa,4BAA4B;;AAGzC,MAAa,0BAA0B;;;;;;;;AASvC,MAAa,8BAA8B;;AAG3C,MAAa,yBAAyB;;AAGtC,MAAa,kBAAkB;AAC/B,MAAa,kBAAkB;AAC/B,MAAa,2BAA2B;AACxC,MAAa,oBAAoB;;AAEjC,MAAa,uBAAuB;;AAEpC,MAAa,qBAAqB;;AAElC,MAAa,2BAA2B;;AAaxC,MAAa,gCAAqD;;;;;;;;AAwBlE,IAAa,uBAAb,cAA0C,MAAM;CAC9C;CAEA,YAAY,SAAiB,MAAe;EAC1C,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,OAAO;CACd;;CAGA,IAAI,cAAuB;EACzB,OAAO,KAAK,SAAS,OAAO,KAAK,SAAS,OAAO,KAAK,SAAS,OAAO,KAAK,SAAS;CACtF;AACF;;AAGA,IAAa,2BAAb,cAA8C,MAAM;CAClD,YAAY,SAAiB;EAC3B,MAAM,OAAO;EACb,KAAK,OAAO;CACd;AACF;AAmIA,SAAS,YAAY,OAA0D;CAC7E,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI,OAAO;CAClE,MAAM,MAAM,OAAO,KAAK;CACxB,OAAO,OAAO,SAAS,GAAG,IAAI,MAAM;AACtC;;AAGA,SAAS,UAAU,MAAc,UAA2C;CAC1E,IAAI,EAAE,QAAQ,WAAW,OAAO;CAChC,IAAI,SAAS;CACb,OAAO,GAAG,KAAK,GAAG,YAAY,UAAU,UAAU;CAClD,OAAO,GAAG,KAAK,GAAG;AACpB;;;;;;AAOA,SAAS,eAAe,KAAsD;CAC5E,MAAM,QAAgC,CAAC;CACvC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,GAAG,GAAG;EAC9C,IAAI,UAAU,QAAQ,UAAU,KAAA,GAAW;EAC3C,MAAM,OAAO,OAAO,UAAU,WAAW,QAAQ,OAAO,KAAK;CAC/D;CACA,OAAO;AACT;;AAGA,SAAS,WAAW,KAAc,KAAiC;CACjE,IAAI,OAAO,OAAO,QAAQ,YAAY,OAAO,KAAK;EAChD,MAAM,QAAS,IAAgC;EAC/C,IAAI,OAAO,UAAU,UAAU,OAAO;EACtC,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,KAAK;CACpD;AAEF;;AAGA,SAAgB,uBACd,UACA,OACA,UAA8C,CAAC,GACvC;CACR,MAAM,MAAM,IAAI,IAAI,QAAQ;CAC5B,IAAI,aAAa,IAAI,KAAK,KAAK;CAC/B,IAAI,aAAa,IAAI,UAAU,QAAQ;CACvC,IAAI,aAAa,IAAI,kBAAkB,GAAG;CAC1C,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ,SAAS,CAAC,CAAC;CACxD,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,SAAS,QAAQ,KAAK;CAC9D,OAAO,IAAI,SAAS;AACtB;;AAGA,SAAgB,uBACd,UACA,KACA,KACA,UAA6C,CAAC,GACtC;CACR,MAAM,MAAM,IAAI,IAAI,QAAQ;CAC5B,IAAI,aAAa,IAAI,OAAO,OAAO,GAAG,CAAC;CACvC,IAAI,aAAa,IAAI,OAAO,OAAO,GAAG,CAAC;CACvC,IAAI,aAAa,IAAI,UAAU,QAAQ;CACvC,IAAI,aAAa,IAAI,kBAAkB,GAAG;CAC1C,IAAI,QAAQ,SAAS,KAAA,GAAW,IAAI,aAAa,IAAI,QAAQ,OAAO,QAAQ,IAAI,CAAC;CACjF,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,SAAS,QAAQ,KAAK;CAC9D,OAAO,IAAI,SAAS;AACtB;;AAGA,SAAS,8BAA8B,QAAqD;CAC1F,MAAM,MAAM,OAAO,OAAO,GAAG;CAC7B,MAAM,MAAM,OAAO,OAAO,GAAG;CAC7B,IAAI,CAAC,OAAO,SAAS,GAAG,KAAK,CAAC,OAAO,SAAS,GAAG,GAAG,OAAO;CAC3D,OAAO;EACL;EACA;EACA,aAAa,OAAO,gBAAgB;EACpC,OAAO,YAAY,OAAO,UAAU;CACtC;AACF;;;;;;;;;;;;;;;AAgBA,SAAgB,sBACd,OACA,cAAsC,CAAC,GACvC,OACuB;CACvB,IAAI,CAAC,OAAO,SAAS,MAAM,GAAG,KAAK,CAAC,OAAO,SAAS,MAAM,GAAG,GAAG,OAAO;CAEvE,MAAM,aAAsC,EAAE,GAAG,YAAY;CAC7D,MAAM,QAAiC;GACpC,kBAAkB,MAAM;GACxB,kBAAkB,MAAM;GACxB,2BAA2B,MAAM,eAAe;GAChD,oBAAoB,MAAM;CAC7B;CACA,IAAI,OAAO,YAAY;EACrB,MAAM,wBAAwB,MAAM;EACpC,MAAM,sBAAsB;CAC9B;CAEA,IAAI,OAAO,YACT,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,MAAM,cAAc,CAAC,CAAC,GAC9D,MAAM,GAAG,2BAA2B,SAAS;CACjD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,WAAW,UAAU,KAAK,UAAU,KAAK;CAG3C,OAAO;EACL,MAAM;EACN,UAAU;GAAE,MAAM;GAAS,aAAa,CAAC,MAAM,KAAK,MAAM,GAAG;EAAE;EAC/D;CACF;AACF;;;;;;;;;AAUA,SAAgB,wBACd,aACA,YACe;CACf,MAAM,aAAsC,EAAE,GAAG,YAAY;CAC7D,MAAM,QAAiC;GACpC,qBAAqB;GACrB,uBAAuB;CAC1B;CACA,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAK,GAC7C,WAAW,UAAU,KAAK,UAAU,KAAK;CAG3C,OAAO;EAAE,MAAM;EAAW,UAAU;EAAM;CAAW;AACvD;;;;;;;AAQA,SAAgB,yBACd,QACA,cAAsC,CAAC,GAChB;CACvB,MAAM,QAAQ,8BAA8B,MAAM;CAClD,OAAO,QAAQ,sBAAsB,OAAO,WAAW,IAAI;AAC7D;;;;;AAMA,SAAgB,gCACd,QAC8B;CAC9B,IAAI,CAAC,UAAU,OAAO,OAAO,OAAO;CACpC,MAAM,cAAc,OAAO,cAAc,KAAK;CAC9C,IAAI,CAAC,aAAa,OAAO;CACzB,OAAO;EAAE;EAAa,OAAO,OAAO,WAAW,CAAC;CAAE;AACpD;AAIA,MAAM,oBAAuC;CAC3C,IAAI;CACJ,OAAO;CACP,SAAS;CACT,SAAS;CACT,gBAAgB;CAChB,eAAe;CACf,wBAAwB;CACxB,wBAAwB;CACxB,kBAAkB,QAAQ,OAAO,YAC/B,uBAAuB,OAAO,iBAAiB,OAAO;EACpD,OAAO,QAAQ;EACf,OAAO,QAAQ;CACjB,CAAC;CACH,eAAe,SACb,MAAM,QAAQ,IAAI,IACb,KACE,IAAI,6BAA6B,CAAC,CAClC,QAAQ,MAAyB,MAAM,IAAI,IAC9C,CAAC;CACP,kBAAkB,QAAQ,KAAK,KAAK,YAClC,uBAAuB,OAAO,iBAAiB,KAAK,KAAK;EACvD,OAAO,QAAQ;EACf,MAAM,QAAQ;CAChB,CAAC;CACH,eAAe,SACb,gCACE,QAAQ,OAAO,SAAS,WAAY,OAAkC,IACxE;AACJ;;AAGA,SAAS,gBAAgB,MAA0C;CACjE,IACE,QACA,OAAO,SAAS,YAChB,MAAM,QAAS,KAAgC,QAAQ,GAEvD,OAAQ,KAAiC,SAAS,QAC/C,MAAoC,CAAC,CAAC,KAAK,OAAO,MAAM,QAC3D;CAEF,OAAO,CAAC;AACV;;AAGA,SAAS,YAAY,SAA2D;CAE9E,MAAM,SADW,QAAQ,UACA;CACzB,IAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,UAAU,GAAG;EAC/C,MAAM,MAAM,OAAO,OAAO,EAAE;EAC5B,MAAM,MAAM,OAAO,OAAO,EAAE;EAC5B,IAAI,OAAO,SAAS,GAAG,KAAK,OAAO,SAAS,GAAG,GAAG,OAAO,CAAC,KAAK,GAAG;CACpE;CACA,OAAO;AACT;AAEA,MAAM,iBAAoC;CACxC,IAAI;CACJ,OAAO;CACP,SAAS;CACT,SAAS;CAGT,gBAAgB;CAChB,eAAe;CACf,wBAAwB;CACxB,wBAAwB;CACxB,kBAAkB,QAAQ,OAAO,YAAY;EAC3C,MAAM,MAAM,IAAI,IAAI,OAAO,eAAe;EAC1C,IAAI,aAAa,IAAI,QAAQ,KAAK;EAClC,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,QAAQ,OAAO,QAAQ,KAAK,CAAC;EACrE,IAAI,OAAO,QAAQ,IAAI,aAAa,IAAI,WAAW,OAAO,MAAM;EAChE,OAAO,IAAI,SAAS;CACtB;CACA,eAAe,SACb,gBAAgB,IAAI,CAAC,CAClB,KAAK,YAAY;EAChB,MAAM,SAAS,YAAY,OAAO;EAClC,IAAI,CAAC,QAAQ,OAAO;EACpB,MAAM,QAAS,QAAQ,cAA0C,CAAC;EAClE,OAAO;GACL,KAAK,OAAO;GACZ,KAAK,OAAO;GACZ,aAAa,WAAW,OAAO,OAAO,KAAK;GAC3C,OAAO,YAAY,MAAM,UAAgC;EAC3D;CACF,CAAC,CAAC,CACD,QAAQ,MAAyB,MAAM,IAAI;CAChD,kBAAkB,QAAQ,KAAK,QAAQ;EACrC,MAAM,MAAM,IAAI,IAAI,OAAO,eAAe;EAC1C,IAAI,aAAa,IAAI,aAAa,OAAO,GAAG,CAAC;EAC7C,IAAI,aAAa,IAAI,aAAa,OAAO,GAAG,CAAC;EAC7C,IAAI,OAAO,QAAQ,IAAI,aAAa,IAAI,WAAW,OAAO,MAAM;EAChE,OAAO,IAAI,SAAS;CACtB;CACA,eAAe,SAAS;EACtB,MAAM,UAAU,gBAAgB,IAAI,CAAC,CAAC;EACtC,IAAI,CAAC,SAAS,OAAO;EACrB,MAAM,QAAS,QAAQ,cAA0C,CAAC;EAClE,MAAM,cAAc,WAAW,OAAO,OAAO,CAAC,EAAE,KAAK;EACrD,OAAO,cAAc;GAAE;GAAa,OAAO,eAAe,KAAK;EAAE,IAAI;CACvE;AACF;AAEA,MAAM,iBAAoC;CACxC,IAAI;CACJ,OAAO;CACP,SAAS;CACT,SAAS;CACT,gBAAgB;CAChB,eAAe;CACf,wBACE;CACF,wBACE;CACF,kBAAkB,QAAQ,OAAO,YAAY;EAC3C,MAAM,MAAM,IAAI,IAAI,OAAO,eAAe;EAC1C,IAAI,aAAa,IAAI,cAAc,KAAK;EACxC,IAAI,aAAa,IAAI,KAAK,MAAM;EAChC,IAAI,aAAa,IAAI,aAAa,YAAY;EAC9C,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,gBAAgB,OAAO,QAAQ,KAAK,CAAC;EAC7E,IAAI,OAAO,QAAQ,IAAI,aAAa,IAAI,SAAS,OAAO,MAAM;EAC9D,OAAO,IAAI,SAAS;CACtB;CACA,cAAc;CACd,kBAAkB,QAAQ,KAAK,QAAQ;EACrC,MAAM,MAAM,IAAI,IAAI,OAAO,eAAe;EAC1C,IAAI,aAAa,IAAI,YAAY,GAAG,IAAI,GAAG,KAAK;EAChD,IAAI,aAAa,IAAI,KAAK,MAAM;EAChC,IAAI,OAAO,QAAQ,IAAI,aAAa,IAAI,SAAS,OAAO,MAAM;EAC9D,OAAO,IAAI,SAAS;CACtB;CACA,cAAc;AAChB;;AAKA,MAAM,qCAAqB,IAAI,IAAI;CAAC;CAAQ;CAAQ;CAAM;AAAM,CAAC;;AAEjE,MAAM,sBAAsB;;AAE5B,MAAM,oBAAoB;;AAG1B,SAAS,mBAAmB,MAA4C;CACtE,MAAM,QAAQ,QAAQ,OAAO,SAAS,WAAY,KAA6B,QAAQ,KAAA;CACvF,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,OAAO,OAAQ,MAA6B,IAAI;CAEtD,OAAO,IAAI,qBADK,WAAW,OAAO,SAAS,KAAK,kCACP,OAAO,SAAS,IAAI,IAAI,OAAO,KAAA,CAAS;AACnF;;AAGA,SAAS,qBAAqB,kBAA0C;CACtE,IAAI,CAAC,oBAAoB,OAAO,qBAAqB,UAAU,OAAO;CACtE,MAAM,KAAK;CACX,MAAM,OAAO,OAAO,GAAG,cAAc,GAAG,IAAI;CAC5C,OAAO,OAAO,SAAS,IAAI,IAAI,OAAO;AACxC;;;;;;;;;;;;;;AAeA,SAAS,wBACP,GACA,GACA,MACqC;CACrC,IAAI,CAAC,OAAO,SAAS,CAAC,KAAK,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO;CACvD,IAAI,MAAM;CACV,IAAI,MAAM;CACV,IAAI,SAAS,QAAQ,mBAAmB,IAAI,IAAI,GAAG;EACjD,MAAO,IAAI,uBAAwB,MAAM,KAAK;EAC9C,OAAO,IAAI,KAAK,KAAK,KAAK,IAAI,IAAI,mBAAmB,CAAC,IAAI,KAAK,KAAK,MAAM,MAAM,KAAK;CACvF,OAAO,IAAI,SAAS,QAAQ,SAAS,MACnC,MAAM,IAAI,qBACR,0DAA0D,KAAK,0CACjE;CAEF,IAAI,MAAM,QAAQ,MAAM,OAAO,MAAM,OAAO,MAAM,IAAI,OAAO;CAC7D,OAAO;EAAE;EAAK;CAAI;AACpB;;;;;AAMA,SAAS,oBACP,OACuD;CACvD,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO,KAAA;CAChD,MAAM,OAAkD,CAAC;CACzD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,KAAgC,GAAG;EAC3E,IAAI,uDAAuD,KAAK,GAAG,GAAG;EACtE,IACE,OAAO,UAAU,WACb,MAAM,KAAK,MAAM,KACjB,OAAO,UAAU,YAAY,OAAO,UAAU,WAElD;EACF,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GAAG;EAC1D,KAAK,OAAO;CACd;CACA,OAAO,OAAO,KAAK,IAAI,CAAC,CAAC,SAAS,OAAO,KAAA;AAC3C;;;;;;;;AASA,SAAS,sBAAsB,MAA+B;CAC5D,MAAM,QAAQ,mBAAmB,IAAI;CACrC,IAAI,OAAO,MAAM;CACjB,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU,OAAO,CAAC;CAC/C,MAAM,OAAO;CACb,IAAI,CAAC,MAAM,QAAQ,KAAK,UAAU,GAAG,OAAO,CAAC;CAC7C,MAAM,OAAO,qBAAqB,KAAK,gBAAgB;CACvD,OAAQ,KAAK,WACV,KAAK,cAAc;EAClB,MAAM,WAAW,UAAU;EAC3B,MAAM,WAAW,wBAAwB,OAAO,UAAU,CAAC,GAAG,OAAO,UAAU,CAAC,GAAG,IAAI;EACvF,IAAI,CAAC,UAAU,OAAO;EACtB,MAAM,aAAa,oBAAoB,UAAU,UAAU;EAC3D,OAAO;GACL,GAAG;GACH,aAAa,WAAW,WAAW,SAAS,KAAK;GACjD,OAAO,YAAY,UAAU,KAA2B;GACxD,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;EACrC;CACF,CAAC,CAAC,CACD,QAAQ,MAAyB,MAAM,IAAI;AAChD;;;;;;AAOA,SAAS,iBAAiB,MAAe,OAAsC;CAC7E,IAAI,MAAM,SAAS,KAAK,OAAO;CAC/B,MAAM,WAAY,KAA2C;CAC7D,MAAM,UAAU,MAAM,QAAQ,UAAU,OAAO,IAAI,SAAS,QAAQ,KAAK,GAAG,IAAI;CAChF,OAAO,0BAA0B,KAAK,GAAG,MAAM,QAAQ,GAAG,SAAS;AACrE;;;;;;;;;AAUA,SAAS,mBAAmB,MAA6C;CACvE,MAAM,QAAQ,mBAAmB,IAAI;CACrC,IAAI,SAAS,CAAC,iBAAiB,MAAM,KAAK,GAAG,MAAM;CACnD,MAAM,UACJ,QAAQ,OAAO,SAAS,WAAY,KAA+B,UAAU,KAAA;CAC/E,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU,OAAO;CACpD,MAAM,eACJ,WAAW,SAAS,WAAW,KAC/B,WAAW,SAAS,YAAY,KAChC,WAAW,SAAS,SAAS,EAAA,EAC5B,KAAK;CACR,OAAO,cACH;EAAE;EAAa,OAAO,eAAe,OAAkC;CAAE,IACzE;AACN;;;;;;;;;AAUA,SAAgB,0BAA0B,OAAiD;CACzF,IAAI;CACJ,IAAI;EACF,MAAM,IAAI,KAAK,SAAS,GAAA,CAAI,KAAK,CAAC;CACpC,QAAQ;EACN,OAAO;CACT;CACA,IAAI,IAAI,aAAa,YAAY,IAAI,aAAa,SAAS,OAAO;CAClE,MAAM,OAAO,IAAI,SAAS,QAAQ,QAAQ,EAAE,CAAC,CAAC,QAAQ,mBAAmB,EAAE;CAC3E,IAAI,CAAC,oBAAoB,KAAK,IAAI,GAAG,OAAO;CAC5C,IAAI,WAAW;CACf,IAAI,SAAS;CACb,IAAI,OAAO;CACX,IAAI,WAAW;CACf,IAAI,WAAW;CACf,OAAO,IAAI,SAAS;AACtB;;;;;;;;;AAUA,SAAgB,0BAA0B,OAAyC;CACjF,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,YAAY;CAClB,MAAM,KAAK,OAAO,UAAU,OAAO,WAAW,UAAU,GAAG,KAAK,IAAI;CACpE,MAAM,MAAM,0BACV,OAAO,UAAU,QAAQ,WAAW,UAAU,MAAM,KAAA,CACtD;CACA,IAAI,CAAC,MAAM,CAAC,KAAK,OAAO;CACxB,MAAM,QAAQ,QACZ,OAAO,UAAU,SAAS,YAAa,UAAU,IAAI,CAAY,KAAK,IACjE,UAAU,IAAI,CAAY,KAAK,IAChC,KAAA;CACN,MAAM,QAAQ,QACZ,OAAO,UAAU,SAAS,YAAa,UAAU,OAAmB,KAAA;CACtE,MAAM,gBAAgB,MAAM,QAAQ,UAAU,aAAa,IACvD,UAAU,cAAc,QACrB,UAA2B,OAAO,UAAU,YAAY,MAAM,KAAK,MAAM,EAC5E,IACA,KAAA;CACJ,MAAM,UAA4B;EAAE;EAAI,MAAM,KAAK,MAAM,KAAK;EAAI;CAAI;CACtE,MAAM,YAAY,KAAK,WAAW;CAClC,IAAI,WAAW,QAAQ,YAAY;CACnC,MAAM,kBAAkB,KAAK,iBAAiB;CAC9C,IAAI,iBAAiB,QAAQ,kBAAkB;CAC/C,IAAI,eAAe,QAAQ,QAAQ,gBAAgB;CACnD,MAAM,UAAU,KAAK,SAAS;CAC9B,IAAI,YAAY,KAAA,GAAW,QAAQ,UAAU;CAC7C,MAAM,UAAU,KAAK,SAAS;CAC9B,IAAI,YAAY,KAAA,GAAW,QAAQ,UAAU;CAC7C,MAAM,cAAc,KAAK,aAAa;CACtC,IAAI,aAAa,QAAQ,cAAc;CACvC,OAAO;AACT;;;;;;;;;;;;AAiBA,SAAgB,uBAAuB,MAA0C;CAC/E,MAAM,QAAQ,mBAAmB,IAAI;CACrC,IAAI,OAAO,MAAM;CACjB,IAAI,CAAC,QAAQ,OAAO,SAAS,UAC3B,MAAM,IAAI,qBAAqB,oDAAoD;CAErF,MAAM,OAAO;CACb,MAAM,aAAa,UACjB,SAAS,OAAO,UAAU,WAAW,WAAW,OAAO,MAAM,CAAC,EAAE,KAAK,KAAK,KAAA,IAAY,KAAA;CACxF,MAAM,gBAAgB,MAAM,QAAQ,KAAK,aAAa,IAClD,KAAK,cAAc,IAAI,SAAS,CAAC,CAAC,QAAQ,SAAyB,CAAC,CAAC,IAAI,IACzE,CAAC;CACL,MAAM,kBAAkB,UAAU,KAAK,sBAAsB;CAC7D,IAAI,CAAC,mBAAmB,CAAC,cAAc,QACrC,MAAM,IAAI,qBAAqB,oDAAoD;CAErF,MAAM,SACJ,OAAO,KAAK,iBAAiB,WACzB,IAAI,IAAI,KAAK,aAAa,MAAM,GAAG,CAAC,CAAC,KAAK,UAAU,MAAM,KAAK,CAAC,CAAC,YAAY,CAAC,CAAC,IAC/E;CACN,IAAI,UAAU,CAAC,OAAO,IAAI,SAAS,GACjC,MAAM,IAAI,yBAAyB,0CAA0C;CAE/E,OAAO;EACL;EACA,eAAe,cAAc,SAAS,gBAAgB,KAAA;EACtD,SAAS,SAAS,OAAO,IAAI,gBAAgB,IAAI;EACjD,SAAS,SAAS,OAAO,IAAI,SAAS,IAAI;CAC5C;AACF;;;;;;;;;;AAWA,SAAgB,wBAAwB,SAAmC;CAEzE,MAAM,YAAY,CAAC,QAAQ,iBAAiB,GAAI,QAAQ,iBAAiB,CAAC,CAAE;CAC5E,IAAI,QAAQ,eAAe,UAAU,SAAS,QAAQ,WAAW,GAAG,OAAO,QAAQ;CACnF,IAAI,QAAQ,iBAAiB,OAAO,QAAQ;CAC5C,IAAI,QAAQ,eAAe,WAAW,GAAG,OAAO,QAAQ,cAAc;CACtE,OAAO;AACT;;AAGA,SAAS,eAAe,QAA0C;CAChE,IAAI,CAAC,OAAO,SACV,MAAM,IAAI,yBAAyB,8CAA8C;CAEnF,OAAO,OAAO;AAChB;AAEA,MAAM,wBAA2C;CAC/C,IAAI;CACJ,OAAO;CACP,SAAS;CAET,SAAS;CAET,gBAAgB;CAChB,eAAe;CAEf,wBAAwB;CACxB,wBAAwB;CACxB,kBAAkB,QAAQ,OAAO,YAAY;EAC3C,MAAM,UAAU,eAAe,MAAM;EACrC,MAAM,MAAM,IAAI,IAAI,OAAO,mBAAmB,GAAG,QAAQ,IAAI,uBAAuB;EACpF,IAAI,aAAa,IAAI,wBAAwB,OAAO,GAAG,KAAK;EAC5D,IAAI,aAAa,IAAI,KAAK,MAAM;EAGhC,IAAI,aAAa,IAAI,SAAS,MAAM;EAGpC,IAAI,QAAQ,YAAY,IAAI,aAAa,IAAI,aAAa,GAAG;EAC7D,IAAI,QAAQ,UAAU,IAAI,aAAa,IAAI,YAAY,QAAQ,QAAQ;EACvE,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,gBAAgB,OAAO,QAAQ,KAAK,CAAC;EAC7E,OAAO,IAAI,SAAS;CACtB;CACA,cAAc;CACd,kBAAkB,QAAQ,KAAK,QAAQ;EACrC,MAAM,UAAU,eAAe,MAAM;EACrC,MAAM,MAAM,IAAI,IAAI,OAAO,mBAAmB,GAAG,QAAQ,IAAI,gBAAgB;EAC7E,IAAI,aAAa,IACf,YACA,KAAK,UAAU;GAAE,GAAG;GAAK,GAAG;GAAK,kBAAkB,EAAE,MAAM,KAAK;EAAE,CAAC,CACrE;EACA,IAAI,aAAa,IAAI,SAAS,MAAM;EACpC,IAAI,aAAa,IAAI,KAAK,MAAM;EAChC,OAAO,IAAI,SAAS;CACtB;CACA,cAAc;AAChB;AAEA,MAAM,iBAAoC;CACxC,IAAI;CACJ,OAAO;CACP,SAAS;CACT,SAAS;CACT,gBAAgB;CAChB,eAAe;CACf,wBAAwB;CACxB,wBAAwB;CACxB,kBAAkB,QAAQ,OAAO,YAAY;EAC3C,MAAM,OAAO,OAAO,gBAAgB,QAAQ,QAAQ,EAAE;EACtD,MAAM,MAAM,IAAI,IAAI,GAAG,KAAK,GAAG,mBAAmB,KAAK,EAAE,MAAM;EAC/D,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,SAAS,OAAO,QAAQ,KAAK,CAAC;EACtE,IAAI,OAAO,QAAQ,IAAI,aAAa,IAAI,gBAAgB,OAAO,MAAM;EACrE,OAAO,IAAI,SAAS;CACtB;CACA,eAAe,SACb,gBAAgB,IAAI,CAAC,CAClB,KAAK,YAAY;EAChB,MAAM,SAAS,QAAQ;EACvB,IAAI,SAAkC;EACtC,IAAI,MAAM,QAAQ,MAAM,KAAK,OAAO,UAAU,GAAG;GAC/C,MAAM,MAAM,OAAO,OAAO,EAAE;GAC5B,MAAM,MAAM,OAAO,OAAO,EAAE;GAC5B,IAAI,OAAO,SAAS,GAAG,KAAK,OAAO,SAAS,GAAG,GAAG,SAAS,CAAC,KAAK,GAAG;EACtE;EACA,WAAW,YAAY,OAAO;EAC9B,IAAI,CAAC,QAAQ,OAAO;EACpB,OAAO;GACL,KAAK,OAAO;GACZ,KAAK,OAAO;GACZ,aAAa,WAAW,SAAS,YAAY,KAAK;GAClD,OAAO,YAAY,QAAQ,SAA+B;EAC5D;CACF,CAAC,CAAC,CACD,QAAQ,MAAyB,MAAM,IAAI;CAChD,kBAAkB,QAAQ,KAAK,QAAQ;EACrC,MAAM,OAAO,OAAO,gBAAgB,QAAQ,QAAQ,EAAE;EACtD,MAAM,MAAM,IAAI,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,MAAM;EAChD,IAAI,OAAO,QAAQ,IAAI,aAAa,IAAI,gBAAgB,OAAO,MAAM;EACrE,OAAO,IAAI,SAAS;CACtB;CACA,eAAe,SAAS;EACtB,MAAM,UAAU,gBAAgB,IAAI,CAAC,CAAC;EACtC,IAAI,CAAC,SAAS,OAAO;EACrB,MAAM,cAAc,WAAW,SAAS,YAAY,CAAC,EAAE,KAAK;EAC5D,OAAO,cACH;GACE;GACA,OAAO,eAAgB,QAAQ,cAA0C,CAAC,CAAC;EAC7E,IACA;CACN;AACF;;;;;;;;AASA,SAAS,mBAAmB,MAA8B;CACxD,MAAM,SAAS,WAAW,MAAM,QAAQ;CACxC,IAAI,CAAC,UAAU,WAAW,QAAQ,WAAW,gBAAgB,OAAO;CACpE,MAAM,SAAS,WAAW,MAAM,eAAe;CAC/C,OAAO,SACH,0BAA0B,OAAO,KAAK,WACtC,0BAA0B;AAChC;AAEA,MAAM,iBAAoC;CACxC,IAAI;CACJ,OAAO;CACP,SAAS;CACT,SAAS;CACT,gBAAgB;CAChB,eAAe;CACf,uBAAuB;CACvB,wBAAwB;CACxB,wBAAwB;CAGxB,kBAAkB,QAAQ,UAAU;EAClC,MAAM,MAAM,IAAI,IAAI,OAAO,eAAe;EAC1C,IAAI,aAAa,IAAI,WAAW,KAAK;EACrC,IAAI,OAAO,QAAQ,IAAI,aAAa,IAAI,OAAO,OAAO,MAAM;EAC5D,OAAO,IAAI,SAAS;CACtB;CACA,eAAe,SAAS;EACtB,MAAM,QAAQ,mBAAmB,IAAI;EACrC,IAAI,OAAO,MAAM,IAAI,MAAM,KAAK;EAChC,OAAO,mBAAmB,IAAI;CAChC;CACA,kBAAkB,QAAQ,KAAK,QAAQ;EACrC,MAAM,MAAM,IAAI,IAAI,OAAO,eAAe;EAC1C,IAAI,aAAa,IAAI,UAAU,GAAG,IAAI,GAAG,KAAK;EAC9C,IAAI,OAAO,QAAQ,IAAI,aAAa,IAAI,OAAO,OAAO,MAAM;EAC5D,OAAO,IAAI,SAAS;CACtB;CACA,eAAe,SAAS;EACtB,MAAM,QAAQ,mBAAmB,IAAI;EACrC,IAAI,OAAO,MAAM,IAAI,MAAM,KAAK;EAKhC,MAAM,cAAc,YAHlB,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAS,KAA+B,OAAO,IACnF,KAAgC,UAClC,CAAC,EAAA,CACgC,IAAI,mBAAmB,CAAC,EAAE,KAAK;EACtE,OAAO,cAAc;GAAE;GAAa,OAAO,CAAC;EAAE,IAAI;CACpD;AACF;;AAGA,SAAS,mBAAmB,MAA+B;CAKzD,QAHE,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAS,KAA+B,OAAO,IACnF,KAAgC,UAClC,CAAC,EAAA,CAEJ,KAAK,WAAgC;EAEpC,MAAM,WADW,OAAO,UACG;EAC3B,MAAM,MAAM,OAAO,UAAU,GAAG;EAChC,MAAM,MAAM,OAAO,UAAU,GAAG;EAChC,IAAI,CAAC,OAAO,SAAS,GAAG,KAAK,CAAC,OAAO,SAAS,GAAG,GAAG,OAAO;EAC3D,OAAO;GACL;GACA;GACA,aAAa,WAAW,QAAQ,mBAAmB,KAAK;GACxD,OAAO;EACT;CACF,CAAC,CAAC,CACD,QAAQ,MAAyB,MAAM,IAAI;AAChD;;;;;;;AAyCA,SAAS,sBAAsB,OAA+B;CAC5D,IAAI,OAAO,UAAU,UAAU,OAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;CACvE,IAAI,OAAO,UAAU,YAAY,CAAC,MAAM,KAAK,GAAG,OAAO;CACvD,MAAM,SAAS,OAAO,KAAK;CAC3B,OAAO,OAAO,SAAS,MAAM,IAAI,SAAS;AAC5C;;AAGA,SAAS,kBAAkB,GAA2D;CACpF,MAAM,MAAM,sBAAsB,EAAE,GAAG;CACvC,MAAM,MAAM,sBAAsB,EAAE,GAAG;CACvC,IAAI,QAAQ,QAAQ,QAAQ,MAAM,OAAO;CACzC,IAAI,MAAM,OAAO,MAAM,MAAM,MAAM,QAAQ,MAAM,KAAK,OAAO;CAC7D,OAAO;EAAE;EAAK;CAAI;AACpB;;AAGA,MAAM,+BAA+B;AACrC,MAAM,+BACJ;;;;;AAMF,SAAS,uBAAuB,GAA8B;CAC5D,MAAM,QAAkB,CAAC;CACzB,MAAM,cAAc,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,CACvC,QAAQ,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CACpC,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;CAC9B,IAAI,YAAY,QAAQ;EACtB,IAAI,SAAS,YAAY,KAAK,GAAG;EACjC,MAAM,eACJ,EAAE,iBAAiB,KAAA,KAAa,EAAE,iBAAiB,OAAO,KAAK,OAAO,EAAE,YAAY,CAAC,CAAC,KAAK;EAC7F,IAAI,cAAc,UAAU,IAAI;EAChC,MAAM,KAAK,MAAM;CACnB;CAMA,MAAM,eAAe,CAAC,EAAE,WAAW,EAAE,IAAI,CAAC,CAAC,MAAM,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC;CAC5E,MAAM,WAAW,CAAC,EAAE,YAAY,YAAY,CAAC,CAC1C,QAAQ,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CACpC,KAAK,MAAM,OAAO,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAC5B,KAAK,GAAG;CACX,IAAI,UAAU,MAAM,KAAK,QAAQ;CACjC,MAAM,WAAW,EAAE,WAAW,OAAO,EAAE,QAAQ,CAAC,CAAC,KAAK,IAAI;CAC1D,IAAI,UAAU,MAAM,KAAK,IAAI,SAAS,EAAE;CACxC,OAAO,MAAM,KAAK,IAAI,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,QAAQ;AACnE;;AAsEA,MAAa,sBAAoD;CAC/D;CACA;CACA;CACA;CACA;CACA;CACA;EA1EA,IAAI;EACJ,OAAO;EACP,SAAS;EACT,SAAS;EACT,gBAAgB;EAChB,eAAe;EACf,wBAAwB;EACxB,wBAAwB;EACxB,kBAAkB,QAAQ,UAAU;GAClC,MAAM,MAAM,IAAI,IAAI,OAAO,eAAe;GAC1C,IAAI,aAAa,IAAI,KAAK,KAAK;GAG/B,OAAO,IAAI,SAAS;EACtB;EACA,eAAe,SAAS;GAItB,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG,OAAO,CAAC;GACtE,MAAM,IAAI;GACV,MAAM,SAAS,kBAAkB,CAAC;GAClC,IAAI,CAAC,QAAQ,OAAO,CAAC;GACrB,MAAM,cAAc,uBAAuB,CAAC,CAAC,CAAC,KAAK;GACnD,IAAI,CAAC,aAAa,OAAO,CAAC;GAC1B,OAAO,CACL;IACE,KAAK,OAAO;IACZ,KAAK,OAAO;IACZ;IACA,OAAO;GACT,CACF;EACF;EACA,kBAAkB,QAAQ,KAAK,QAAQ;GACrC,MAAM,MAAM,IAAI,IAAI,OAAO,eAAe;GAC1C,IAAI,aAAa,IAAI,OAAO,OAAO,GAAG,CAAC;GACvC,IAAI,aAAa,IAAI,OAAO,OAAO,GAAG,CAAC;GACvC,OAAO,IAAI,SAAS;EACtB;EACA,eAAe,SAAS;GACtB,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG,OAAO;GACrE,MAAM,IAAI;GACV,IAAI,CAAC,kBAAkB,CAAC,GAAG,OAAO;GAClC,MAAM,cAAc,uBAAuB,CAAC,CAAC,CAAC,KAAK;GACnD,IAAI,CAAC,aAAa,OAAO;GAEzB,MAAM,QAAgC,CAAC;GACvC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ;IACxC,MAAM,EAAE;IACR,SAAS,EAAE;IACX,SAAS,EAAE;IACX,cAAc,EAAE;IAChB,YAAY,EAAE;IACd,WAAW,EAAE;IACb,MAAM,EAAE;IACR,UAAU,EAAE;IACZ,mBAAmB,EAAE;IACrB,cAAc,EAAE;GAClB,CAAC,GACC,IAAI,UAAU,KAAA,KAAa,UAAU,QAAQ,UAAU,IAAI,MAAM,OAAO,OAAO,KAAK;GAEtF,OAAO;IAAE;IAAa;GAAM;EAC9B;CAWA;AACF;AAEA,MAAM,kBAAkB,IAAI,IAC1B,oBAAoB,KAAK,aAAa,CAAC,SAAS,IAAI,QAAQ,CAAC,CAC/D;;AAGA,SAAgB,6BACd,OACqB;CACrB,MAAM,KAAK,OAAO,KAAK;CACvB,OAAO,MAAM,gBAAgB,IAAI,EAAE,IAAI,KAAK;AAC9C;;AAGA,SAAgB,qBAAqB,IAAkD;CACrF,OAAO,gBAAgB,IAAI,6BAA6B,EAAE,CAAC,KAAK;AAClE;;;;;;;;;AAUA,SAAgB,oBAAoC;CAClD,MAAM,MAAM,sBAAsB;CAClC,MAAM,WAAW,qBAAqB,IAAI,sBAAsB;CAIhE,IAAI;CACJ,IAAI,SAAS,OAAO,oBAAoB,IAAI,uBAC1C,IAAI;EACF,UAAU,0BAA0B,KAAK,MAAM,IAAI,qBAAqB,CAAC,KAAK,KAAA;CAChF,QAAQ;EACN,UAAU,KAAA;CACZ;CAEF,MAAM,YAAY,UAAU,iBAAiB,OAAO,IAAI;CAGxD,MAAM,YAAY,SAAS,OAAO,mBAAmB,CAAC,IAAI;CAC1D,OAAO;EACL,YAAY,SAAS;EACrB,iBAAiB,UAAU,wBAAwB,KAAK,KAAK,UAAU;EACvE,iBACE,UAAU,gCAAgC,KAAK,KAAK,UAAU;EAChE,OAAO,IAAI,qBAAqB,KAAK,KAAK,KAAA;EAC1C,QAAQ,IAAI,uBAAuB,KAAK,KAAK,KAAA;EAC7C,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;CAC/B;AACF;;AAGA,SAAS,iBAAiB,SAGxB;CACA,OAAO;EACL,wBAAwB,GAAG,QAAQ,IAAI;EACvC,wBAAwB,GAAG,QAAQ,IAAI;CACzC;AACF;;;;;;;;AASA,SAAgB,yBACd,OAC8B;CAC9B,MAAM,WAAW,MAAM,YAAY,CAAC;CACpC,OAAO,SAAS,MAAM,YAAY,QAAQ,OAAO,MAAM,SAAS,KAAK,SAAS;AAChF;;;;;;AAuBA,SAAgB,sBAAsB,OAAiD;CACrF,MAAM,WAAW,qBAAqB,MAAM,UAAU;CACtD,IAAI,SAAS,OAAO,kBAAkB;EAGpC,MAAM,UAAU,yBAAyB,KAAK;EAC9C,MAAM,YAAY,UAAU,iBAAiB,OAAO,IAAI;EACxD,OAAO;GACL,YAAY,SAAS;GACrB,iBAAiB,UAAU;GAC3B,iBAAiB,UAAU;GAC3B,OAAO,MAAM,OAAO,KAAK,KAAK,KAAA;GAC9B,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAC/B;CACF;CACA,OAAO;EACL,YAAY,SAAS;EACrB,iBAAiB,MAAM,iBAAiB,KAAK,KAAK,SAAS;EAC3D,iBAAiB,MAAM,iBAAiB,KAAK,KAAK,SAAS;EAC3D,OAAO,MAAM,OAAO,KAAK,KAAK,KAAA;EAC9B,QAAQ,MAAM,UAAU,SAAS,GAAG,EAAE,KAAK,KAAK,KAAA;CAClD;AACF;;AAGA,SAAS,iBAAiB,UAAiC;CACzD,IAAI;EACF,OAAO,IAAI,IAAI,QAAQ,CAAC,CAAC;CAC3B,QAAQ;EACN,OAAO;CACT;AACF;;AAGA,SAAS,kBAAkB,UAAkC;CAC3D,IAAI,aAAa,MAAM,OAAO;CAC9B,OAAO,aAAA,oBAAwC,SAAS,SAAS,iBAA6B;AAChG;;;;;;;;;;;AAYA,SAAgB,eAAe,UAA2B;CACxD,MAAM,WAAW,iBAAiB,QAAQ;CAC1C,OAAO,aAAa,QAAQ,aAAA;AAC9B;;;;;;;;AASA,SAAgB,OAAO,UAA0B;CAC/C,OAAO,eAAe,QAAQ,IAAI,yBAAyB,OAAO;AACpE;;;;;;;;;;;;;;;AAgBA,SAAgB,sBAAsB,UAA0B;CAC9D,IAAI,eAAe,QAAQ,GAAG,OAAO;CACrC,OAAO,kBAAkB,iBAAiB,QAAQ,CAAC,IAAA,MAAkC;AACvF;;AAGA,MAAM,4BAA4B;;;;;;;AAQlC,SAAgB,oBAAoB,QAAiC;CACnE,MAAM,WAAW,qBAAqB,OAAO,UAAU;CACvD,IAAI,SAAS,gBAAgB,OAAO;CACpC,IAAI,SAAS,OAAO,UAClB,IAAI;EACF,MAAM,OAAO,IAAI,IAAI,OAAO,eAAe,CAAC,CAAC;EAC7C,OAAO,SAAS,6BAA6B,KAAK,SAAS,IAAI,2BAA2B;CAC5F,QAAQ;EACN,OAAO;CACT;CAEF,OAAO;AACT;;;;;;;AAQA,SAAgB,YAAY,eAA8B,KAAa,YAA4B;CACjG,IAAI,kBAAkB,MAAM,OAAO;CACnC,OAAO,KAAK,IAAI,GAAG,cAAc,MAAM,cAAc;AACvD;;;;;;;AAQA,SAAgB,yBACd,MACA,gBACkB;CAClB,MAAM,WAA6B,CAAC;CACpC,KAAK,SAAS,KAAK,UAAU;EAC3B,MAAM,UAAU,eACb,KAAK,YAAY,IAAI,WAAW,GAAA,CAAI,KAAK,CAAC,CAAC,CAC3C,OAAO,OAAO,CAAC,CACf,KAAK,IAAI,CAAC,CACV,KAAK;EACR,IAAI,CAAC,SAAS;EACd,SAAS,KAAK;GAAE;GAAO;GAAS;EAAI,CAAC;CACvC,CAAC;CACD,OAAO;AACT;;;;;;AAOA,IAAI,iBAAiD;;;;;;;;;;;;;;;;;AAkBrD,SAAgB,kBAAkB,WAAiD;CACjF,iBAAiB;AACnB;AAQA,IAAI,yBAAwD;;;;;;;;;;;AAY5D,SAAgB,0BAA0B,UAA+C;CACvF,yBAAyB;AAC3B;;AAGA,eAAe,aAAa,QAAqD;CAC/E,MAAM,UAAU,OAAO,eAAe,mBAAmB,OAAO,UAAU,KAAA;CAC1E,IAAI,CAAC,SAAS,aAAa,CAAC,wBAAwB,OAAO,KAAA;CAC3D,OAAO,uBAAuB,OAAO;AACvC;;;;;;;;;AAUA,eAAe,iBACb,KACA,QAC0C;CAC1C,MAAM,QAAQ,MAAM,aAAa,MAAM;CACvC,IAAI,CAAC,OAAO,OAAO;EAAE;EAAK,OAAO;CAAM;CACvC,MAAM,YAAY,IAAI,IAAI,GAAG;CAC7B,MAAM,OAAO,GAAG,OAAO,SAAS,IAAI;CACpC,IAAI,CAAC,GAAG,UAAU,SAAS,UAAU,WAAW,WAAW,IAAI,GAC7D,MAAM,IAAI,qBACR,4FACF;CAEF,UAAU,aAAa,IAAI,SAAS,KAAK;CACzC,OAAO;EAAE,KAAK,UAAU,SAAS;EAAG,OAAO;CAAK;AAClD;;;;;;;;AASA,SAAgB,wBAAwB,QAAiC;CACvE,MAAM,WAAW,qBAAqB,OAAO,UAAU;CACvD,IAAI,CAAC,SAAS,SAAS,OAAO;CAC9B,OAAO,SAAS,OAAO,oBAAoB,OAAO,SAAS,YAAY;AACzE;;;;;;;;;AAUA,SAAgB,wBAAwB,QAAiC;CACvE,MAAM,UAAU,OAAO,eAAe,mBAAmB,OAAO,UAAU,KAAA;CAC1E,IAAI,CAAC,SAAS,WAAW,CAAC,QAAQ,iBAAiB,OAAO;CAC1D,OAAO,wBAAwB,OAAO,MAAM,QAAQ;AACtD;;;;;;;;AASA,SAAgB,uBAAuB,MAAoC;CACzE,MAAM,QAAQ,mBAAmB,IAAI;CACrC,IAAI,OAAO,MAAM;CACjB,MAAM,cACJ,QAAQ,OAAO,SAAS,WAAY,KAAmC,cAAc,KAAA;CACvF,IAAI,CAAC,MAAM,QAAQ,WAAW,GAAG,OAAO,CAAC;CACzC,OAAO,YAAY,SAAS,UAAU;EACpC,MAAM,OAAO,WAAW,OAAO,MAAM,CAAC,EAAE,KAAK;EAC7C,MAAM,WAAW,WAAW,OAAO,UAAU;EAC7C,MAAM,aAAc,OAA6C,iBAAiB;EAClF,OAAO,QAAQ,YAAY,CAAC,aAAa,CAAC;GAAE;GAAM;EAAS,CAAC,IAAI,CAAC;CACnE,CAAC;AACH;;;;;;;;;;;AAYA,eAAsB,eACpB,MACA,UAA6E,CAAC,GAChD;CAC9B,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,IAAI,CAAC,wBAAwB,MAAM,GACjC,MAAM,IAAI,yBAAyB,mDAAmD;CAExF,MAAM,MAAM,IAAI,IAAI,GAAG,eAAe,MAAM,CAAC,CAAC,IAAI,SAAS;CAC3D,IAAI,aAAa,IAAI,QAAQ,IAAI;CACjC,IAAI,aAAa,IAAI,KAAK,MAAM;CAChC,IAAI,QAAQ,OAAO,IAAI,aAAa,IAAI,kBAAkB,OAAO,QAAQ,KAAK,CAAC;CAC/E,MAAM,UAAU,MAAM,iBAAiB,IAAI,SAAS,GAAG,MAAM;CAC7D,MAAM,WAAW,MAAM,aAAa,CAAC,CAAC,QAAQ,KAAK;EACjD,QAAQ,QAAQ;EAChB,SAAS,EAAE,QAAQ,mBAAmB;EACtC,GAAI,QAAQ,QAAQ,EAAE,UAAU,QAAiB,IAAI,CAAC;CACxD,CAAC;CACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,qBAAqB,0BAA0B,SAAS,UAAU,SAAS,MAAM;CAE7F,OAAO,uBAAuB,MAAM,gBAAgB,QAAQ,CAAC;AAC/D;;;;;;;;;;AAWA,eAAsB,uBACpB,KACA,UAAoD,CAAC,GACjB;CACpC,MAAM,OAAO,0BAA0B,GAAG;CAC1C,IAAI,CAAC,MAAM,MAAM,IAAI,qBAAqB,kCAAkC;CAC5E,MAAM,UAAU,IAAI,IAAI,IAAI;CAC5B,QAAQ,aAAa,IAAI,KAAK,MAAM;CACpC,IAAI,QAAQ,OAAO,QAAQ,aAAa,IAAI,SAAS,QAAQ,KAAK;CAClE,MAAM,WAAW,MAAM,aAAa,CAAC,CAAC,QAAQ,SAAS,GAAG;EACxD,QAAQ,QAAQ;EAChB,SAAS,EAAE,QAAQ,mBAAmB;EAEtC,GAAI,QAAQ,QAAQ,EAAE,UAAU,QAAiB,IAAI,CAAC;CACxD,CAAC;CACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,qBAAqB,0BAA0B,SAAS,UAAU,SAAS,MAAM;CAE7F,OAAO,uBAAuB,MAAM,gBAAgB,QAAQ,CAAC;AAC/D;;AAGA,SAAS,eAAwC;CAC/C,OAAO,kBAAkB;AAC3B;;;;;;;;;;;AAYA,eAAe,gBAAgB,UAAsC;CACnE,IAAI,SAAS,WAAW,KAAK,OAAO,KAAA;CACpC,MAAM,OAAO,MAAM,SAAS,KAAK;CACjC,IAAI,CAAC,KAAK,KAAK,GAAG,OAAO,KAAA;CACzB,OAAO,KAAK,MAAM,IAAI;AACxB;;;;;;;;;;;;;AAcA,SAAS,cAAc,KAA4B;CACjD,IAAI,CAAC,OAAO,SAAS,GAAG,GAAG,OAAO;CAClC,IAAI,OAAO,QAAQ,OAAO,KAAK,OAAO;CACtC,SAAW,MAAM,OAAO,MAAO,OAAO,MAAO;AAC/C;;;;;AAMA,eAAsB,eACpB,OACA,UAQI,CAAC,GACoB;CACzB,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,MAAM,WAAW,qBAAqB,OAAO,UAAU;CACvD,MAAM,UAAU,MAAM,iBACpB,SAAS,gBAAgB,QAAQ,OAAO;EACtC,OAAO,OAAO;EACd,OAAO,QAAQ;EACf,UAAU,QAAQ;EAClB,YAAY,QAAQ;CACtB,CAAC,GACD,MACF;CACA,MAAM,WAAW,MAAM,aAAa,CAAC,CAAC,QAAQ,KAAK;EACjD,QAAQ,QAAQ;EAChB,SAAS,EAAE,QAAQ,mBAAmB;EACtC,GAAI,QAAQ,QAAQ,EAAE,UAAU,QAAiB,IAAI,CAAC;CACxD,CAAC;CACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,qBAAqB,0BAA0B,SAAS,UAAU,SAAS,MAAM;CAE7F,MAAM,OAAgB,MAAM,gBAAgB,QAAQ;CACpD,OAAO,SAAS,aAAa,IAAI;AACnC;;;;;AAMA,eAAsB,eACpB,KACA,KACA,UAA4E,CAAC,GACtC;CACvC,MAAM,aAAa,cAAc,GAAG;CACpC,IAAI,eAAe,QAAQ,CAAC,OAAO,SAAS,GAAG,KAAK,MAAM,OAAO,MAAM,IACrE,OAAO;CAET,MAAM,SAAS,QAAQ,UAAU,kBAAkB;CACnD,MAAM,WAAW,qBAAqB,OAAO,UAAU;CACvD,IAAI,CAAC,wBAAwB,MAAM,GACjC,MAAM,IAAI,yBAAyB,2DAA2D;CAEhG,MAAM,UAAU,MAAM,iBACpB,SAAS,gBAAgB,QAAQ,YAAY,KAAK;EAChD,OAAO,OAAO;EACd,MAAM,QAAQ;CAChB,CAAC,GACD,MACF;CACA,MAAM,WAAW,MAAM,aAAa,CAAC,CAAC,QAAQ,KAAK;EACjD,QAAQ,QAAQ;EAChB,SAAS,EAAE,QAAQ,mBAAmB;EACtC,GAAI,QAAQ,QAAQ,EAAE,UAAU,QAAiB,IAAI,CAAC;CACxD,CAAC;CACD,IAAI,CAAC,SAAS,IACZ,MAAM,IAAI,qBAAqB,0BAA0B,SAAS,UAAU,SAAS,MAAM;CAE7F,MAAM,OAAgB,MAAM,gBAAgB,QAAQ;CACpD,OAAO,SAAS,aAAa,IAAI;AACnC;;;ACroDA,MAAa,uBAAuB;AASpC,SAAgB,8BAA8B,OAA+B;CAC3E,OAAO,OAAO,UAAU,YAAY,kBAAkB,KAAK,KAAK,IAAI,QAAQ;AAC9E;AAEA,SAAgB,uBAAqC;CACnD,OAAO;EACL,QAAQ,CAAC,MAAM,EAAE;EACjB,MAAM;EACN,SAAS;EACT,OAAO;CACT;AACF;AAEA,SAAgB,mBACd,OAAO,sBACP,UAAgC,CAAC,GAChB;CACjB,OAAO;EACL,SAAS;EACT;EACA,SAAS,QAAQ,WAAW,qBAAqB;EACjD,iBAAiB,QAAQ,mBAAA;EACzB,gBAAgB;EAChB,gBAAgB;EAChB,sBAAsB;EACtB,QAAQ,CAAC;EACT,aAAa,CAAC;EACd,QAAQ,CAAC;EAKT,aAAa;GACX,GAAG;GACH,KAAK;IACH,GAAG,4BAA4B;IAC/B,GAAI,QAAQ,cAAc,EAAE,aAAa,aAAa,QAAQ,WAAW,CAAC,CAAC,GAAG,IAAI,CAAC;GACrF;EACF;EACA,QAAQ,EAAE,GAAG,sBAAsB;EACnC,UAAU,CAAC;EACX,UAAU,CAAC;CACb;AACF;;;;;;AAOA,MAAM,gCAAgB,IAAI,IAAI;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;AAGD,MAAM,iBAAiB;;;;;;;;;;;AAYvB,SAAS,eAAe,OAAwB;CAC9C,MAAM,OAAQ,MAA6B;CAC3C,OAAO,OAAO,SAAS,YAAY,cAAc,IAAI,IAAI;AAC3D;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAS,sBACP,OACA,OACA,KACA,WACA,SACoB;CACpB,IAAI,YAAY,KAAA,KAAa,UAAU,QAAQ,OAAO,UAAU,UAAU;EACxE,MAAM,SAAS,QAAQ,IAAI,KAAK;EAChC,IAAI,WAAW,KAAA,GAAW,OAAO;CACnC;CACA,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU;EAI/C,MAAM,SAAU,MAA+B;EAC/C,IAAI,OAAO,WAAW,YACpB,QAAS,OAAoC,KAAK,OAAO,GAAG;CAEhE;CACA,IAAI,UAAU,QAAQ,OAAO,UAAU,UAAU,OAAO,KAAK,UAAU,KAAK;CAG5E,IAAI,iBAAiB,UAAU,iBAAiB,UAAU,iBAAiB,SACzE,OAAO,KAAK,UAAU,KAAK;CAG7B,IAAI,eAAe,KAAK,GAAG,OAAO,KAAK,UAAU,KAAK;CAOtD,IAAI,UAAU,IAAI,KAAK,GAAG,MAAM,IAAI,UAAU,uCAAuC;CACrF,UAAU,IAAI,KAAK;CACnB,IAAI;EACF,MAAM,MAAM,eAAe,OAAO,QAAQ,CAAC;EAC3C,MAAM,WAAW,eAAe,OAAO,KAAK;EAC5C,IAAI,MAAM,QAAQ,KAAK,GAAG;GACxB,IAAI,MAAM,WAAW,GAAG,OAAO;GAS/B,OAAO,MAAM,MANG,MAAM,KACpB,EAAE,QAAQ,MAAM,OAAO,IACtB,SAAS,UACR,sBAAsB,MAAM,QAAQ,QAAQ,GAAG,OAAO,KAAK,GAAG,WAAW,OAAO,KAChF,MAEqB,CAAC,CAAC,KAAK,MAAM,KAAK,EAAE,IAAI,SAAS;EAC5D;EACA,MAAM,UAAoB,CAAC;EAC3B,KAAK,MAAM,CAAC,UAAU,UAAU,OAAO,QAAQ,KAAK,GAAG;GACrD,MAAM,aAAa,sBAAsB,OAAO,QAAQ,GAAG,UAAU,WAAW,OAAO;GAEvF,IAAI,eAAe,KAAA,GAAW;GAC9B,QAAQ,KAAK,GAAG,KAAK,UAAU,QAAQ,EAAE,IAAI,YAAY;EAC3D;EACA,IAAI,QAAQ,WAAW,GAAG,OAAO;EACjC,OAAO,MAAM,MAAM,QAAQ,KAAK,MAAM,KAAK,EAAE,IAAI,SAAS;CAC5D,UAAU;EACR,UAAU,OAAO,KAAK;CACxB;AACF;;;;;;;;;AAUA,SAAgB,iBAAiB,SAAkC;CACjE,OACE,sBACE;EAAE,GAAG;EAAS,QAAQ,QAAQ,OAAO,IAAI,aAAa;CAAE,GACxD,GACA,oBACA,IAAI,IAAI,CACV,KAAK;AAET;;AAUA,SAAgB,uCAAuE;CACrF,uBAAO,IAAI,QAAQ;AACrB;;;;;;;;;;;;;;;;;;;;;;;;;AA0BA,SAAgB,+BACd,SACA,cACA,OACQ;CACR,MAAM,QAAQ,oCAAoC,SAAS,cAAc,KAAK;CAC9E,SAAS;EACP,MAAM,OAAO,MAAM,KAAK;EACxB,IAAI,KAAK,MAAM,OAAO,KAAK;CAC7B;AACF;;AAGA,MAAM,qBAAqB;;AAG3B,SAAS,oBAAmC;CAC1C,OAAO,IAAI,SAAS,YAAY,WAAW,SAAS,CAAC,CAAC;AACxD;;;;;;;;;;;;;;;;;;;;;AAsBA,eAAsB,oCACpB,SACA,cACA,OACA,UAA+B,mBACd;CACjB,MAAM,QAAQ,oCAAoC,SAAS,cAAc,KAAK;CAC9E,IAAI,aAAa,KAAK,IAAI;CAC1B,SAAS;EACP,MAAM,OAAO,MAAM,KAAK;EACxB,IAAI,KAAK,MAAM,OAAO,KAAK;EAC3B,IAAI,KAAK,IAAI,IAAI,cAAc,oBAAoB;GACjD,MAAM,QAAQ;GACd,aAAa,KAAK,IAAI;EACxB;CACF;AACF;;AAGA,UAAU,oCACR,SACA,cACA,OAC+B;CAC/B,IAAI,aAAa,WAAW,QAAQ,OAAO,QAAQ,OAAO,iBAAiB,OAAO;CAClF,MAAM,SAAS,QAAQ,OAAO,IAAI,aAAa;CAG/C,IAAI,IAAI,IAAI,MAAM,CAAC,CAAC,SAAS,OAAO,QAAQ,OAAO,iBAAiB,OAAO;CAC3E,MAAM,0BAAU,IAAI,IAAoB;CACxC,KAAK,IAAI,QAAQ,GAAG,QAAQ,OAAO,QAAQ,SAAS,GAAG;EACrD,MAAM,QAAQ,OAAO;EACrB,MAAM,SAAS,aAAa;EAC5B,MAAM,SAAS,MAAM,IAAI,MAAM;EAG/B,IAAI,OAAO,QAAQ,UAAU,QAAQ,OAAO,OAAO,KAAA;EACnD,IAAI,SAAS,KAAA,GAAW;GAEtB,OAAO,sBAAsB,OAAO,GAAG,OAAO,KAAK,mBAAG,IAAI,IAAI,CAAC,KAAK;GACpE,MAAM,IAAI,QAAQ;IAAE;IAAO;GAAK,CAAC;GACjC;EACF;EACA,QAAQ,IAAI,OAAO,IAAI;CACzB;CACA,OAAO,sBAAsB;EAAE,GAAG;EAAS;CAAO,GAAG,GAAG,oBAAI,IAAI,IAAI,GAAG,OAAO,KAAK;AACrF;AAEA,SAAgB,aAAa,MAA+B;CAC1D,MAAM,OAAO,KAAK,MAAM,IAAI;CAC5B,IAAI,CAAC,KAAK,WAAW,CAAC,KAAK,QAAQ,CAAC,KAAK,SACvC,MAAM,IAAI,MAAM,mDAAmD;CAErE,MAAM,cAAc,qBAAqB,KAAK,WAAW;CACzD,MAAM,gBAAgB,IAAI,IAAI,YAAY,KAAK,MAAM,EAAE,EAAE,CAAC;CAC1D,MAAM,UAAU,KAAK,UAAU,CAAC,EAAA,CAC7B,IAAI,cAAc,CAAC,CACnB,KAAK,UACJ,MAAM,WAAW,CAAC,cAAc,IAAI,MAAM,OAAO,IAAI;EAAE,GAAG;EAAO,SAAS,KAAA;CAAU,IAAI,KAC1F;CACF,MAAM,kBACJ,KAAK,oBAAoB,OACrB,OACA,OAAO,KAAK,oBAAoB,YAC9B,OAAO,MAAM,UAAU,MAAM,OAAO,KAAK,eAAe,IACxD,KAAK,kBACL,KAAA;CACR,MAAM,kBAAkB,KAAK,mBAAA;CAC7B,MAAM,iBAAiB,KAAK,kBAAkB;CAC9C,MAAM,iBAAiB,KAAK,kBAAkB;CAC9C,MAAM,uBAAuB,8BAA8B,KAAK,oBAAoB;CAIpF,MAAM,UAAU,sBAAsB,KAAK,OAAO;CAClD,MAAM,EAAE,WAAW,sBAAsB,eACvC,mBAAmB,KAAK,SAAS,GACjC,2BAA2B,KAAK,iBAAiB,GACjD,EAAE,QAAQ,CACZ;CACA,MAAM,eAAe,6BAA6B,KAAK,YAAY;CACnE,MAAM,iBAAiB,yBAAyB,KAAK,QAAQ;CAC7D,MAAM,oBAAoB,4BAA4B,KAAK,WAAW;CACtE,OAAO;EACL,SAAS,KAAK;EACd,MAAM,KAAK;EACX;EACA;EACA;EACA;EACA;EACA;EACA,GAAI,oBAAoB,KAAA,IAAY,EAAE,gBAAgB,IAAI,CAAC;EAC3D,GAAI,YAAY,SAAS,IAAI,EAAE,YAAY,IAAI,CAAC;EAChD,QAAQ,KAAK,UAAU,CAAC;EACxB,aAAa,4BAA4B,KAAK,WAAW;EACzD,SAAS,wBAAwB,KAAK,OAAO,KAAK,KAAA;EAClD,QAAQ,sBAAsB,KAAK,MAAM;EACzC,aAAa,2BAA2B,KAAK,WAAW,KAAK,KAAA;EAC7D,UAAU,kBAAkB,KAAK,QAAQ,KAAK,KAAA;EAC9C,QAAQ,gBAAgB,KAAK,MAAM,KAAK,KAAA;EACxC,mBAAmB,2BAA2B,KAAK,iBAAiB,KAAK,KAAA;EACzE,MAAM,uBAAuB,KAAK,IAAI,KAAK,KAAA;EAC3C,SAAS,iBAAiB,KAAK,OAAO,KAAK,KAAA;EAC3C,GAAG,eAAe,KAAK,WAAW,KAAK,cAAc;EACrD,GAAI,KAAK,qBAAqB,KAAA,IAC1B,CAAC,IACD,EAAE,kBAAkB,0BAA0B,KAAK,gBAAgB,EAAE;EAGzE,GAAI,UAAU,OAAO,UAAU,OAAO,IAClC;GACE;GACA;GACA,GAAI,gBAAgB,KAAK,eAAe,IACpC,EAAE,iBAAiB,gBAAgB,KAAK,eAAe,EAAE,IACzD,CAAC;EACP,IACA,CAAC;EAGL,GAAI,yBAAyB,KAAK,eAAe,IAC7C,EAAE,iBAAiB,yBAAyB,KAAK,eAAe,EAAG,IACnE,CAAC;EACL,GAAI,aAAa,SAAS,IAAI,EAAE,aAAa,IAAI,CAAC;EAClD,GAAI,eAAe,SAAS,IAAI,EAAE,UAAU,eAAe,IAAI,CAAC;EAChE,GAAI,oBAAoB,EAAE,aAAa,kBAAkB,IAAI,CAAC;EAC9D,UAAU,KAAK,YAAY,CAAC;CAC9B;AACF;;;;;;;;;AAUA,SAAS,qBAAqB,OAA8B;CAC1D,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,SAAuB,CAAC;CAC9B,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,OAAO;EACzB,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;EACzC,MAAM,YAAY;EAClB,MAAM,KAAK,OAAO,UAAU,OAAO,WAAW,UAAU,GAAG,KAAK,IAAI;EACpE,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,GAAG;EACzB,KAAK,IAAI,EAAE;EACX,MAAM,UACJ,OAAO,UAAU,YAAY,YAAY,OAAO,SAAS,UAAU,OAAO,IACtE,KAAK,IAAI,KAAK,IAAI,UAAU,SAAS,CAAC,GAAG,CAAC,IAAA;EAEhD,OAAO,KAAK;GACV;GACA,MAAM,OAAO,UAAU,SAAS,WAAW,UAAU,OAAO;GAC5D,GAAI,OAAO,UAAU,aAAa,YAAY,UAAU,SAAS,KAAK,IAClE,EAAE,UAAU,UAAU,SAAS,KAAK,EAAE,IACtC,CAAC;GACL,WAAW,UAAU,cAAc;GACnC,SAAS,UAAU,YAAY;GAC/B;EACF,CAAC;CACH;CACA,MAAM,MAAM,IAAI,IAAI,OAAO,KAAK,UAAU,MAAM,EAAE,CAAC;CACnD,MAAM,OAAO,IAAI,IAAI,OAAO,KAAK,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;CAC7D,OAAO,OAAO,KAAK,UAAU;EAC3B,IAAI,CAAC,MAAM,YAAY,CAAC,IAAI,IAAI,MAAM,QAAQ,KAAK,MAAM,aAAa,MAAM,IAC1E,OAAO,MAAM,WAAW;GAAE,GAAG;GAAO,UAAU,KAAA;EAAU,IAAI;EAE9D,IAAI,KAAyB,MAAM;EACnC,MAAM,uBAAO,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;EAC/B,OAAO,IAAI;GACT,IAAI,KAAK,IAAI,EAAE,GAAG,OAAO;IAAE,GAAG;IAAO,UAAU,KAAA;GAAU;GACzD,KAAK,IAAI,EAAE;GACX,KAAK,KAAK,IAAI,EAAE,CAAC,EAAE;EACrB;EACA,OAAO;CACT,CAAC;AACH;;;;;;AAOA,SAAgB,sBAAsB,QAA2C;CAC/E,IAAI,CAAC,UAAU,OAAO,WAAW,UAAU,OAAO,KAAA;CAClD,MAAM,YAAY;CAElB,MAAM,QAAQ,MAAM,QAAQ,UAAU,KAAK,IAAI,cAAc,UAAU,KAAK,IAAI,CAAC;CAEjF,MAAM,YAAgD,CAAC;CACvD,IAAI,UAAU,aAAa,OAAO,UAAU,cAAc,UACxD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,SAAS,GAAG;EAC9D,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,SAAS,OAAO,UAAU,UAAU;EACxD,MAAM,WAAW;EACjB,MAAM,aAAiC,CAAC;EAGxC,IAAI,OAAO,SAAS,UAAU,YAAY,SAAS,MAAM,KAAK,MAAM,IAClE,WAAW,QAAQ,SAAS;EAK9B,IAAI,SAAS,WAAW,MAAM,WAAW,SAAS;EAClD,IAAI,WAAW,UAAU,KAAA,KAAa,WAAW,WAAW,KAAA,GAC1D,UAAU,IAAI,KAAK,KAAK;CAE5B;CAMF,MAAM,gBAAmD,CAAC;CAC1D,IACE,UAAU,iBACV,OAAO,UAAU,kBAAkB,YACnC,CAAC,MAAM,QAAQ,UAAU,aAAa,GAEtC,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,aAAa,GAAG;EAClE,IAAI,CAAC,IAAI,KAAK,KAAK,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;EAChF,MAAM,QAAQ;EACd,IAAI,CAAC,MAAM,QAAQ,MAAM,KAAK,GAAG;EACjC,MAAM,QAA4B,CAAC;EACnC,KAAK,MAAM,QAAQ,MAAM,OAAO;GAC9B,IAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,GAAG;GAC9D,MAAM,MAAM;GACZ,IAAI,OAAO,IAAI,UAAU,YAAY,OAAO,IAAI,UAAU,UAAU;GACpE,MAAM,QACJ,IAAI,UAAU,YAAY,IAAI,UAAU,UAAU,IAAI,UAAU,WAC5D,IAAI,QACJ,KAAA;GAGN,MAAM,OACJ,OAAO,IAAI,SAAS,YAAY,OAAO,SAAS,IAAI,IAAI,KAAK,IAAI,OAAO,IACpE,KAAK,IAAI,IAAI,MAAM,GAAI,IACvB,KAAA;GACN,MAAM,KAAK;IACT,OAAO,IAAI;IACX,OAAO,IAAI;IACX,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;IACzB,GAAI,SAAS,KAAA,IAAY,EAAE,KAAK,IAAI,CAAC;GACvC,CAAC;EACH;EACA,IAAI,MAAM,WAAW,GAAG;EACxB,cAAc,IAAI,KAAK,KAAK;GAC1B,GAAI,OAAO,MAAM,UAAU,YAAY,MAAM,MAAM,KAAK,MAAM,KAC1D,EAAE,OAAO,MAAM,MAAM,IACrB,CAAC;GACL;EACF;CACF;CAGF,MAAM,gBACJ,UAAU,kBAAkB,cAC5B,UAAU,kBAAkB,eAC5B,UAAU,kBAAkB,iBAC5B,UAAU,kBAAkB,iBACxB,UAAU,gBACV,KAAA;CAIN,MAAM,aAAa,UACjB,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,KAAK,SAAS,OAAO,SAAS,MAC5E,KAAK,MAAM,KAAK,IAChB,KAAA;CACN,MAAM,aAAa,UAAU,UAAU,UAAU;CACjD,MAAM,cAAc,UAAU,UAAU,WAAW;CAEnD,OAAO;EACL,OAAO,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ,sBAAsB;EACrF,cAAc,iBAAiB,UAAU,cAAc,sBAAsB,YAAY;EACzF;EACA;EACA,GAAI,OAAO,KAAK,aAAa,CAAC,CAAC,SAAS,IAAI,EAAE,cAAc,IAAI,CAAC;EACjE,GAAI,UAAU,iBAAiB,OAAO,EAAE,cAAc,KAAK,IAAI,CAAC;EAChE,GAAI,UAAU,mBAAmB,OAAO,EAAE,gBAAgB,KAAK,IAAI,CAAC;EACpE,GAAI,gBAAgB,EAAE,cAAc,IAAI,CAAC;EACzC,GAAI,eAAe,KAAA,IAAY,EAAE,WAAW,IAAI,CAAC;EACjD,GAAI,gBAAgB,KAAA,IAAY,EAAE,YAAY,IAAI,CAAC;CACrD;AACF;;;;;;;;;;AAWA,SAAgB,kBAAkB,UAAoC;CACpE,IAAI,CAAC,YAAY,OAAO,aAAa,UAAU,OAAO;CAEtD,MAAM,YAAY;CAGlB,MAAM,iCAAiB,IAAI,IAAY;CACvC,MAAM,WAAW,MAAM,QAAQ,UAAU,QAAQ,IAC7C,UAAU,SAAS,IAAI,qBAAqB,CAAC,CAAC,QAAQ,YAAqC;EACzF,IAAI,CAAC,WAAW,eAAe,IAAI,QAAQ,EAAE,GAAG,OAAO;EACvD,eAAe,IAAI,QAAQ,EAAE;EAC7B,OAAO;CACT,CAAC,IACD,CAAC;CAEL,MAAM,aAAuB;EAC3B,OAAO,gBAAgB,UAAU,KAAK;EACtC,UAAU,gBAAgB,UAAU,QAAQ;EAC5C,QAAQ,gBAAgB,UAAU,MAAM;EACxC,QAAQ,gBAAgB,UAAU,MAAM;EACxC,OAAO,UAAU,UAAU,UAAU,UAAU;EAC/C,aAAa,iBAAiB,UAAU,aAAa,KAAK;EAC1D,aAAa,gBAAgB,UAAU,WAAW,KAAK,kBAAkB;EACzE,OAAO,iBAAiB,UAAU,OAAO,KAAK;EAC9C,eAAe,sBAAsB,IAAI,UAAU,aAAmC,IACjF,UAAU,gBACX,kBAAkB;EACtB,gBAAgB,iBAAiB,UAAU,gBAAgB,KAAK;EAChE,YAAY,kBAAkB,IAAI,UAAU,UAA4B,IACnE,UAAU,aACX,kBAAkB;EACtB,UAAU,kBAAkB,IAAI,UAAU,QAA0B,IAC/D,UAAU,WACX,kBAAkB;EACtB;CACF;CAKA,OAAO,mBAAmB,UAAU,IAAI,aAAa;AACvD;;AAGA,SAAgB,mBAAmB,OAA0B;CAC3D,IAAI,MAAM,SAAS,SAAS,GAAG,OAAO;CACtC,OACE,MAAM,MAAM,KAAK,MAAM,MACvB,MAAM,SAAS,KAAK,MAAM,MAC1B,MAAM,OAAO,KAAK,MAAM,MACxB,MAAM,OAAO,KAAK,MAAM,MACxB,MAAM,UAAU,kBAAkB,SAClC,MAAM,gBAAgB,kBAAkB,eACxC,MAAM,gBAAgB,kBAAkB,eACxC,MAAM,UAAU,kBAAkB,SAClC,MAAM,kBAAkB,kBAAkB,iBAC1C,MAAM,mBAAmB,kBAAkB,kBAC3C,MAAM,eAAe,kBAAkB,cACvC,MAAM,aAAa,kBAAkB;AAEzC;AAEA,MAAM,mCAAmB,IAAI,IAA2B;CAAC;CAAQ;CAAU;CAAS;AAAM,CAAC;AAE3F,MAAM,mCAAmB,IAAI,IAA2B;CAAC;CAAS;CAAU;AAAQ,CAAC;AAErF,MAAM,wCAAwB,IAAI,IAAwB;CACxD;CACA;CACA;CACA;AACF,CAAC;AAED,MAAM,oCAAoB,IAAI,IAAoB;CAAC;CAAQ;CAAS;CAAS;CAAU;AAAU,CAAC;AAElG,SAAS,sBAAsB,SAAuC;CACpE,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU,OAAO;CAEpD,MAAM,YAAY;CAClB,MAAM,KAAK,gBAAgB,UAAU,EAAE;CACvC,IAAI,CAAC,IAAI,OAAO;CAEhB,MAAM,WAAW,UAAU;CAC3B,MAAM,SAAS,UAAU;CACzB,IACE,CAAC,MAAM,QAAQ,MAAM,KACrB,OAAO,WAAW,KAClB,CAAC,OAAO,OAAO,UAAU,OAAO,SAAS,KAAK,CAAC,GAE/C,OAAO;CAGT,OAAO;EACL;EACA,OAAO,gBAAgB,UAAU,KAAK;EACtC,aAAa,gBAAgB,UAAU,WAAW;EAClD,OAAO,gBAAgB,UAAU,KAAK,KAAK,KAAA;EAC3C,WAAW,iBAAiB,IAAI,UAAU,SAAkC,IACvE,UAAU,YACX;EACJ,QAAQ,iBAAiB,UAAU,QAAQ,KAAK;EAChD,UAAU;GAER,QAAQ,CACN,gBAAgB,OAAO,OAAO,EAAE,GAAG,MAAM,GAAG,GAC5C,gBAAgB,OAAO,OAAO,EAAE,GAAG,KAAK,EAAE,CAC5C;GAGA,MAAM,MAAM,gBAAgB,UAAU,MAAM,CAAC,GAAG,GAAG,EAAE;GACrD,OAAO,MAAM,gBAAgB,UAAU,OAAO,CAAC,GAAG,GAAG,EAAE;GACvD,UAAW,gBAAgB,UAAU,SAAS,CAAC,IAAI,MAAO,OAAO;EACnE;EACA,cAAc,iBAAiB,IAAI,UAAU,YAAqC,IAC7E,UAAU,eACX;EACJ,iBAAiB,iBAAiB,UAAU,iBAAiB,KAAK;EAElE,GAAI,OAAO,UAAU,oBAAoB,YAAY,OAAO,SAAS,UAAU,eAAe,IAC1F,EAAE,iBAAiB,qCAAqC,UAAU,eAAe,EAAE,IACnF,CAAC;EACL,gBAAgB,wBAAwB,UAAU,cAAc;EAChE,eAAe,wBAAwB,UAAU,aAAa;CAChE;AACF;AAEA,SAAS,wBAAwB,OAA2C;CAC1E,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,OAAO,MACJ,KAAK,UAA0C;EAC9C,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;EAChD,MAAM,YAAY;EAClB,MAAM,UAAU,gBAAgB,UAAU,OAAO;EACjD,IAAI,CAAC,SAAS,OAAO;EACrB,MAAM,KAAK,gBAAgB,UAAU,EAAE;EACvC,OAAO;GACL,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;GACnB;GACA,SAAS,MAAM,gBAAgB,UAAU,SAAS,CAAC,GAAG,GAAG,CAAC;GAC1D,GAAI,OAAO,SAAS,UAAU,QAAQ,IAClC,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,UAAU,QAAQ,CAAC,EAAE,IACpD,CAAC;EACP;CACF,CAAC,CAAC,CACD,QAAQ,UAA4C,QAAQ,KAAK,CAAC;AACvE;AAEA,SAAS,gBAAgB,OAAwB;CAC/C,OAAO,OAAO,UAAU,WAAW,QAAQ;AAC7C;;;;;;;;;;;;AAaA,SAAgB,gBAAgB,OAA0C;CACxE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAClC,MAAM,SAA4B,CAAC;CACnC,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,OAAO;EACzB,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;EACzC,MAAM,YAAY;EAClB,MAAM,KAAK,gBAAgB,UAAU,EAAE,CAAC,CAAC,KAAK;EAC9C,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,GAAG;EACzB,MAAM,QAA+B,CAAC;EACtC,MAAM,WAAW,MAAM,QAAQ,UAAU,KAAK,IAAI,UAAU,QAAQ,CAAC;EACrE,MAAM,8BAAc,IAAI,IAAY;EACpC,KAAK,MAAM,WAAW,UAAU;GAC9B,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU;GAC7C,MAAM,OAAO;GACb,MAAM,SAAS,gBAAgB,KAAK,EAAE,CAAC,CAAC,KAAK;GAC7C,MAAM,SAAS,gBAAgB,KAAK,MAAM,CAAC,CAAC,KAAK;GACjD,IAAI,CAAC,UAAU,CAAC,UAAU,YAAY,IAAI,MAAM,GAAG;GACnD,YAAY,IAAI,MAAM;GACtB,MAAM,aAAa,gBAAgB,KAAK,UAAU,CAAC,CAAC,KAAK;GACzD,MAAM,KAAK;IACT,IAAI;IACJ;IACA,YACE,KAAK,cAAc,OAAO,KAAK,eAAe,WACzC,KAAK,aACN,CAAC;IACP,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;GACrC,CAAC;EACH;EACA,KAAK,IAAI,EAAE;EACX,MAAM,QAAQ,oBAAqB,UAAkC,KAAK;EAC1E,OAAO,KAAK;GACV;GACA,MAAM,gBAAgB,UAAU,IAAI;GACpC;GACA,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;EAC3B,CAAC;CACH;CACA,OAAO,OAAO,SAAS,IAAI,SAAS;AACtC;AAEA,MAAM,mCAAmB,IAAI,IAAwB;CACnD;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;AACD,MAAM,uCAAuB,IAAI,IAAuB,CAAC,UAAU,UAAU,CAAC;;AAG9E,MAAM,uBAAuB;;AAG7B,SAAS,oBAAoB,OAAwB;CACnD,OAAO,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;AACvE;;;;;;;;;;;;;;;AAgBA,SAAgB,oBAAoB,OAAgB,QAAQ,GAAgC;CAC1F,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,MAAM;CACZ,MAAM,QAA0B,CAAC;CACjC,MAAM,0BAAU,IAAI,IAAY;CAChC,KAAK,MAAM,SAAS,MAAM,QAAQ,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,GAAG;EAC7D,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;EACzC,MAAM,OAAO;EACb,MAAM,SAAS,gBAAgB,KAAK,EAAE,CAAC,CAAC,KAAK;EAC7C,MAAM,OAAO,KAAK;EAClB,IAAI,CAAC,UAAU,QAAQ,IAAI,MAAM,KAAK,CAAC,iBAAiB,IAAI,IAAI,GAAG;EACnE,QAAQ,IAAI,MAAM;EAClB,MAAM,UAAU,gBAAgB,KAAK,OAAO,CAAC,CAAC,KAAK;EACnD,MAAM,SAAS,gBAAgB,KAAK,MAAM,CAAC,CAAC,KAAK;EACjD,MAAM,OAAO,gBAAgB,KAAK,IAAI,CAAC,CAAC,KAAK;EAC7C,MAAM,WAAW,KAAK;EAGtB,MAAM,OACJ,KAAK,QAAQ,QAAQ,uBAAuB,oBAAoB,KAAK,MAAM,QAAQ,CAAC,IAAI;EAC1F,MAAM,KAAK;GACT,IAAI;GACJ;GACA,GAAG,oBAAoB,KAAK,CAAC;GAC7B,GAAG,oBAAoB,KAAK,CAAC;GAC7B,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;GAC7B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;GAC3B,GAAI,qBAAqB,IAAI,QAAQ,IAAI,EAAE,SAAS,IAAI,CAAC;GACzD,GAAI,KAAK,cAAc,OAAO,KAAK,eAAe,YAAY,CAAC,MAAM,QAAQ,KAAK,UAAU,IACxF,EAAE,YAAY,KAAK,WAAsC,IACzD,CAAC;GACL,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;GACvB,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EACzB,CAAC;CACH;CACA,IAAI,MAAM,WAAW,GAAG,OAAO;CAE/B,MAAM,QAA0B,CAAC;CACjC,MAAM,0BAAU,IAAI,IAAY;CAChC,KAAK,MAAM,SAAS,MAAM,QAAQ,IAAI,KAAK,IAAI,IAAI,QAAQ,CAAC,GAAG;EAC7D,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;EACzC,MAAM,OAAO;EACb,MAAM,SAAS,gBAAgB,KAAK,EAAE,CAAC,CAAC,KAAK;EAC7C,MAAM,OAAO,gBAAgB,KAAK,IAAI,CAAC,CAAC,KAAK;EAC7C,MAAM,KAAK,gBAAgB,KAAK,EAAE,CAAC,CAAC,KAAK;EACzC,MAAM,WAAW,gBAAgB,KAAK,QAAQ,CAAC,CAAC,KAAK;EACrD,MAAM,SAAS,gBAAgB,KAAK,MAAM,CAAC,CAAC,KAAK;EACjD,IAAI,CAAC,UAAU,QAAQ,IAAI,MAAM,GAAG;EACpC,IAAI,CAAC,QAAQ,IAAI,IAAI,KAAK,CAAC,QAAQ,IAAI,EAAE,KAAK,SAAS,IAAI;EAC3D,IAAI,CAAC,YAAY,CAAC,QAAQ;EAC1B,QAAQ,IAAI,MAAM;EAClB,MAAM,KAAK;GAAE,IAAI;GAAQ;GAAM;GAAU;GAAI;EAAO,CAAC;CACvD;CACA,OAAO;EAAE;EAAO;CAAM;AACxB;AAEA,MAAM,uCAAuB,IAAI,IAAuB;CACtD;CACA;CACA;CACA;CACA;CACA;CACA;AACF,CAAC;;;;;AAMD,MAAM,gCAAkE;CACtE,WAAW;CACX,iBAAiB;AACnB;;;;;;;;;;;;;;;;;;;;;AAsBA,SAAgB,uBAAuB,OAAgD;CACrF,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO;CACxE,IAAI,OAAQ,MAAgC,YAAY,UAAU,OAAO;CACzE,IAAI;EACF,MAAM,OAAgB,KAAK,MAAM,KAAK,UAAU,KAAK,CAAC;EACtD,OAAO,QAAQ,OAAO,SAAS,WAAY,OAAmC;CAChF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAgB,2BAA2B,OAAwC;CACjF,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAIlC,MAAM,SACJ,MAAM,SAAA,MAAuC,MAAM,MAAM,IAA4B,IAAI;CAC3F,MAAM,OAAwB,CAAC;CAC/B,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;EACzC,MAAM,YAAY;EAClB,MAAM,KAAK,gBAAgB,UAAU,EAAE,CAAC,CAAC,KAAK;EAC9C,IAAI,SAAS,gBAAgB,UAAU,MAAM,CAAC,CAAC,KAAK;EACpD,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,MAAM,CAAC,UAAU,KAAK,IAAI,EAAE,GAAG;EACpC,IAAI,CAAC,QAAQ,CAAC,qBAAqB,IAAI,IAAI,GAAG;EAC9C,KAAK,IAAI,EAAE;EACX,MAAM,kBACJ,UAAU,mBAAmB,OAAO,UAAU,oBAAoB,WAC9D,OAAO,YACL,OAAO,QAAQ,UAAU,eAAe,CAAC,CAAC,QACvC,GAAG,UAAU,OAAO,SAAS,QAChC,CACF,IACA,KAAA;EACN,MAAM,mBAAmB,MAAM,QAAQ,UAAU,gBAAgB,IAC7D,UAAU,iBAAiB,QAAQ,SAAyB,OAAO,SAAS,QAAQ,IACpF,KAAA;EACJ,IAAI,aACF,UAAU,cAAc,OAAO,UAAU,eAAe,WACpD,EAAE,GAAI,UAAU,WAAuC,IACvD,CAAC;EACP,MAAM,WAAW,8BAA8B;EAC/C,IAAI,UAAU;GACZ,SAAS;GACT,IAAI,WAAW,YAAY,MAAM,aAAa;IAAE,GAAG;IAAY,UAAU;GAAK;EAChF;EACA,KAAK,KAAK;GACR;GACA;GACA;GACA,UAAU,gBAAgB,UAAU,QAAQ,KAAK;GACjD,QAAQ,gBAAgB,UAAU,MAAM;GACxC;GACA,GAAI,mBAAmB,OAAO,KAAK,eAAe,CAAC,CAAC,SAAS,IAAI,EAAE,gBAAgB,IAAI,CAAC;GACxF,GAAI,kBAAkB,SAAS,EAAE,iBAAiB,IAAI,CAAC;GACvD,GAAI,gBAAgB,UAAU,SAAS,IACnC,EAAE,WAAW,gBAAgB,UAAU,SAAS,EAAE,IAClD,CAAC;GACL,GAAI,gBAAgB,UAAU,UAAU,IACpC,EAAE,YAAY,gBAAgB,UAAU,UAAU,EAAE,IACpD,CAAC;GACL,WAAW,gBAAgB,UAAU,SAAS;GAC9C,GAAI,OAAO,SAAS,UAAU,UAAU,IACpC,EAAE,YAAY,KAAK,IAAI,GAAG,OAAO,UAAU,UAAU,CAAC,EAAE,IACxD,CAAC;GAIL,QAAQ,UAAU,WAAW,YAAY,YAAY;GACrD,GAAI,gBAAgB,UAAU,KAAK,IAAI,EAAE,OAAO,gBAAgB,UAAU,KAAK,EAAE,IAAI,CAAC;EACxF,CAAC;CACH;CACA,IAAI,KAAK,WAAW,GAAG,OAAO;CAC9B,OAAO,KAAK,MAAM,IAAuB;AAC3C;;;;;AAMA,SAAgB,sBAAsB,OAA8B;CAClE,MAAM,WAAW,qBAAqB;CACtC,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,YAAY;CAClB,MAAM,SAAS,MAAM,QAAQ,UAAU,MAAM,IAAI,UAAU,SAAS,SAAS;CAK7E,MAAM,OAAqB;EACzB,QAAQ,CACN,gBAAgB,gBAAgB,OAAO,IAAI,SAAS,OAAO,EAAE,GAAG,MAAM,GAAG,GACzE,gBAAgB,gBAAgB,OAAO,IAAI,SAAS,OAAO,EAAE,GAAG,KAAK,EAAE,CACzE;EACA,MAAM,MAAM,gBAAgB,UAAU,MAAM,SAAS,IAAI,GAAG,GAAG,EAAE;EACjE,UAAW,gBAAgB,UAAU,SAAS,SAAS,OAAO,IAAI,MAAO,OAAO;EAChF,OAAO,MAAM,gBAAgB,UAAU,OAAO,SAAS,KAAK,GAAG,GAAG,EAAE;CACtE;CACA,IACE,MAAM,QAAQ,UAAU,IAAI,KAC5B,UAAU,KAAK,WAAW,KAC1B,UAAU,KAAK,OAAO,MAAM,OAAO,SAAS,CAAC,CAAC,GAE9C,KAAK,OAAO;EACV,OAAO,UAAU,KAAK,EAAE;EACxB,OAAO,UAAU,KAAK,EAAE;EACxB,OAAO,UAAU,KAAK,EAAE;EACxB,OAAO,UAAU,KAAK,EAAE;CAC1B;CAEF,OAAO;AACT;;;;;;AAOA,SAAgB,mBAAmB,OAAsC;CACvE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO;CAChD,MAAM,YAAY;CAClB,MAAM,OAAO,MAAM,KAAK,MAAM,gBAAgB,UAAU,MAAM,CAAC,CAAC,GAAG,GAAA,CAAmB;CACtF,MAAM,OAAO,MAAM,KAAK,MAAM,gBAAgB,UAAU,MAAM,CAAC,CAAC,GAAG,GAAA,CAAmB;CACtF,IAAI,OAAO,QAAQ,GAAG,OAAO;CAC7B,OAAO;EACL;EACA;EACA,UAAU,iBAAiB,UAAU,UAAU,wBAAwB,QAAQ;CACjF;AACF;;;;;;;;;;;AAYA,SAAgB,yBAAyB,OAAuD;CAC9F,OAAO,UAAU,YAAY,UAAU,YAAY,UAAU,WAAW,QAAQ;AAClF;;;;;;AAOA,SAAgB,2BAA2B,OAA2C;CACpF,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAClC,MAAM,QAA4B,CAAC;CACnC,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,OAAO;EACzB,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;EACzC,MAAM,YAAY;EAClB,MAAM,KAAK,gBAAgB,UAAU,EAAE,CAAC,CAAC,KAAK;EAC9C,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,GAAG;EACzB,KAAK,IAAI,EAAE;EACX,MAAM,QAAQ,gBAAgB,UAAU,KAAK;EAI7C,MAAM,WACJ,UAAU,aAAa,YACvB,UAAU,aAAa,cACvB,UAAU,aAAa,YACvB,UAAU,aAAa,WACnB,UAAU,WACV,KAAA;EAEN,MAAM,KAAK;GACT;GACA,MAAM,sBAAsB,UAAU,IAAI;GAC1C,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC;GACzB,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;GAC/B,iBAAiB,yBAAyB,UAAU,eAAe;EACrE,CAAC;CACH;CACA,OAAO,MAAM,SAAS,IAAI,QAAQ;AACpC;;AAGA,SAAS,yBAAyB,OAAyC;CACzE,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU,OAAO,CAAC;CACjD,MAAM,SAAkC,CAAC;CACzC,KAAK,MAAM,CAAC,KAAK,QAAQ,OAAO,QAAQ,KAAgC,GACtE,IAAI,OAAO,QAAQ,WAAW,OAAO,OAAO;CAE9C,OAAO;AACT;;;;;;;AAQA,SAAgB,eACd,QACA,gBACA,SACqE;CACrE,IAAI,CAAC,QACH,OAAO;EAAE,WAAW,EAAE,GAAG,wBAAwB;EAAG,mBAAmB,CAAC;CAAE;CAE5E,MAAM,UAAU,OAAO,OAAO,OAAO,OAAO;CAC5C,IAAI,QAAQ,kBAAkB,CAAC;CAC/B,IAAI,MAAM,SAAS,SACjB,QAAQ,MAAM,MAAM,GAAG,OAAO;MACzB,IAAI,MAAM,SAAS,SAAS;EACjC,MAAM,OAAO,IAAI,IAAI,MAAM,KAAK,MAAM,EAAE,EAAE,CAAC;EAC3C,MAAM,YAAgC,CAAC;EACvC,KAAK,IAAI,IAAI,MAAM,QAAQ,IAAI,SAAS,KAAK;GAC3C,IAAI,KAAK,aAAa;GAGtB,IAAI,SAAS;GACb,OAAO,KAAK,IAAI,EAAE,GAAG,KAAK,aAAa,EAAE,GAAG,EAAE;GAC9C,KAAK,IAAI,EAAE;GACX,UAAU,KAAK;IACb;IACA,MAAM,EAAE,GAAG,QAAQ,QAAQ;IAC3B,iBAAiB,CAAC;GACpB,CAAC;EACH;EACA,QAAQ,CAAC,GAAG,OAAO,GAAG,SAAS;CACjC;CACA,OAAO;EAAE,WAAW;EAAQ,mBAAmB;CAAM;AACvD;;AAGA,MAAM,YAAY;;;AAIlB,MAAM,qBAAqB;;;AAI3B,MAAM,0BAA0B;AAMhC,MAAM,yBAAyB,OAAO,KAAK;CACzC,WAAW;CACX,SAAS;CACT,KAAK;CACL,MAAM;CACN,KAAK;CACL,KAAK;CACL,WAAW;CACX,UAAU;CACV,MAAM;AACR,CAA6C;AAC7C,MAAM,gCAAuE;CAC3E;CACA;CACA;AACF;AACA,MAAM,yBAA0D;CAC9D;CACA;CACA;CACA;CACA;CACA;AACF;;AAGA,MAAa,wBAAwB;;;;;;;;;AAUrC,SAAgB,wBAAwB,OAAwC;CAC9E,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,SAAiC,CAAC;CACxC,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,OAAO,OAAO;EACvB,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;EACrC,MAAM,QAAQ;EACd,MAAM,KAAK,OAAO,MAAM,OAAO,WAAW,MAAM,GAAG,KAAK,IAAI;EAC5D,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,GAAG;EACzB,KAAK,IAAI,EAAE;EACX,OAAO,KAAK;GACV;GACA,MAAM,OAAO,MAAM,SAAS,YAAY,MAAM,KAAK,KAAK,IAAI,MAAM,OAAO;GACzE,WAAW,MAAM,cAAc;EACjC,CAAC;CACH;CACA,OAAO;AACT;;;;;;;;;;;AAYA,SAAgB,mBACd,OACA,SAA0C,CAAC,GACxB;CACnB,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO,CAAC;CACnC,MAAM,WAAW,IAAI,IAAI,OAAO,KAAK,UAAU,MAAM,EAAE,CAAC;CACxD,MAAM,YAA+B,CAAC;CACtC,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,UAAU,UACd,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,IAAI,QAAQ;CAChE,KAAK,MAAM,OAAO,OAAO;EACvB,IAAI,UAAU,UAAA,KAAiC;EAC/C,IAAI,CAAC,OAAO,OAAO,QAAQ,UAAU;EACrC,MAAM,QAAQ;EACd,MAAM,KAAK,OAAO,MAAM,OAAO,WAAW,MAAM,GAAG,KAAK,IAAI;EAC5D,MAAM,MAAM,OAAO,MAAM,GAAG;EAC5B,MAAM,MAAM,OAAO,MAAM,GAAG;EAC5B,MAAM,OAAO,OAAO,MAAM,IAAI;EAC9B,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,KAAK,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,MAAM;EAC1E,IAAI,MAAM,QAAQ,MAAM,OAAO,MAAM,OAAO,MAAM,MAAM,OAAO,KAAK,OAAO,IAAI;EAC/E,KAAK,IAAI,EAAE;EACX,MAAM,QAAQ,KAAK,IAAI,IAAI,KAAK,IAAI,GAAG,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC;EAChE,MAAM,YAAa,OAAO,MAAM,OAAO,KAAK,KAAK,MAAO,OAAO;EAC/D,MAAM,WAA4B;GAChC;GACA,MAAM,OAAO,MAAM,SAAS,WAAW,MAAM,OAAO;GACpD;GACA;GACA;GACA;GACA;GACA,WAAW,OAAO,MAAM,SAAS,KAAK;EACxC;EAEA,MAAM,UAAU,OAAO,MAAM,YAAY,WAAW,MAAM,QAAQ,KAAK,IAAI;EAC3E,IAAI,WAAW,SAAS,IAAI,OAAO,GAAG,SAAS,UAAU;EACzD,IAAI,MAAM,SAAS,OAAO,MAAM,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM,KAAK,GAC9E,SAAS,QAAQ,MAAM;EAEzB,UAAU,KAAK,QAAQ;CACzB;CACA,OAAO;AACT;;;;;AAMA,SAAS,eACP,cACA,WAC4E;CAC5E,MAAM,iBAAiB,wBAAwB,SAAS;CACxD,MAAM,YAAY,mBAAmB,cAAc,cAAc;CACjE,OAAO;EACL,GAAI,UAAU,SAAS,IAAI,EAAE,UAAU,IAAI,CAAC;EAC5C,GAAI,eAAe,SAAS,IAAI,EAAE,eAAe,IAAI,CAAC;CACxD;AACF;;;;;;;;;;;;AAaA,SAAgB,iBAAiB,OAA0C;CACzE,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;CAClC,MAAM,UAA6B,CAAC;CACpC,MAAM,uBAAO,IAAI,IAAY;CAC7B,KAAK,MAAM,SAAS,OAAO;EACzB,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;EACzC,MAAM,YAAY;EAClB,MAAM,KAAK,gBAAgB,UAAU,EAAE,CAAC,CAAC,KAAK;EAC9C,MAAM,UAAU,gBAAgB,UAAU,OAAO,CAAC,CAAC,KAAK;EACxD,IAAI,CAAC,MAAM,CAAC,WAAW,KAAK,IAAI,EAAE,GAAG;EACrC,MAAM,OAAO,UAAU;EACvB,IAAI,CAAC,QAAQ,CAAC,uBAAuB,SAAS,IAAI,GAAG;EACrD,KAAK,IAAI,EAAE;EACX,MAAM,SAA0B;GAAE;GAAI;GAAS;EAAK;EACpD,MAAM,QAAQ,gBAAgB,UAAU,KAAK,CAAC,CAAC,KAAK;EACpD,IAAI,OAAO,OAAO,QAAQ;EAC1B,MAAM,QAAQ,gBAAgB,UAAU,KAAK,CAAC,CAAC,KAAK;EACpD,IAAI,UAAU,KAAK,KAAK,GAAG,OAAO,QAAQ;EAC1C,MAAM,QAAQ,gBAAgB,UAAU,KAAK,CAAC,CAAC,KAAK;EACpD,IAAI,OAAO,OAAO,QAAQ;EAC1B,MAAM,SAAS,gBAAgB,UAAU,MAAM,CAAC,CAAC,KAAK;EACtD,IAAI,QAAQ,OAAO,SAAS;EAC5B,MAAM,SAAS,gBAAgB,UAAU,MAAM,CAAC,CAAC,KAAK;EACtD,IAAI,QAAQ,OAAO,SAAS;EAC5B,IAAI,OAAO,UAAU,SAAS,YAAY,OAAO,SAAS,UAAU,IAAI,GAAG;GAGzE,MAAM,OAAO,KAAK,MAAM,UAAU,IAAI;GACtC,IAAI,QAAQ,GAAG,OAAO,OAAO,KAAK,IAAI,oBAAoB,IAAI;EAChE;EACA,MAAM,WAAW,gBAAgB,UAAU,QAAQ,CAAC,CAAC,KAAK;EAC1D,IAAI,UAAU,OAAO,WAAW;EAChC,IACE,UAAU,eACV,8BAA8B,SAAS,UAAU,WAAW,KAG5D,EAAE,SAAS,SAAS,UAAU,gBAAgB,SAE9C,OAAO,cAAc,UAAU;EAEjC,MAAM,aAAa,gBAAgB,UAAU,UAAU,CAAC,CAAC,KAAK;EAC9D,IAAI,YAAY,OAAO,aAAa;EAGpC,IAAI,SAAS,aAAa;GACxB,IACE,UAAU,wBACV,uBAAuB,SAAS,UAAU,oBAAoB,GAE9D,OAAO,uBAAuB,UAAU;GAI1C,MAAM,SAAS,gBAAgB,UAAU,MAAM;GAC/C,IAAI,QAAQ,OAAO,SAAS;GAC5B,MAAM,SAAS,gBAAgB,UAAU,MAAM;GAC/C,IAAI,QAAQ,OAAO,SAAS;EAC9B;EAGA,IAAI,SAAS,cAAc,UAAU,aAAa,MAChD,OAAO,WAAW;EAMpB,IAAI,SAAS,QAAQ;GACnB,IAAI,MAAM,QAAQ,UAAU,UAAU,GAAG;IACvC,MAAM,aAAa,UAAU,WAC1B,KAAK,UAAU,gBAAgB,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,CAC7C,QAAQ,UAAU,UAAU,EAAE;IACjC,IAAI,WAAW,SAAS,GAAG,OAAO,aAAa;GACjD;GACA,MAAM,SAAS,gBAAgB,UAAU,MAAM,CAAC,CAAC,KAAK;GACtD,IAAI,QAAQ,OAAO,SAAS;GAC5B,IAAI,UAAU,YAAY,SAAS,UAAU,YAAY,QACvD,OAAO,UAAU,UAAU;GAE7B,IAAI,OAAO,UAAU,UAAU,YAAY,OAAO,SAAS,UAAU,KAAK,GAAG;IAG3E,MAAM,QAAQ,KAAK,MAAM,UAAU,KAAK;IACxC,IAAI,SAAS,GAAG,OAAO,QAAQ,KAAK,IAAI,yBAAyB,KAAK;GACxE;EACF;EACA,QAAQ,KAAK,MAAM;CACrB;CACA,OAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;;;;;;;;AASA,SAAgB,0BAA0B,OAAwB;CAChE,IAAI,OAAO,UAAU,YAAY,CAAC,OAAO,SAAS,KAAK,GACrD,OAAA;CAEF,OAAO,KAAK,IAAA,GAA2B,KAAK,IAAA,GAA2B,KAAK,MAAM,KAAK,CAAC,CAAC;AAC3F;;;;;;;;;;;;AAaA,SAAgB,4BAA4B,OAA2C;CACrF,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG,OAAO;CACxE,MAAM,MAAM;CACZ,MAAM,SAA6B,CAAC;CACpC,IAAI,IAAI,aAAa,MACnB,OAAO,WAAW;MACb,IAAI,OAAO,IAAI,aAAa,YAAY,IAAI,UACjD,OAAO,WAAW,IAAI;MACjB,IAAI,MAAM,QAAQ,IAAI,QAAQ,GAAG;EACtC,MAAM,MAAM,CACV,GAAG,IAAI,IAAI,IAAI,SAAS,QAAQ,OAAqB,OAAO,OAAO,YAAY,CAAC,CAAC,EAAE,CAAC,CACtF;EACA,IAAI,IAAI,SAAS,GAAG,OAAO,WAAW;CACxC;CACA,IAAI,IAAI,YAAY,OAAO,IAAI,aAAa,YAAY,CAAC,MAAM,QAAQ,IAAI,QAAQ,GAAG;EACpF,MAAM,WAA4D,CAAC;EACnE,KAAK,MAAM,CAAC,MAAM,UAAU,OAAO,QAAQ,IAAI,QAAQ,GAAG;GACxD,IAAI,CAAC,MAAM;GACX,IAAI,OAAO,UAAU,WAAW;IAC9B,SAAS,QAAQ;IACjB;GACF;GAGA,IAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,GAAG;GACjE,MAAM,UAAU;GAChB,IAAI,OAAO,QAAQ,YAAY,WAAW;GAC1C,MAAM,OAA8B,EAAE,SAAS,QAAQ,QAAQ;GAC/D,KAAK,MAAM,CAAC,QAAQ,UAAU,OAAO,QAAQ,OAAO,GAClD,IACE,OAAO,UAAU,YACjB,OAAO,UAAU,aAChB,OAAO,UAAU,YAAY,OAAO,SAAS,KAAK,GAEnD,KAAK,UAAU;GAGnB,SAAS,QAAQ;EACnB;EACA,IAAI,OAAO,KAAK,QAAQ,CAAC,CAAC,SAAS,GAAG,OAAO,WAAW;CAC1D;CACA,OAAO,cAAc,UAAU,OAAO,WAAW,SAAS;AAC5D;AAEA,SAAS,4BAA4B,aAA0C;CAC7E,IAAI,CAAC,eAAe,OAAO,gBAAgB,UACzC,OAAO;CAGT,MAAM,YAAY;CAClB,MAAM,MAAM,UAAU,OAAO,CAAC;CAI9B,OAAO;EACL,KAAK;GACH,GAAG,4BAA4B;GAC/B,QAAQ,gBAAiB,IAA2C,MAAM;GAC1E,SAAS,gBACN,IAA2C,SAC5C,4BAA4B,IAAI,OAClC;GACA,SAAS,gBACN,IAA2C,SAC5C,4BAA4B,IAAI,OAClC;GACA,UAAU,gBACP,IAA2C,UAC5C,4BAA4B,IAAI,QAClC;GACA,gBAAgB,QAAS,IAA2C,cAAc;GAClF,mBAAmB,iBAChB,IAA2C,mBAC5C,IACF;GACA,YACG,IAA2C,eAAe,aACvD,aACA;GAEN,aAAa,aAAc,IAA2C,WAAW,CAAC,CAAC;GACnF,WAAW,mBAAoB,IAA2C,SAAS;GAGnF,sBAAsB,iBACnB,IAA2C,sBAC5C,4BAA4B,IAAI,oBAClC;GAGA,gBACE,gBAAiB,IAA2C,cAAc,KAAK,KAAA;GACjF,eACE,gBAAiB,IAA2C,aAAa,KAAK,KAAA;GAEhF,YAAY,oBAAqB,IAA2C,UAAU;GAEtF,QAAQ,gBAAiB,IAA2C,MAAM;GAM1E,eAAe,uBACZ,IAA2C,aAC9C;GAEA,gBAAgB,iBACb,IAA2C,gBAC5C,4BAA4B,IAAI,cAClC;GACA,mBACE,sBAAuB,IAA2C,iBAAiB,KACnF,KAAA;GAIF,kBACE,OAAQ,IAA2C,qBAAqB,WAClE,IAA2C,mBAC7C,4BAA4B,IAAI;GAGtC,oBAAoB,kBACjB,IAA2C,kBAC9C;GAEA,iBAAiB,iBACd,IAA2C,iBAC5C,4BAA4B,IAAI,eAClC;EACF;EACA,sBAAsB,MAAM,QAAQ,UAAU,oBAAoB,IAC9D,UAAU,qBACP,IAAI,4BAA4B,CAAC,CACjC,QAAQ,aAAqD,QAAQ,QAAQ,CAAC,IACjF,CAAC;EACL,WAAW,8BAA8B,UAAU,SAAS;CAC9D;AACF;;;;;;;;;;AAWA,SAAS,kBAAkB,OAAoC;CAC7D,OAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,QAAQ,IAAI,QAAQ,KAAA;AACrF;AAEA,SAAS,8BAA8B,WAAqD;CAC1F,IAAI,CAAC,aAAa,OAAO,cAAc,UACrC,OAAO;EAAE,GAAG,4BAA4B;EAAW,SAAS,CAAC;CAAE;CAEjE,MAAM,YAAY;CAClB,MAAM,UAAkC,CAAC;CACzC,IAAI,UAAU,WAAW,OAAO,UAAU,YAAY,UACpD,KAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,OAAO,GAAG;EAC5D,MAAM,gBAAgB,IAAI,KAAK;EAC/B,IAAI,iBAAiB,OAAO,UAAU,UACpC,QAAQ,iBAAiB;CAE7B;CAEF,OAAO;EACL,YACE,OAAO,UAAU,eAAe,YAAY,UAAU,WAAW,KAAK,IAClE,UAAU,WAAW,KAAK,IAC1B,4BAA4B,UAAU;EAC5C;EACA,iBACE,OAAO,UAAU,oBAAoB,YAAY,UAAU,gBAAgB,KAAK,IAC5E,UAAU,gBAAgB,KAAK,IAC/B,KAAA;EACN,iBACE,OAAO,UAAU,oBAAoB,YAAY,UAAU,gBAAgB,KAAK,IAC5E,UAAU,gBAAgB,KAAK,IAC/B,KAAA;EACN,OACE,OAAO,UAAU,UAAU,YAAY,UAAU,MAAM,KAAK,IACxD,UAAU,MAAM,KAAK,IACrB,KAAA;EACN,GAAG,2BAA2B,UAAU,UAAU,UAAU,SAAS;CACvE;AACF;;;;;;AAOA,SAAS,2BACP,UACA,WACiE;CACjE,IAAI,CAAC,MAAM,QAAQ,QAAQ,GAAG,OAAO,CAAC;CACtC,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,SAAS,SACZ,IAAI,yBAAyB,CAAC,CAC9B,QAAQ,YAAoD;EAC3D,IAAI,CAAC,WAAW,KAAK,IAAI,QAAQ,EAAE,GAAG,OAAO;EAC7C,KAAK,IAAI,QAAQ,EAAE;EACnB,OAAO;CACT,CAAC;CACH,IAAI,CAAC,OAAO,QAAQ,OAAO,CAAC;CAC5B,OAAO,OAAO,cAAc,YAAY,KAAK,IAAI,SAAS,IACtD;EAAE,UAAU;EAAQ;CAAU,IAC9B,EAAE,UAAU,OAAO;AACzB;;AAGA,SAAS,mBAAmB,OAA8B;CACxD,OAAO,UAAU,cAAc,UAAU,aAAa,QAAQ;AAChE;AAEA,SAAS,gBAAgB,QAAsD;CAC7E,IACE,MAAM,QAAQ,MAAM,KACpB,OAAO,WAAW,KAClB,OAAO,OAAO,UAAU,OAAO,SAAS,KAAK,CAAC,GAC9C;EAIA,MAAM,OAAO,gBAAgB,OAAO,OAAO,EAAE,GAAG,MAAM,GAAG;EACzD,MAAM,QAAQ,gBAAgB,OAAO,OAAO,EAAE,GAAG,KAAK,EAAE;EACxD,MAAM,OAAO,gBAAgB,OAAO,OAAO,EAAE,GAAG,MAAM,GAAG;EACzD,MAAM,QAAQ,gBAAgB,OAAO,OAAO,EAAE,GAAG,KAAK,EAAE;EACxD,IAAI,OAAO,QAAQ,QAAQ,OACzB,OAAO;GAAC;GAAM;GAAO;GAAM;EAAK;CAEpC;CAEA,OAAO,4BAA4B,IAAI;AACzC;AAEA,SAAS,gBAAgB,OAAgB,UAA0B;CACjE,OAAO,OAAO,SAAS,KAAK,IAAI,OAAO,KAAK,IAAI;AAClD;AAEA,SAAS,MAAM,OAAe,KAAa,KAAqB;CAC9D,OAAO,KAAK,IAAI,KAAK,KAAK,IAAI,KAAK,KAAK,CAAC;AAC3C;AAEA,SAAS,gBAAgB,OAAe,KAAa,KAAqB;CACxE,OAAO,MAAM,OAAO,KAAK,GAAG;AAC9B;AAEA,SAAS,iBAAiB,OAAgB,UAA4B;CACpE,OAAO,OAAO,UAAU,YAAY,QAAQ;AAC9C;AAEA,MAAM,oCAAoC;AAE1C,SAAS,6BAA6B,UAAsD;CAC1F,IAAI,CAAC,YAAY,OAAO,aAAa,UAAU,OAAO;CACtD,MAAM,YAAY;CAClB,MAAM,MAAM,OAAO,UAAU,QAAQ,WAAW,UAAU,IAAI,KAAK,IAAI;CACvE,IAAI,CAAC,OAAO,CAAC,kCAAkC,KAAK,GAAG,GAAG,OAAO;CAEjE,OAAO;EACL;EACA,OAAO,OAAO,UAAU,UAAU,WAAW,UAAU,QAAQ;EAC/D,SAAS,iBAAiB,UAAU,SAAS,IAAI;EACjD,GAAI,UAAU,WAAW,QAAQ,EAAE,QAAQ,MAAM,IAAI,CAAC;CACxD;AACF;AAEA,MAAM,mDAAmC,IAAI,IAAkC;CAC7E;CACA;CACA;CACA;AACF,CAAC;AAED,SAAS,wBAAwB,SAA6C;CAC5E,IAAI,CAAC,WAAW,OAAO,YAAY,UAAU,OAAO;CAEpD,MAAM,YAAY;CAClB,MAAM,eAAe,MAAM,QAAQ,UAAU,YAAY,IACrD,cAAc,UAAU,YAAY,CAAC,CAAC,OAAO,0BAA0B,IACvE,CAAC;CACL,MAAM,kBAAkB,MAAM,QAAQ,UAAU,eAAe,IAC3D,cAAc,UAAU,eAAe,IACvC,CAAC;CACL,MAAM,sBAAoE,CAAC;CAC3E,MAAM,WAAoC,CAAC;CAE3C,IAAI,UAAU,uBAAuB,OAAO,UAAU,wBAAwB,UACvE;OAAA,MAAM,CAAC,UAAU,aAAa,OAAO,QAAQ,UAAU,mBAAmB,GAC7E,IACE,OAAO,aAAa,YACpB,SAAS,KAAK,KACd,iCAAiC,IAAI,QAAwC,GAE7E,oBAAoB,SAAS,KAAK,KAAK;CAAA;CAK7C,IAAI,UAAU,YAAY,OAAO,UAAU,aAAa,UACjD;OAAA,MAAM,CAAC,UAAU,UAAU,OAAO,QAAQ,UAAU,QAAQ,GAC/D,IAAI,OAAO,aAAa,YAAY,SAAS,KAAK,KAAK,iBAAiB,KAAK,GAC3E,SAAS,SAAS,KAAK,KAAK;CAAA;CAKlC,OAAO;EACL;EACA;EACA;EACA;CACF;AACF;AAKA,SAAgB,2BAA2B,KAAsB;CAC/D,IAAI;EACF,MAAM,EAAE,UAAU,aAAa,IAAI,IAAI,GAAG;EAC1C,OACE,aAAa,YACZ,aAAa,WAAW;GAAC;GAAa;GAAa;EAAO,CAAC,CAAC,SAAS,QAAQ;CAElF,QAAQ;EACN,OAAO;CACT;AACF;AAEA,SAAS,cAAc,QAA6B;CAClD,MAAM,uBAAO,IAAI,IAAY;CAC7B,MAAM,aAAuB,CAAC;CAE9B,KAAK,MAAM,SAAS,QAAQ;EAC1B,IAAI,OAAO,UAAU,UAAU;EAC/B,MAAM,KAAK,MAAM,KAAK;EACtB,IAAI,CAAC,MAAM,KAAK,IAAI,EAAE,GAAG;EACzB,KAAK,IAAI,EAAE;EACX,WAAW,KAAK,EAAE;CACpB;CAEA,OAAO;AACT;AAEA,SAAS,iBAAiB,OAAyB;CACjD,IAAI,UAAU,MAAM,OAAO;CAE3B,QAAQ,OAAO,OAAf;EACE,KAAK;EACL,KAAK,WACH,OAAO;EACT,KAAK,UACH,OAAO,OAAO,SAAS,KAAK;EAC9B,KAAK;GACH,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,MAAM,gBAAgB;GAC7D,IAAI,CAACC,gBAAc,KAAK,GAAG,OAAO;GAClC,OAAO,OAAO,OAAO,KAAK,CAAC,CAAC,MAAM,gBAAgB;EACpD,SACE,OAAO;CACX;AACF;AAEA,SAASA,gBAAc,OAAwB;CAC7C,MAAM,YAAY,OAAO,eAAe,KAAK;CAC7C,OAAO,cAAc,OAAO,aAAa,cAAc;AACzD;;AAGA,SAAS,wBAAwB,OAAqC;CACpE,IAAI,MAAM,UAAU,kBAAkB,KAAA,GAAW,OAAO;CACxD,MAAM,EAAE,eAAe,gBAAgB,GAAG,aAAa,MAAM;CAC7D,OAAO;EAAE,GAAG;EAAO;CAAS;AAC9B;;;;;;;;;;;;;AAcA,SAAS,mBAAmB,OAAqC;CAC/D,IAAI,MAAM,SAAS,UAAU,MAAM,QAAQ,YAAY,KAAA,GAAW,OAAO;CACzE,MAAM,EAAE,SAAS,UAAU,GAAG,WAAW,MAAM;CAC/C,OAAO;EAAE,GAAG;EAAO;CAAO;AAC5B;AAEA,SAAS,uBAAuB,OAAqC;CACnE,IAAI,MAAM,aAAa,KAAA,GAAW,OAAO;CACzC,MAAM,EAAE,UAAU,WAAW,GAAG,SAAS;CACzC,OAAO;AACT;;;;;;;;AASA,SAAS,cAAc,OAAqC;CAC1D,OAAO,uBAAuB,mBAAmB,wBAAwB,KAAK,CAAC,CAAC;AAClF;AAEA,SAAS,eAAe,OAAqC;CAC3D,QAAQ,yBAAyB,wBAAwB,KAAK,CAAC;CAK/D,MAAM,EACJ,cAAc,iBACd,kBAAkB,qBAClB,qBAAqB,wBACrB,UAAU,cACV,GAAG,SACD;CACJ,MAAM,eAAe,2BAA2B,eAAe;CAG/D,MAAM,sBAAsB,kCAAkC,sBAAsB;CACpF,MAAM,mBACJ,MAAM,QAAQ,mBAAmB,KACjC,oBAAoB,SAAS,KAC7B,sBAAsB,KAAK,UAAU,mBAAmB,GAAG,EAAE,cAAc,UAAU,CAAC,CAAC,CAAC,KACpF,sBACA,KAAA;CAGN,OAAO;EACL,GAAG;EACH,OAAO;GAAE,GAAG;GAAqB,GAAG,MAAM;EAAM;EAChD,SAAS,MAAM,WAAW;EAC1B,SAAS,MAAM,WAAW;EAC1B,UAAU,MAAM,YAAY,CAAC;EAC7B,QAAQ,MAAM,UAAU,CAAC;EACzB,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;EACvC,GAAI,mBAAmB,EAAE,iBAAiB,IAAI,CAAC;EAC/C,GAAI,sBAAsB,EAAE,oBAAoB,IAAI,CAAC;CACvD;AACF;AAEA,SAAgB,yBAAyB,aAAwC;CAC/E,IAAI,CAAC,MAAM,QAAQ,WAAW,GAAG,OAAO,CAAC;CACzC,MAAM,SAA2B,CAAC;CAClC,KAAK,MAAM,QAAQ,aAAa;EAC9B,IAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;EACvC,MAAM,IAAI;EACV,IAAI,OAAO,EAAE,OAAO,YAAY,CAAC,EAAE,IAAI;EAEvC,IAAI,CAAC,EAAE,UAAU,OAAO,EAAE,WAAW,UAAU;EAC/C,MAAM,YAAY,EAAE;EACpB,IAAI;EACJ,IACE,UAAU,SAAS,WACnB,MAAM,QAAQ,UAAU,MAAM,KAC9B,UAAU,OAAO,WAAW,KAC5B,OAAO,UAAU,OAAO,OAAO,YAC/B,OAAO,UAAU,OAAO,OAAO,UAE/B,SAAS;GAAE,MAAM;GAAS,QAAQ,CAAC,UAAU,OAAO,IAAI,UAAU,OAAO,EAAE;EAAE;OACxE,IACL,UAAU,SAAS,aACnB,OAAO,UAAU,YAAY,aAC5B,OAAO,UAAU,cAAc,YAAY,OAAO,UAAU,cAAc,WAC3E;GACA,MAAM,aACJ,MAAM,QAAQ,UAAU,MAAM,KAC9B,UAAU,OAAO,WAAW,KAC5B,OAAO,UAAU,OAAO,OAAO,YAC/B,OAAO,UAAU,OAAO,OAAO,WAC1B,CAAC,UAAU,OAAO,IAAI,UAAU,OAAO,EAAE,IAC1C,KAAA;GACN,SAAS;IACP,MAAM;IACN,SAAS,UAAU;IACnB,WAAW,UAAU;IACrB,GAAI,aAAa,EAAE,QAAQ,WAAW,IAAI,CAAC;GAC7C;EACF,OACE;EAGF,MAAM,YACJ,EAAE,UAAU,OAAO,EAAE,WAAW,WAAY,EAAE,SAAqC,CAAC;EACtF,MAAM,MAAM;EACZ,MAAM,SAAwB;GAC5B,MACE,OAAO,UAAU,SAAS,YAAY,UAAU,KAAK,KAAK,IACtD,UAAU,KAAK,KAAK,IACpB;GACN,OACE,OAAO,UAAU,UAAU,YAAY,IAAI,KAAK,UAAU,MAAM,KAAK,CAAC,IAClE,UAAU,MAAM,KAAK,IACrB;EACR;EAEA,MAAM,UAA0B,CAAC;EACjC,IAAI,MAAM,QAAQ,EAAE,OAAO,GACzB,KAAK,MAAM,SAAS,EAAE,SAAS;GAC7B,IAAI,CAAC,SAAS,OAAO,UAAU,UAAU;GACzC,MAAM,IAAI;GACV,IAAI,OAAO,EAAE,OAAO,YAAY,CAAC,EAAE,IAAI;GACvC,MAAM,aACJ,EAAE,UAAU,OAAO,EAAE,WAAW,WAAY,EAAE,SAAqC,CAAC;GACtF,QAAQ,KAAK;IACX,IAAI,EAAE;IACN,QAAQ;KACN,MACE,OAAO,WAAW,SAAS,YAAY,WAAW,KAAK,KAAK,IACxD,WAAW,KAAK,KAAK,IACrB;KACN,OACE,OAAO,WAAW,UAAU,YAAY,IAAI,KAAK,WAAW,MAAM,KAAK,CAAC,IACpE,WAAW,MAAM,KAAK,IACtB;IACR;IACA,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;IAC5C,WAAW,OAAO,EAAE,cAAc,WAAW,EAAE,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;GACpF,CAAC;EACH;EAGF,OAAO,KAAK;GACV,IAAI,EAAE;GACN;GACA;GACA,MAAM,OAAO,EAAE,SAAS,WAAW,EAAE,OAAO;GAC5C,WAAW,OAAO,EAAE,cAAc,WAAW,EAAE,6BAAY,IAAI,KAAK,EAAA,CAAE,YAAY;GAClF,UAAU,QAAQ,EAAE,QAAQ;GAC5B;EACF,CAAC;CACH;CACA,OAAO;AACT;AAEA,SAAgB,iBAAiB,OAgCb;CAClB,MAAM,SAAS,MAAM,OAAO,QAAQ,UAAU,CAAC,mBAAmB,KAAK,CAAC;CACxE,MAAM,SAAqC,CAAC;CAC5C,KAAK,MAAM,SAAS,QAClB,OAAO,MAAM,MAAM,MAAM;CAE3B,MAAM,UAAU,wBAAwB,MAAM,OAAO;CACrD,MAAM,SAAS,sBAAsB,MAAM,MAAM;CAGjD,MAAM,cAAc,2BAA2B,MAAM,WAAW;CAChE,MAAM,WAAW,kBAAkB,MAAM,QAAQ;CACjD,MAAM,SAAS,gBAAgB,MAAM,MAAM;CAC3C,MAAM,oBAAoB,2BAA2B,MAAM,iBAAiB;CAC5E,MAAM,OAAO,uBAAuB,MAAM,IAAI;CAC9C,MAAM,UAAU,iBAAiB,MAAM,OAAO;CAC9C,MAAM,WAAW,yBAAyB,MAAM,QAAQ;CACxD,MAAM,cAAc,4BAA4B,MAAM,WAAW;CAGjE,MAAM,mBACJ,MAAM,qBAAqB,KAAA,IAAA,IAEvB,0BAA0B,MAAM,gBAAgB;CAItD,MAAM,cAAc,MAAM,eAAe,CAAC;CAI1C,MAAM,EAAE,WAAW,sBAAsB,eACvC,mBAAmB,MAAM,SAAS,GAClC,2BAA2B,MAAM,iBAAiB,GAClD,EAAE,SAAS,MAAM,QAAQ,CAC3B;CACA,MAAM,cAAc,UAAU,OAAO,UAAU,OAAO;CACtD,MAAM,eAAe,6BAA6B,MAAM,YAAY;CACpE,MAAM,kBACJ,MAAM,oBAAoB,OACtB,OACA,OAAO,MAAM,oBAAoB,YAC/B,OAAO,MAAM,UAAU,MAAM,OAAO,MAAM,eAAe,IACzD,MAAM,kBACN,KAAA;CACR,OAAO;EACL,SAAS;EACT,MAAM,MAAM;EACZ,SAAS,MAAM;EACf,iBAAiB,MAAM;EACvB,gBAAgB,MAAM;EACtB,gBAAgB,MAAM;EACtB,GAAI,MAAM,uBAAuB,EAAE,sBAAsB,MAAM,qBAAqB,IAAI,CAAC;EACzF,QAAQ,OAAO,IAAI,mBAAmB;EACtC,GAAI,oBAAoB,KAAA,IAAY,EAAE,gBAAgB,IAAI,CAAC;EAC3D,GAAI,YAAY,SAAS,IAAI,EAAE,YAAY,IAAI,CAAC;EAChD;EACA,aAAa,MAAM;EACnB,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAC7B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,eAAe,CAAC,qBAAqB,WAAW,IAAI,EAAE,YAAY,IAAI,CAAC;EAC3E,GAAI,WAAW,EAAE,SAAS,IAAI,CAAC;EAC/B,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC;EAC3B,GAAI,oBAAoB,EAAE,kBAAkB,IAAI,CAAC;EACjD,GAAI,OAAO,EAAE,KAAK,IAAI,CAAC;EACvB,GAAI,UAAU,EAAE,QAAQ,IAAI,CAAC;EAC7B,GAAG,eAAe,MAAM,WAAW,MAAM,cAAc;EACvD,GAAI,qBAAA,IAAiD,EAAE,iBAAiB,IAAI,CAAC;EAC7E,GAAI,cACA;GACE;GACA;GACA,GAAI,gBAAgB,MAAM,eAAe,IACrC,EAAE,iBAAiB,gBAAgB,MAAM,eAAe,EAAE,IAC1D,CAAC;EACP,IACA,CAAC;EAIL,GAAI,yBAAyB,MAAM,eAAe,IAC9C,EAAE,iBAAiB,yBAAyB,MAAM,eAAe,EAAG,IACpE,CAAC;EACL,GAAI,aAAa,SAAS,IAAI,EAAE,aAAa,IAAI,CAAC;EAClD,GAAI,SAAS,SAAS,IAAI,EAAE,SAAS,IAAI,CAAC;EAC1C,GAAI,cAAc,EAAE,YAAY,IAAI,CAAC;EACrC,UAAU,MAAM;CAClB;AACF;AAQA,SAAS,uBAAuB,OAA+B;CAC7D,MAAM,YAAY,MAAM,OAAO;CAC/B,MAAM,cAAc,MAAM,SAAS;CACnC,OACG,OAAO,cAAc,YAAY,UAAU,KAAK,MAAM,MACtD,OAAO,gBAAgB,YAAY,YAAY,KAAK,MAAM;AAE/D;;;;;;;;;;;AAYA,SAAgB,mBAAmB,OAA+B;CAChE,OAAO,MAAM,SAAS,gBAAgB;AACxC;AAEA,SAAS,oBAAoB,OAAqC;CAChE,MAAM,mBAAmB,MAAM,SAAS,mBAAmB;CAC3D,QAAQ,cAAc,KAAK;CAQ3B,IAAI,yBAAyB,MAAM,SAAS,2BAA2B;CACvE,IAAI,MAAM,SAAS,eAAe,kBAAkB;EAClD,MAAM,YACJ,OAAO,MAAM,OAAO,QAAQ,WACxB,qBAAqB,MAAM,OAAO,GAAG,IACrC,MAAM,OAAO;EACnB,MAAM,aACJ,OAAO,MAAM,eAAe,WACxB,qBAAqB,MAAM,UAAU,IACrC,MAAM;EACZ,MAAM,cACJ,OAAO,MAAM,SAAS,gBAAgB,WAClC,qBAAqB,MAAM,SAAS,WAAW,IAC/C,MAAM,SAAS;EACrB,MAAM,WACJ,cAAc,MAAM,OAAO,OAC3B,eAAe,MAAM,cACrB,gBAAgB,MAAM,SAAS;EACjC,2BAA2B;EAC3B,IAAI,UACF,QAAQ;GACN,GAAG;GACH,QAAQ,cAAc,MAAM,OAAO,MAAM,MAAM,SAAS;IAAE,GAAG,MAAM;IAAQ,KAAK;GAAU;GAC1F;GACA,UAAU;IACR,GAAG,MAAM;IACT,GAAI,gBAAgB,MAAM,SAAS,cAAc,CAAC,IAAI,EAAE,YAAY;IACpE,wBAAwB;GAC1B;EACF;CAEJ;CAIA,IAAI,MAAM,SAAS,mBAAmB,KAAA,GAAW;EAC/C,MAAM,EAAE,gBAAgB,iBAAiB,GAAG,aAAa,MAAM;EAC/D,QAAQ;GAAE,GAAG;GAAO;EAAS;CAC/B;CAMA,IAAI,MAAM,eAAe,KAAA,GAAW;EAClC,MAAM,EAAE,YAAY,aAAa,GAAG,SAAS;EAC7C,QAAQ;CACV;CACA,IAAI,MAAM,gBAAgB,KAAA,GAAW;EACnC,MAAM,EAAE,aAAa,cAAc,GAAG,SAAS;EAC/C,QAAQ;CACV;CAIA,IAAI,MAAM,wBAAwB,KAAA,GAAW;EAC3C,MAAM,EAAE,qBAAqB,wBAAwB,GAAG,SAAS;EACjE,MAAM,sBAAsB,kCAAkC,sBAAsB;EACpF,QAAQ,sBAAsB;GAAE,GAAG;GAAM;EAAoB,IAAI;CACnE;CAOA,IAAI,MAAM,WAAW,MAAM,SAAS,qBAAqB,MAAM;EAC7D,MAAM,EAAE,SAAS,UAAU,GAAG,SAAS;EACvC,QAAQ;CACV;CAKA,IAAI,MAAM,OAAO,aAAa,KAAA,KAAa,MAAM,SAAS,kBAAkB,MAAM;EAChF,MAAM,EAAE,UAAU,WAAW,GAAG,WAAW,MAAM;EACjD,QAAQ;GAAE,GAAG;GAAO;EAAO;CAC7B;CAeA,MAAM,uBAAuB,MAAM,SAAS,eAAe;CAC3D,IACE,MAAM,SAAS,wBAAwB,QACvC,MAAM,YACL,uBAAuB,KAAK,KAAK,uBAClC;EACA,MAAM,EAAE,SAAS,UAAU,GAAG,SAAS;EACvC,QAAQ;CACV;CAEA,MAAM,eAAe,MAAM,OAAO;CAClC,IAAI,mBAAmB;CACvB,IAAI,OAAO,iBAAiB,UAC1B,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,YAAY;EAChC,mBAAmB,IAAI,aAAa,WAAW,IAAI,aAAa;CAClE,QAAQ;EACN,mBAAmB;CACrB;CAQF,IACE,MAAM,SAAS,aACf,MAAM,SAAS,eAAe,oBAC9B,MAAM,WACN,kBACA;EACA,IAAI,kBAAkB;GACpB,MAAM,EAAE,KAAK,MAAM,GAAG,WAAW,MAAM;GACvC,QAAQ;IAAE,GAAG;IAAO;GAAO;EAC7B,OAAO,IAAI,CAAC,wBAAwB;GAClC,MAAM,EAAE,SAAS,UAAU,GAAG,SAAS;GAEvC,QAAQ;EACV;CACF;CAOA,IACE,MAAM,WACN,MAAM,SAAS,eAAe,gCAC9B,uBAAuB,KAAK,GAC5B;EACA,MAAM,EAAE,SAAS,UAAU,GAAG,SAAS;EACvC,QAAQ;CACV;CAOA,IAAI,MAAM,WAAW,MAAM,SAAS,wBAAwB,MAAM;EAChE,MAAM,EAAE,SAAS,UAAU,GAAG,SAAS;EACvC,QAAQ;CACV;CAEA,IAAI,MAAM,SAAS,OAAO;EACxB,MAAM,QAAQ,MAAM,OAAO;EAC3B,IAAI,CAAC,MAAM,QAAQ,KAAK,GAAG,OAAO;EAClC,MAAM,gBAAgB,MAAM,KAAK,SAAS,mBAAmB,IAAI,CAAC;EAClE,OAAO,cAAc,MAAM,MAAM,UAAU,SAAS,MAAM,MAAM,IAC5D;GAAE,GAAG;GAAO,QAAQ;IAAE,GAAG,MAAM;IAAQ,OAAO;GAAc;EAAE,IAC9D;CACN;CAEA,IAAI,MAAM,SAAS,OAAO,OAAO;CAEjC,MAAM,cACJ,OAAO,MAAM,SAAS,gBAAgB,YAAY,MAAM,SAAS,YAAY,KAAK,IAC9E,MAAM,SAAS,cACf,OAAO,MAAM,OAAO,QAAQ,YAAY,MAAM,OAAO,IAAI,KAAK,IAC5D,MAAM,OAAO,MACb;CACR,IAAI,CAAC,aAAa,OAAO;CAEzB,MAAM,WAAW,EAAE,GAAG,MAAM,SAAS;CACrC,OAAO,SAAS;CAUhB,MAAM,QAAQ,OAAO,MAAM,SAAS,gBAAgB,WAAW,CAAC,IAAI,EAAE,OAAO,CAAC,WAAW,EAAE;CAE3F,OAAO;EACL,GAAG;EACH,QAAQ;GACN,GAAG,MAAM;GACT,GAAG;GACH,KAAK;EACP;EACA;CACF;AACF;;;;;;;;;;AAWA,SAAgB,mBAAmB,MAAwB;CAGzD,IAAI,OAAO,SAAS,YAAY,CAAC,KAAK,WAAW,iBAAiB,GAAG,OAAO;CAC5E,IAAI;EACF,MAAM,MAAM,IAAI,IAAI,IAAI,CAAC,CAAC,aAAa,IAAI,KAAK;EAChD,IAAI,CAAC,KAAK,OAAO;EACjB,MAAM,SAAS,IAAI,IAAI,GAAG;EAC1B,OAAO,OAAO,aAAa,WAAW,OAAO,aAAa,WAAW,MAAM;CAC7E,QAAQ;EACN,OAAO;CACT;AACF;;;;;;;;;;;;;;AAeA,SAAgB,oBACd,SACA,OACe;CACf,QAAQ,yBAAyB,KAAK;CACtC,MAAM,WAAW,QAAQ,SAAS,MAAM;CACxC,OAAO;EACL,GAAG;EACH,OAAO,WACH;GAAE,GAAG;GAAqB,GAAG,MAAM;GAAO,GAAG;EAAS,IACtD;GAAE,GAAG;GAAqB,GAAG,MAAM;EAAM;CAC/C;AACF;AAEA,SAAgB,oBAAoB,SA6BlC;CACA,MAAM,SAAS,QAAQ,OAAO,KAAK,UAAU,oBAAoB,SAAS,KAAK,CAAC;CAMhF,MAAM,cAAc,qBAAqB,QAAQ,WAAW;CAC5D,MAAM,gBAAgB,IAAI,IAAI,YAAY,KAAK,MAAM,EAAE,EAAE,CAAC;CAI1D,MAAM,mBAAmB,yBACvB,OAAO,KAAK,UACV,MAAM,WAAW,CAAC,cAAc,IAAI,MAAM,OAAO,IAAI;EAAE,GAAG;EAAO,SAAS,KAAA;CAAU,IAAI,KAC1F,CACF;CACA,MAAM,kBAAkB,QAAQ;CAChC,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,iBAAiB,QAAQ,kBAAkB;CACjD,MAAM,uBAAuB,8BAA8B,QAAQ,oBAAoB;CAGvF,MAAM,UAAU,sBAAsB,QAAQ,OAAO;CACrD,MAAM,EAAE,WAAW,sBAAsB,eACvC,mBAAmB,QAAQ,SAAS,GACpC,2BAA2B,QAAQ,iBAAiB,GACpD,EAAE,QAAQ,CACZ;CAIA,MAAM,mBAAmB,IAAI,IAAI,iBAAiB,KAAK,MAAM,EAAE,EAAE,CAAC;CAClE,MAAM,UAAU,iBAAiB,QAAQ,OAAO,KAAK,CAAC;CACtD,MAAM,WAAW,yBAAyB,QAAQ,QAAQ;CAC1D,MAAM,SAAS,sBAAsB,QAAQ,MAAM,KAAK,EACtD,GAAG,sBACL;CAEA,MAAM,mCAAmB,IAAI,IAAY;CACzC,KAAK,MAAM,KAAK,SAAS,iBAAiB,IAAI,EAAE,OAAO;CACvD,KAAK,MAAM,KAAK,UACd,IAAI,EAAE,OAAO,SAAS,WAAW,iBAAiB,IAAI,EAAE,OAAO,OAAO;CAExE,KAAK,MAAM,MAAM,OAAO,OAAO,iBAAiB,IAAI,EAAE;CACtD,KAAK,MAAM,OAAO,OAAO,KAAK,OAAO,SAAS,GAAG;EAC/C,MAAM,OAAO,IAAI,SAAS,IAAI,IAAI,IAAI,MAAM,GAAG,IAAI,QAAQ,IAAI,CAAC,IAAI;EACpE,iBAAiB,IAAI,IAAI;CAC3B;CACA,IAAI,OAAO,eACJ;OAAA,MAAM,OAAO,OAAO,KAAK,OAAO,aAAa,GAChD,IAAI,CAAC,IAAI,WAAW,SAAS,GAAG,iBAAiB,IAAI,GAAG;CAAA;CAI5D,MAAM,YAAY,IAAI,IAAI,CAAC,GAAG,gBAAgB,CAAC,CAAC,QAAQ,OAAO,CAAC,iBAAiB,IAAI,EAAE,CAAC,CAAC;CAEzF,MAAM,kBACJ,UAAU,OAAO,IAAI,6BAA6B,SAAS,SAAS,IAAI;CAC1E,MAAM,mBACJ,UAAU,OAAO,IAAI,8BAA8B,UAAU,SAAS,IAAI;CAC5E,MAAM,iBACJ,UAAU,OAAO,IAAI,4BAA4B,QAAQ,SAAS,IAAI;CACxE,MAAM,iBAAiB,wBAAwB,QAAQ,cAAc;CAGrE,MAAM,cAAc,0BAClB,2BAA2B,QAAQ,WAAW,KAAK,yBAAyB,GAC5E,gBACF;CAEA,OAAO;EACL,aAAa,QAAQ;EACrB;EACA;EACA;EACA;EACA;EACA,QAAQ;EACR;EACA,aAAa,4BAA4B,QAAQ,WAAW;EAC5D,gBAAgB,wBAAwB,QAAQ,OAAO;EACvD,QAAQ;EACR;EACA,UAAU,kBAAkB,QAAQ,QAAQ;EAC5C,QAAQ,gBAAgB,QAAQ,MAAM,KAAK,CAAC;EAC5C,mBAAmB,2BAA2B,QAAQ,iBAAiB,KAAK,CAAC;EAC7E,eAAe,uBAAuB,QAAQ,IAAI;EAClD,SAAS;EACT;EACA,WAAW,mBAAmB,QAAQ,WAAW,cAAc;EAC/D,kBAAkB,0BAA0B,QAAQ,gBAAgB;EACpE;EACA;EACA,iBAAiB,gBAAgB,QAAQ,eAAe;EAGxD,iBAAiB,yBAAyB,QAAQ,eAAe,KAAA;EACjE,qBAAqB,6BAA6B,QAAQ,YAAY;EACtE,UAAU;EACV,oBAAoB,4BAA4B,QAAQ,WAAW;EACnE,UAAU,QAAQ;CACpB;AACF;;;;;AAMA,SAAgB,kBACd,SACA,UAAmC,CAAC,GACnB;CACjB,MAAM,SAAS,QAAQ,cAAc;CACrC,MAAM,UAAU,QAAQ,KAAK,KAAK,KAAA;CAClC,MAAM,OAAO,SAAU,QAAQ,SAAS,MAAM,IAAI,UAAU,GAAG,QAAQ,GAAG,WAAY;CAEtF,MAAM,WAAW,EAAE,GAAI,QAAQ,YAAY,CAAC,EAAG;CAC/C,KAAK,MAAM,OAAO,OAAO,KAAK,QAAQ,GACpC,IAAI,UAAU,KAAK,GAAG,GACpB,OAAO,SAAS;CAIpB,OAAO;EACL,GAAG;EACH,IAAIC,GAAO;EACX;EACA;CACF;AACF;;;;;;AAOA,SAAgB,sBACd,SACA,UAAwD,CAAC,GACxC;CACjB,MAAM,WAAW,kBAAkB,SAAS,EAAE,YAAY,GAAG,CAAC;CAC9D,MAAM,OAAO,QAAQ,MAAM,KAAK,KAAK,SAAS;CAG9C,MAAM,SAFkB,QAAQ,oBAAoB,QAEnB,CAAC,IAAI,SAAS;CAE/C,OAAO;EACL,GAAG;EACH;EACA;EACA,UAAU;GACR,GAAG,SAAS;GACZ,YAAY;EACd;CACF;AACF;;AC99EA,MAAM,yBAAyB;AAC/B,MAAM,iBAAiB;;AAGvB,MAAM,qCAAqB,IAAI,IAAI;CACjC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;AACF,CAAC;;AAGD,MAAM,uCAAuB,IAAI,IAAI;CACnC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CAEA;AACF,CAAC;AAED,SAAS,cAAc,OAAkD;CACvE,OAAO,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAC5E;;;;;;;;;;AAWA,SAAgB,gBAAgB,GAAY,GAAqB;CAC/D,IAAI,MAAM,GAAG,OAAO;CACpB,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU,OAAO,OAAO,MAAM,CAAC,KAAK,OAAO,MAAM,CAAC;CAC5F,IAAI,MAAM,QAAQ,CAAC,GAAG;EACpB,IAAI,CAAC,MAAM,QAAQ,CAAC,KAAK,EAAE,WAAW,EAAE,QAAQ,OAAO;EACvD,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK,IAAI,CAAC,gBAAgB,EAAE,IAAI,EAAE,EAAE,GAAG,OAAO;EAC5E,OAAO;CACT;CACA,IAAI,cAAc,CAAC,GAAG;EACpB,IAAI,CAAC,cAAc,CAAC,GAAG,OAAO;EAC9B,KAAK,MAAM,OAAO,OAAO,KAAK,CAAC,GAAG;GAChC,IAAI,EAAE,SAAS,KAAA,GAAW;GAC1B,IAAI,CAAC,gBAAgB,EAAE,MAAM,EAAE,IAAI,GAAG,OAAO;EAC/C;EACA,KAAK,MAAM,OAAO,OAAO,KAAK,CAAC,GAC7B,IAAI,EAAE,SAAS,KAAA,KAAa,EAAE,SAAS,KAAA,GAAW,OAAO;EAE3D,OAAO;CACT;CACA,OAAO;AACT;AAEA,SAAS,QAAQ,OAAyB;CACxC,IAAI,UAAU,KAAA,KAAa,UAAU,MAAM,OAAO;CAClD,IAAI,MAAM,QAAQ,KAAK,GAAG,OAAO,MAAM,WAAW;CAClD,OAAO,cAAc,KAAK,KAAK,OAAO,OAAO,KAAK,CAAC,CAAC,OAAO,SAAS,SAAS,KAAA,CAAS;AACxF;;;;;;AAOA,SAAS,cAAc,GAAY,GAAqB;CACtD,OAAO,gBAAgB,GAAG,CAAC,KAAM,QAAQ,CAAC,KAAK,QAAQ,CAAC;AAC1D;;;;;;;;;;AAWA,SAAgB,aACd,OACA,YAAY,wBACQ;CACpB,IAAI,UAAU,KAAA,GAAW,OAAO,KAAA;CAChC,IAAI,OAAO,UAAU,UACnB,OAAO,MAAM,SAAS,YAAY,GAAG,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE,KAAK;CAEvF,IAAI,MAAM;CACV,IAAI,YAAY;CAChB,MAAM,QAAQ,SAA0B;EACtC,IAAI,IAAI,SAAS,KAAK,SAAS,WAAW;GACxC,OAAO,KAAK,MAAM,GAAG,KAAK,IAAI,GAAG,YAAY,IAAI,MAAM,CAAC;GACxD,YAAY;GACZ,OAAO;EACT;EACA,OAAO;EACP,OAAO;CACT;CACA,MAAM,QAAQ,MAAwB;EACpC,IAAI,MAAM,QAAQ,OAAO,MAAM,UAAU;GACvC,IAAI,OAAO,MAAM,YAAY,CAAC,OAAO,SAAS,CAAC,GAAG,OAAO,KAAK,MAAM;GAGpE,IAAI,OAAO,MAAM,YAAY,EAAE,SAAS,WACtC,OAAO,KAAK,KAAK,UAAU,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;GAEnD,MAAM,OAAO,OAAO,MAAM,WAAW,EAAE,SAAS,IAAI,KAAK,UAAU,CAAC;GACpE,OAAO,KAAK,QAAQ,MAAM;EAC5B;EACA,IAAI,MAAM,QAAQ,CAAC,GAAG;GACpB,IAAI,CAAC,KAAK,GAAG,GAAG,OAAO;GACvB,KAAK,IAAI,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;IACjC,IAAI,IAAI,KAAK,CAAC,KAAK,GAAG,GAAG,OAAO;IAChC,IAAI,CAAC,KAAK,EAAE,OAAO,KAAA,IAAY,OAAO,EAAE,EAAE,GAAG,OAAO;GACtD;GACA,OAAO,KAAK,GAAG;EACjB;EACA,IAAI,CAAC,KAAK,GAAG,GAAG,OAAO;EACvB,IAAI,QAAQ;EACZ,KAAK,MAAM,CAAC,KAAK,SAAS,OAAO,QAAQ,CAA4B,GAAG;GACtE,IAAI,SAAS,KAAA,GAAW;GACxB,IAAI,CAAC,SAAS,CAAC,KAAK,GAAG,GAAG,OAAO;GACjC,QAAQ;GACR,IAAI,CAAC,KAAK,GAAG,KAAK,UAAU,GAAG,EAAE,EAAE,GAAG,OAAO;GAC7C,IAAI,CAAC,KAAK,IAAI,GAAG,OAAO;EAC1B;EACA,OAAO,KAAK,GAAG;CACjB;CACA,KAAK,KAAK;CACV,IAAI,WAAW,MAAM,GAAG,IAAI,MAAM,GAAG,KAAK,IAAI,GAAG,YAAY,CAAC,CAAC,EAAE;CACjE,OAAO;AACT;;;;;;AAOA,SAAS,YACP,QACA,OACA,SACA,KACM;CACN,MAAM,IAAI,UAAU,CAAC;CACrB,MAAM,IAAI,SAAS,CAAC;CACpB,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,uBAAO,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,CAAC,GAAG,GAAG,OAAO,KAAK,CAAC,CAAC,CAAC;CAC3D,KAAK,MAAM,OAAO,MAAM;EACtB,IAAI,QAAQ,MAAM,IAAI,GAAG,GAAG;EAC5B,MAAM,IAAI,EAAE;EACZ,MAAM,IAAI,EAAE;EACZ,IAAI,cAAc,GAAG,CAAC,GAAG;EACzB,IAAI,QAAQ,KAAK,cAAc,CAAC,KAAK,cAAc,CAAC,GAAG;GACrD,YACE,GACA,GACA;IAAE,QAAQ,GAAG,SAAS,IAAI;IAAI,OAAO,QAAQ;IAAG,WAAW,QAAQ;GAAU,GAC7E,GACF;GACA;EACF;EACA,IAAI,KAAK;GACP,MAAM,GAAG,SAAS;GAClB,QAAQ,aAAa,GAAG,QAAQ,SAAS;GACzC,OAAO,aAAa,GAAG,QAAQ,SAAS;EAC1C,CAAC;CACH;AACF;AAaA,MAAM,mCAAmB,IAAI,QAA6B;;AAG1D,SAAS,WAAW,MAAc,OAAO,GAAW;CAClD,IAAI,KAAK,aAAa;CACtB,IAAI,KAAK,aAAa;CACtB,KAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK;EACpC,MAAM,KAAK,KAAK,WAAW,CAAC;EAC5B,KAAK,KAAK,KAAK,KAAK,IAAI,UAAU;EAClC,KAAK,KAAK,KAAK,KAAK,IAAI,UAAU;CACpC;CACA,KAAK,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC3C,MAAM,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC5C,KAAK,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC3C,MAAM,KAAK,KAAK,KAAM,OAAO,IAAK,UAAU;CAC5C,QAAQ,cAAc,UAAU,OAAO,OAAO,GAAA,CAAI,SAAS,EAAE;AAC/D;AAEA,SAAS,YAAY,SAA+B;CAClD,IAAI,OAAO,YAAY,YAAY,YAAY,MAAM;EAEnD,MAAM,IAAI,WADG,KAAK,UAAU,WAAW,IACf,CAAC;EACzB,OAAO;GAAE,UAAU;GAAG,MAAM;EAAE;CAChC;CACA,MAAM,SAAS,iBAAiB,IAAI,OAAO;CAC3C,IAAI,QAAQ,OAAO;CACnB,MAAM,EAAE,UAAU,YAAY,IAAI,KAAK,GAAG,SAAS;CACnD,MAAM,eAAe,WAAW,KAAK,UAAU,YAAY,IAAI,CAAC;CAIhE,MAAM,SAAS;EAAE,UAAU;EAAc,MAAM,GAAG,aAAa,GAHxC,WAAW,KAAK,UAAU,cAAc,IAAI,GAAG,CAGS,EAAE,GAFhE,OAAO,KAAK,IAAI,CAAC,CAAC,QAAQ,QAAQ,QAAQ,UAAU,KAAK,SAAS,KAAA,CAC3D,CAAC,CAAC,SAAS,IAAI,WAAW,KAAK,UAAU,IAAI,GAAG,CAAC,IAAI;CACkB;CAC/F,iBAAiB,IAAI,SAAS,MAAM;CACpC,OAAO;AACT;AAEA,SAAS,WAAW,SAAiC;CACnD,IAAI,OAAO,YAAY,YAAY,YAAY,MAAM,OAAO;CAC5D,MAAM,KAAM,QAA6B;CACzC,IAAI,OAAO,OAAO,UAAU,OAAO,KAAK;CACxC,IAAI,OAAO,OAAO,YAAY,OAAO,SAAS,EAAE,GAAG,OAAO,KAAK;CAC/D,OAAO;AACT;AAUA,SAAS,cAAc,UAA+C;CACpE,MAAM,uBAAO,IAAI,IAAoB;CACrC,MAAM,6BAAa,IAAI,IAAsB;CAC7C,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,KAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,OAAO,YAAY,OAAO;EAChC,MAAM,MAAM,WAAW,OAAO;EAC9B,IAAI,QAAQ,QAAQ,CAAC,KAAK,IAAI,GAAG,GAAG;GAClC,KAAK,IAAI,KAAK,KAAK,IAAI;GACvB;GACA;EACF;EAEA,MAAM,OAAO,WAAW,IAAI,KAAK,QAAQ;EACzC,IAAI,MAAM,KAAK,KAAK,KAAK,IAAI;OACxB,WAAW,IAAI,KAAK,UAAU,CAAC,KAAK,IAAI,CAAC;EAC9C;CACF;CACA,OAAO;EAAE;EAAM;EAAY;EAAO;CAAQ;AAC5C;;;;;;;;;;;;AAaA,SAAgB,uBACd,QACA,OACsB;CACtB,MAAM,IAAI,cAAc,MAAM,QAAQ,QAAQ,QAAQ,IAAI,OAAO,WAAW,CAAC,CAAC;CAC9E,MAAM,IAAI,cAAc,MAAM,QAAQ,OAAO,QAAQ,IAAI,MAAM,WAAW,CAAC,CAAC;CAC5E,IAAI,QAAQ;CACZ,IAAI,UAAU;CACd,IAAI,WAAW;CACf,IAAI,YAAY;CAEhB,KAAK,MAAM,CAAC,KAAK,SAAS,EAAE,MAAM;EAChC,MAAM,QAAQ,EAAE,KAAK,IAAI,GAAG;EAC5B,IAAI,UAAU,KAAA,GAAW;OACpB,IAAI,UAAU,MAAM;OACpB;CACP;CACA,KAAK,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,IAAI,CAAC,EAAE,KAAK,IAAI,GAAG,GAAG;CAEvD,MAAM,+BAAe,IAAI,IAAI,CAAC,GAAG,EAAE,WAAW,KAAK,GAAG,GAAG,EAAE,WAAW,KAAK,CAAC,CAAC;CAC7E,KAAK,MAAM,YAAY,cAAc;EACnC,MAAM,OAAO,EAAE,WAAW,IAAI,QAAQ,KAAK,CAAC;EAC5C,MAAM,QAAQ,EAAE,WAAW,IAAI,QAAQ,KAAK,CAAC;EAE7C,MAAM,yBAAS,IAAI,IAAoB;EACvC,KAAK,MAAM,QAAQ,MAAM,OAAO,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK,KAAK,CAAC;EACrE,IAAI,OAAO;EACX,KAAK,MAAM,QAAQ,OAAO;GACxB,MAAM,IAAI,OAAO,IAAI,IAAI,KAAK;GAC9B,IAAI,IAAI,GAAG;IACT,OAAO,IAAI,MAAM,IAAI,CAAC;IACtB;GACF;EACF;EACA,MAAM,WAAW,KAAK,SAAS;EAC/B,MAAM,YAAY,MAAM,SAAS;EACjC,MAAM,SAAS,KAAK,IAAI,UAAU,SAAS;EAC3C,aAAa;EACb,YAAY;EACZ,WAAW,WAAW;EACtB,SAAS,YAAY;CACvB;CAEA,MAAM,QAAQ,EAAE,QAAQ,EAAE;CAE1B,MAAM,YADU,EAAE,UAAU,EAAE,YACA,IAAI,OAAO,UAAU,IAAI,aAAa;CACpE,OAAO;EAAE;EAAO;EAAS;EAAU;EAAW;CAAU;AAC1D;AAMA,SAAS,eACP,SACA,OACqC;CACrC,MAAM,MAAM,QAAQ,SAAS,MAAM;CACnC,IAAI,CAAC,KAAK,OAAO,MAAM;CACvB,OAAO;EAAE,GAAI,MAAM,SAAS,CAAC;EAAI,GAAG;CAAI;AAC1C;AAEA,SAAS,eAAe,OAA0C;CAChE,OAAO,MAAM,QAAQ,MAAM,SAAS,QAAQ,IAAI,MAAM,QAAQ,SAAS,SAAS,KAAA;AAClF;AAEA,SAAS,eAAe,OAAsB,QAAwC;CACpF,OAAO;EACL,IAAI,MAAM;EACV,MAAM,MAAM;EACZ,MAAM,MAAM;EACZ;EACA,OAAO,CAAC;EACR,QAAQ,CAAC;EACT,QAAQ,CAAC;EACT,QAAQ,CAAC;EACT,OAAO,CAAC;CACV;AACF;;;;;;AAOA,SAAS,cAAc,WAAqB,YAA8C;CACxF,MAAM,SAAS,UAAU,QAAQ,OAAO,WAAW,IAAI,EAAE,CAAC;CAC1D,MAAM,MAAM,OAAO,KAAK,OAAO,WAAW,IAAI,EAAE,CAAE;CAElD,MAAM,QAAkB,CAAC;CACzB,MAAM,OAAO,IAAI,MAAc,IAAI,MAAM,CAAC,CAAC,KAAK,EAAE;CAClD,KAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,IAAI,KAAK;EACT,IAAI,KAAK,MAAM;EACf,OAAO,KAAK,IAAI;GACd,MAAM,MAAO,KAAK,MAAO;GACzB,IAAI,IAAI,MAAM,QAAQ,IAAI,IAAI,KAAK,MAAM;QACpC,KAAK;EACZ;EACA,IAAI,KAAK,GAAG,KAAK,KAAK,MAAM,KAAK;EACjC,MAAM,MAAM;CACd;CACA,MAAM,yBAAS,IAAI,IAAY;CAC/B,KAAK,IAAI,IAAI,MAAM,SAAS,MAAM,MAAM,SAAS,KAAK,IAAI,KAAK,GAAG,IAAI,KAAK,IACzE,OAAO,IAAI,OAAO,EAAE;CAEtB,OAAO,IAAI,IAAI,OAAO,QAAQ,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC;AACvD;AAEA,SAAS,UACP,eACA,cACA,GACA,GACA,WACW;CACX,MAAM,OAAO,eAAe,GAAG,SAAS;CACxC,IAAI,EAAE,SAAS,EAAE,MAAM,KAAK,UAAU;EAAE,QAAQ,EAAE;EAAM,OAAO,EAAE;CAAK;CACtE,IAAI,QAAQ,EAAE,OAAO,MAAM,QAAQ,EAAE,OAAO,GAC1C,KAAK,aAAa;EAAE,QAAQ,QAAQ,EAAE,OAAO;EAAG,OAAO,QAAQ,EAAE,OAAO;CAAE;CAE5E,IAAI,CAAC,gBAAgB,EAAE,SAAS,EAAE,OAAO,GACvC,KAAK,UAAU;EAAE,QAAQ,EAAE;EAAS,OAAO,EAAE;CAAQ;CAEvD,MAAM,SAAS,eAAe,eAAe,CAAC;CAC9C,MAAM,SAAS,eAAe,cAAc,CAAC;CAC7C,IAAI,CAAC,gBAAgB,QAAQ,MAAM,GAAG;EACpC,YAAY,QAAQ,QAAQ;GAAE,sBAAM,IAAI,IAAI,CAAC,QAAQ,CAAC;GAAG;EAAU,GAAG,KAAK,KAAK;EAChF,MAAM,UAAU,QAAQ;EACxB,MAAM,UAAU,QAAQ;EACxB,IAAI,CAAC,gBAAgB,SAAS,OAAO,GACnC,YACE,cAAc,OAAO,IAAI,UAAU,KAAA,GACnC,cAAc,OAAO,IAAI,UAAU,KAAA,GACnC,EAAE,UAAU,GACZ,KAAK,MACP;CAEJ;CAEA,YACE;EACE,kBAAkB,EAAE;EACpB,cAAc,EAAE;EAChB,aAAa,EAAE;CACjB,GACA;EACE,kBAAkB,EAAE;EACpB,cAAc,EAAE;EAChB,aAAa,EAAE;CACjB,GACA;EAAE,OAAO;EAAG;CAAU,GACtB,KAAK,MACP;CAEA,IAAI,EAAE,SAAS,EAAE,MACf,KAAK,OAAO,KAAK;EAAE,MAAM;EAAQ,QAAQ,EAAE;EAAM,OAAO,EAAE;CAAK,CAAC;CAElE,IAAI,CAAC,gBAAgB,EAAE,QAAQ,EAAE,MAAM,GACrC,YAAY,EAAE,QAAQ,EAAE,QAAQ;EAAE,QAAQ;EAAW,OAAO;EAAG;CAAU,GAAG,KAAK,MAAM;CAEzF,IAAI,EAAE,eAAe,EAAE,YACrB,KAAK,OAAO,KAAK;EACf,MAAM;EACN,QAAQ,aAAa,EAAE,YAAY,SAAS;EAC5C,OAAO,aAAa,EAAE,YAAY,SAAS;CAC7C,CAAC;CAGH,YACE,GACA,GACA;EAAE,MAAM;EAAoB,OAAO;EAAG;CAAU,GAChD,KAAK,KACP;CAEA,IAAI,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU;EACvD,MAAM,WAAW,uBAAuB,EAAE,SAAS,EAAE,OAAO;EAC5D,IAAI,SAAS,SAAS,SAAS,WAAW,SAAS,UAAU,KAAK,WAAW;CAC/E;CACA,OAAO;AACT;AAEA,SAAS,gBAAgB,MAA0B;CACjD,OAAO,QACL,KAAK,WACL,KAAK,SACL,KAAK,cACL,KAAK,WACL,KAAK,YACL,KAAK,MAAM,UACX,KAAK,OAAO,UACZ,KAAK,OAAO,UACZ,KAAK,OAAO,UACZ,KAAK,MAAM,MACb;AACF;AAEA,SAAS,iBAAiB,MAAyB;CACjD,QACG,KAAK,UAAU,IAAI,MACnB,KAAK,QAAQ,IAAI,MACjB,KAAK,aAAa,IAAI,MACtB,KAAK,UAAU,IAAI,MACnB,KAAK,WAAW,IAAI,KACrB,KAAK,MAAM,SACX,KAAK,OAAO,SACZ,KAAK,OAAO,SACZ,KAAK,OAAO,SACZ,KAAK,MAAM;AAEf;AAMA,SAAS,YACP,QACA,OACA,WAC8E;CAC9E,MAAM,UAAU,IAAI,IAAI,QAAQ,mBAAmB,CAAC,CAAC;CACrD,MAAM,UAAU,IAAI,IAAI,OAAO,mBAAmB,CAAC,CAAC;CACpD,MAAM,YAAY,QAAQ,YAAY,CAAC;CACvC,MAAM,YAAY,OAAO,YAAY,CAAC;CACtC,MAAM,aAAa,QAAQ,uBAAuB,CAAC;CACnD,MAAM,aAAa,OAAO,uBAAuB,CAAC;CAClD,MAAM,sBAAM,IAAI,IAAI;EAClB,GAAG;EACH,GAAG;EACH,GAAG,OAAO,KAAK,SAAS;EACxB,GAAG,OAAO,KAAK,SAAS;EACxB,GAAG,OAAO,KAAK,UAAU;EACzB,GAAG,OAAO,KAAK,UAAU;CAC3B,CAAC;CACD,MAAM,UAAwB,CAAC;CAC/B,KAAK,MAAM,MAAM,CAAC,GAAG,GAAG,CAAC,CAAC,KAAK,GAAG;EAChC,MAAM,UAAgC,CAAC;EACvC,MAAM,YAAY,QAAQ,IAAI,EAAE;EAChC,MAAM,WAAW,QAAQ,IAAI,EAAE;EAC/B,IAAI,CAAC,gBAAgB,WAAW,KAAK,WAAW,GAAG,GACjD,QAAQ,KAAK;GACX,MAAM;GACN,QAAQ,aAAa,WAAW,KAAK,SAAS;GAC9C,OAAO,aAAa,WAAW,KAAK,SAAS;EAC/C,CAAC;EAEH,MAAM,KAAK,UAAU;EACrB,MAAM,KAAK,UAAU;EACrB,IAAI,CAAC,gBAAgB,IAAI,EAAE,GAAG;GAC5B,IAAI,cAAc,EAAE,KAAK,cAAc,EAAE,GACvC,YACE,cAAc,EAAE,IAAI,KAAK,KAAA,GACzB,cAAc,EAAE,IAAI,KAAK,KAAA,GACzB;IAAE,QAAQ;IAAa,OAAO;IAAG;GAAU,GAC3C,OACF;QAEA,QAAQ,KAAK;IACX,MAAM;IACN,QAAQ,aAAa,IAAI,SAAS;IAClC,OAAO,aAAa,IAAI,SAAS;GACnC,CAAC;EAEL;EACA,IAAI,cAAc,UAChB,QAAQ,KAAK;GAAE;GAAI,QAAQ,WAAW,UAAU;GAAW;EAAQ,CAAC;OAC/D,IAAI,QAAQ,SAAS,GAC1B,QAAQ,KAAK;GAAE;GAAI,QAAQ;GAAW;EAAQ,CAAC;CAEnD;CACA,MAAM,QAAQ,IAAI,IAAI,QAAQ,gBAAgB,CAAC,CAAC;CAChD,MAAM,QAAQ,IAAI,IAAI,OAAO,gBAAgB,CAAC,CAAC;CAC/C,OAAO;EACL;EACA,WAAW;GACT,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC;GACjD,SAAS,CAAC,GAAG,KAAK,CAAC,CAAC,QAAQ,QAAQ,CAAC,MAAM,IAAI,GAAG,CAAC;EACrD;CACF;AACF;AAMA,SAAS,cAAc,GAAY,GAAqB;CACtD,IAAI,OAAO,MAAM,YAAY,OAAO,MAAM,UAAU,OAAO,KAAK,IAAI,IAAI,CAAC,IAAI;CAC7E,OAAO,CAAC,gBAAgB,GAAG,CAAC;AAC9B;;;;;;;;;;;;;;AAeA,SAAgB,aACd,QACA,OACA,UAA8B,CAAC,GAClB;CACb,MAAM,YAAY,QAAQ,oBAAoB;CAC9C,MAAM,UAAU,MAAM,QAAQ,OAAO,MAAM,IAAI,OAAO,SAAS,CAAC;CAChE,MAAM,UAAU,MAAM,QAAQ,MAAM,MAAM,IAAI,MAAM,SAAS,CAAC;CAC9D,MAAM,QAAQ,IAAI,IAAI,QAAQ,KAAK,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;CAC/D,MAAM,QAAQ,IAAI,IAAI,QAAQ,KAAK,UAAU,CAAC,MAAM,IAAI,KAAK,CAAC,CAAC;CAC/D,MAAM,SAAS,IAAI,IAAI,QAAQ,KAAK,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;CAC/D,MAAM,SAAS,IAAI,IAAI,QAAQ,KAAK,OAAO,MAAM,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;CAC/D,MAAM,QAAQ,cACZ,QAAQ,KAAK,UAAU,MAAM,EAAE,GAC/B,MACF;CAEA,MAAM,QAAqB,CAAC;CAC5B,MAAM,UAAuB,CAAC;CAC9B,MAAM,UAAuB,CAAC;CAC9B,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,MAAM,IAAI,MAAM,EAAE,GAAG;EACzB,MAAM,OAAO,eAAe,OAAO,OAAO;EAC1C,MAAM,QAAQ,eAAe,KAAK;EAClC,IAAI,UAAU,KAAA,GAAW,KAAK,eAAe;EAC7C,MAAM,KAAK,IAAI;CACjB;CACA,KAAK,MAAM,SAAS,SAAS;EAC3B,IAAI,MAAM,IAAI,MAAM,EAAE,GAAG;EACzB,MAAM,OAAO,eAAe,OAAO,SAAS;EAC5C,MAAM,QAAQ,eAAe,KAAK;EAClC,IAAI,UAAU,KAAA,GAAW,KAAK,eAAe;EAC7C,QAAQ,KAAK,IAAI;CACnB;CAEA,KAAK,IAAI,IAAI,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;EAC5C,MAAM,IAAI,QAAQ;EAClB,MAAM,IAAI,MAAM,IAAI,EAAE,EAAE;EACxB,IAAI,CAAC,GAAG;EAER,MAAM,OADY,MAAM,KAAK,OAAO,SAAS,EAAE,QAAQ,MAAM,SAAS,EAAE,MAEpE,eAAe,GAAG,SAAS,IAC3B,UAAU,QAAQ,OAAO,GAAG,GAAG,SAAS;EAC5C,IAAI,MAAM,IAAI,EAAE,EAAE,GAAG,KAAK,QAAQ;GAAE,QAAQ,OAAO,IAAI,EAAE,EAAE;GAAI,OAAO,OAAO,IAAI,EAAE,EAAE;EAAG;EACxF,IAAI,gBAAgB,IAAI,GAAG,QAAQ,KAAK,IAAI;CAC9C;CAEA,MAAM,SAA+B,CAAC;CACtC,MAAM,QAAQ,OAAO,WAAY,CAAC;CAClC,MAAM,QAAQ,MAAM,WAAY,CAAC;CACjC,MAAM,UAAU,MAAM,UAAU,CAAC;CACjC,MAAM,UAAU,MAAM,UAAU,CAAC;CACjC,IAAI,cAAc,QAAQ,IAAI,QAAQ,EAAE,KAAK,cAAc,QAAQ,IAAI,QAAQ,EAAE,GAAG;EAClF,MAAM,OAAO,MACX,EAAE,WAAW,KAAK,EAAE,OAAO,MAAM,OAAO,MAAM,QAAQ,IAClD,GAAI,EAAE,EAAE,CAAY,QAAQ,CAAC,EAAE,IAAK,EAAE,EAAE,CAAY,QAAQ,CAAC,MAC7D,KAAA;EACN,OAAO,KAAK;GAAE,MAAM;GAAU,QAAQ,IAAI,OAAO;GAAG,OAAO,IAAI,OAAO;EAAE,CAAC;CAC3E;CACA,KAAK,MAAM,OAAO;EAAC;EAAQ;EAAW;CAAO,GAC3C,IAAI,cAAc,MAAM,MAAM,MAAM,IAAI,GAAG;EACzC,MAAM,OAAO,MACX,OAAO,MAAM,WAAW,OAAO,KAAK,MAAM,IAAI,GAAG,IAAI,GAAG,IAAI,aAAa,GAAG,SAAS;EACvF,OAAO,KAAK;GAAE,MAAM;GAAK,QAAQ,IAAI,MAAM,IAAI;GAAG,OAAO,IAAI,MAAM,IAAI;EAAE,CAAC;CAC5E;CAGF,MAAM,UAAgC,CAAC;CACvC,YACE;EACE,iBAAiB,OAAO;EACxB,gBAAgB,OAAO;EACvB,gBAAgB,OAAO;EACvB,sBAAsB,OAAO,wBAAwB,KAAA;EACrD,iBAAiB,OAAO;CAC1B,GACA;EACE,iBAAiB,MAAM;EACvB,gBAAgB,MAAM;EACtB,gBAAgB,MAAM;EACtB,sBAAsB,MAAM,wBAAwB,KAAA;EACpD,iBAAiB,MAAM;CACzB,GACA;EAAE,OAAO;EAAG,WAAW,KAAK,IAAI,WAAW,GAAG;CAAE,GAChD,OACF;CAEA,MAAM,aAAmC,CAAC;CAC1C,MAAM,SAAU,OAAO,eAAe,CAAC;CACvC,MAAM,SAAU,MAAM,eAAe,CAAC;CACtC,MAAM,OAAO,cAAc,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC;CACvD,MAAM,OAAO,cAAc,OAAO,GAAG,IAAI,OAAO,MAAM,CAAC;CACvD,IAAI,CAAC,gBAAgB,KAAK,YAAY,KAAK,UAAU,GACnD,WAAW,KAAK;EACd,MAAM;EACN,QAAQ,aAAa,KAAK,YAAY,SAAS;EAC/C,OAAO,aAAa,KAAK,YAAY,SAAS;CAChD,CAAC;CAEH,MAAM,cAAoC,CAAC;CAC3C,IAAI,CAAC,gBAAgB,QAAQ,MAAM,GACjC,YACE;EAAE,GAAG;EAAQ,KAAK;GAAE,GAAG;GAAM,YAAY,KAAA;EAAU;CAAE,GACrD;EAAE,GAAG;EAAQ,KAAK;GAAE,GAAG;GAAM,YAAY,KAAA;EAAU;CAAE,GACrD;EAAE,OAAO;EAAG;CAAU,GACtB,WACF;CAGF,MAAM,EAAE,SAAS,cAAc,YAAY,OAAO,SAAS,MAAM,SAAS,SAAS;CAEnF,MAAM,WAAiC,CAAC;CACxC,IAAI,OAAO,SAAS,MAAM,MACxB,SAAS,KAAK;EACZ,MAAM;EACN,QAAQ,aAAa,OAAO,MAAM,SAAS;EAC3C,OAAO,aAAa,MAAM,MAAM,SAAS;CAC3C,CAAC;CAEH,YAAY,OAAO,UAAU,MAAM,UAAU;EAAE,OAAO;EAAG;CAAU,GAAG,QAAQ;CAE9E,SAAS,MAAM,GAAG,MAAM,aAAa,EAAE,IAAI,IAAI,aAAa,EAAE,IAAI,CAAC;CAEnE,MAAM,WAAqB,CAAC;CAC5B,MAAM,WAAW;CACjB,MAAM,WAAW;CACjB,KAAK,MAAM,uBAAO,IAAI,IAAI,CAAC,GAAG,OAAO,KAAK,QAAQ,GAAG,GAAG,OAAO,KAAK,QAAQ,CAAC,CAAC,GAAG;EAC/E,IAAI,qBAAqB,IAAI,GAAG,GAAG;EACnC,IAAI,CAAC,cAAc,SAAS,MAAM,SAAS,IAAI,GAAG,SAAS,KAAK,GAAG;CACrE;CACA,SAAS,KAAK;CAEd,MAAM,cACJ,MAAM,SACN,QAAQ,SACR,QAAQ,QAAQ,KAAK,SAAS,MAAM,iBAAiB,IAAI,GAAG,CAAC,IAC7D,OAAO,SACP,QAAQ,SACR,WAAW,SACX,YAAY,SAGZ,QAAQ,QAAQ,KAAK,WAAW,MAAM,KAAK,IAAI,GAAG,OAAO,QAAQ,MAAM,GAAG,CAAC,IAC3E,UAAU,MAAM,SAChB,UAAU,QAAQ,SAClB,SAAS,SACT,SAAS;CAEX,OAAO;EACL,QAAQ;GAAE;GAAO;GAAS;GAAS,WAAW,MAAM,OAAO;EAAE;EAC7D;EACA;EACA;EACA;EACA;EACA,iBAAiB;EACjB;EACA;EACA;CACF;AACF;AAEA,SAAS,aAAa,MAAsB;CAC1C,IAAI,SAAS,QAAQ,OAAO;CAC5B,IAAI,SAAS,eAAe,OAAO;CACnC,OAAO;AACT"}