{
  "version": 3,
  "sources": ["elevation_profile_core.js"],
  "sourcesContent": ["/*\n * static/js/elevation_profile_core.js \u2014 the saved-route elevation profile.\n *\n * A route's popup (map.js's `activateRoute`) reports its length and its\n * vertical figures as text. This module turns the SAME data into the\n * picture those figures summarise: where the climbing is, where the\n * descending is, and how the two are distributed along the track.\n *\n * ## Where the data comes from \u2014 and what is deliberately absent\n *\n * Nothing here fetches anything. `Route.points` is stored as\n * `[lon, lat, ele]` and `routes_geojson` hands it to the map verbatim as\n * LineString `coordinates`, so the elevation series is already the third\n * ordinate of the geometry the routes layer is drawn from. RFC 7946\n * allows that third element and MapLibre ignores it. The profile is\n * therefore a pure derivation of a payload the page already holds \u2014 and\n * because the routes overlay is write-through cached\n * (`pwaMapOverlayCache.putOverlay('routes', \u2026)`), it draws offline too.\n *\n * `ele` is whatever the uploaded GPX's `<ele>` said, and NOTHING ELSE.\n * There is no DEM lookup, no smoothing and \u2014 the important one \u2014 no\n * interpolation across a gap. A point whose `<ele>` the source omitted is\n * parsed to `null` (`apps/routes/services/gpx.py`) and stays null here:\n * the line BREAKS across it rather than being bridged with a plausible\n * straight segment. Drawing terrain the file never recorded is the same\n * class of error as reporting 0 m ascent for a track with no elevation at\n * all, which `Route` refuses to do for stated safety reasons.\n *\n * A track with no elevation anywhere has no profile, and `readProfile`\n * says so with `hasElevation: false` rather than returning a flat line at\n * zero. The caller draws nothing.\n *\n * ## Why the figures are NOT recomputed here\n *\n * `distance_m`, `ascent_m` and `descent_m` arrive as feature properties,\n * measured at ingest on the FULL-RESOLUTION track. What this module sees\n * is the simplified geometry (Douglas-Peucker, capped at 2,000 points),\n * so any total it computed itself would be a second, slightly smaller set\n * of numbers for the same route \u2014 shown next to the first, in the same\n * popup. So the totals stay the server's, and everything here is used\n * only to SHAPE the curve. `readProfile` returns `distanceM` for scaling\n * the x-axis, never for display.\n *\n * The min/max elevation it reports ARE displayed, and are honest: they\n * are the extremes of the stored series, and simplification cannot\n * invent a high or low point that was not in the source.\n *\n * ## The slope colouring (SNOW-960)\n *\n * SNOW-910 colours the route LINE on the map by the steepness of the\n * GROUND under it. This chart is a picture of the same track, so it\n * carries the same colours: the CURVE is stroked in the slope classes.\n * One object, one palette, whichever surface you read it on.\n *\n * THE COLOUR IS ON THE STROKE AND NOWHERE ELSE. Tinting the area\n * underneath by class as well was built, drawn against two real tracks and\n * rejected: on a 16 km tour of 638 samples, 77% of which are under 30\n * degrees, the steep classes arrive as 25 m slivers and a per-class tint\n * renders them as hairline vertical stripes \u2014 a texture the eye reads as\n * noise rather than as a place along the tour. It also costs the fill the\n * one job it has, which is to make the shape read as ground at a glance\n * rather than as a line graph. So the region stays ONE shape in the\n * route's own colour, and the classes live on the line, where each is a\n * position rather than a stripe.\n *\n * The region does drop from 16% to 6% opacity when the curve is coloured\n * (`SLOPE_AREA_OPACITY`). At the strength that suits a fuchsia curve, a\n * fuchsia region overpowers a class-coloured one.\n *\n * The record arrives as `properties.slope` on the route feature the\n * caller already holds \u2014 `{points: [\u2026N + 1], angles: [\u2026N]}` \u2014 and\n * `route_slope_core.js` owns both the palette and the bucketing. Nothing\n * here classifies an angle itself.\n *\n * BANDS ARE PLACED BY SHARE, AND THE SHARE COMES FROM THE INDEX. The\n * sampler walks a fixed stride ALONG the track\n * (apps/routes/services/slope_segments.py), so segment i owns the i-th of\n * N equal shares of it \u2014 no distance needs to be measured on this side at\n * all, and none should be. Neither of the two obvious alternatives works:\n * an absolute metre mark is wrong because this module's x-axis is summed\n * from the SIMPLIFIED geometry while the stride was walked on the\n * full-resolution track, and measuring the straight chords between sample\n * coordinates is wrong because a chord cuts the corner at every bend, by\n * more on a switchback than on a straight, so the loss cannot be\n * normalised away. See `slopeBands` for the measured cost of that second\n * one.\n *\n * ## Exports (frozen `self.pwaElevationProfileCore`)\n *\n *   readProfile(coordinates)            \u2192 profile data, gaps preserved\n *   buildPaths(profile, geometry)       \u2192 SVG path `d` strings\n *   slopeBands(slope)                   \u2192 the track's classes, as fractions\n *   buildSlopePaths(profile, slope, \u2026)  \u2192 one LINE path per class present\n *   createProfileSvg(profile, options)  \u2192 the <svg> element, or null\n *   VIEWBOX                             \u2192 { width, height }\n */\n\n(function () {\n  'use strict';\n\n  /**\n   * Mean Earth radius in metres (IUGG).\n   *\n   * The same constant `apps/routes/services/gpx.py` measures with, for the\n   * same reason: over tens of kilometres of mountain terrain the spherical\n   * / ellipsoidal difference sits far below the GPS noise already in the\n   * source track.\n   */\n  var EARTH_RADIUS_M = 6371008.8;\n\n  /**\n   * The SVG user-space box every profile is drawn in.\n   *\n   * Sized for the route popup, which is `min(320px, 100vw - 32px)` wide\n   * with padding either side. The element itself is rendered `w-full`, so\n   * this is an aspect ratio and a coordinate system rather than a pixel\n   * size \u2014 a narrower popup scales it down intact.\n   */\n  var VIEWBOX = { width: 288, height: 72 };\n\n  /**\n   * Vertical inset, in user-space units, above and below the curve.\n   *\n   * Keeps the highest point's stroke from being clipped by the top edge\n   * and leaves the lowest point sitting on a visible floor rather than\n   * merged into the baseline rule.\n   */\n  var PAD_Y = 6;\n\n  /**\n   * Stroke width of the curve, in user-space units.\n   *\n   * Named rather than inline because the unknown treatment's dash is\n   * expressed in multiples of it \u2014 the same way the map line's\n   * `line-dasharray` is in line-widths, so the two dashes read as one\n   * pattern at whatever size each is drawn.\n   */\n  var STROKE_WIDTH = 1.75;\n\n  /**\n   * Opacity of the region beneath an UNCOLOURED curve.\n   *\n   * The chart as it stood before SNOW-960, and the value a route that has\n   * never been sampled still draws at.\n   */\n  var AREA_OPACITY = 0.16;\n\n  /**\n   * Opacity of that same region beneath a SLOPE-COLOURED curve.\n   *\n   * Lower, and measured by eye against two real tracks rather than\n   * derived. The region is the route's own fuchsia either way; at the\n   * strength that sits correctly under a fuchsia curve it overpowers a\n   * class-coloured one, and the colours that matter are the ones on the\n   * line. See the module comment.\n   */\n  var SLOPE_AREA_OPACITY = 0.06;\n\n  /**\n   * The dash of a stretch the terrain had no answer for.\n   *\n   * `[2, 1.5]` line-widths, which is `trip-route-slope-unknown`'s\n   * `line-dasharray` in `trip_map.js` verbatim \u2014 the same stretch of\n   * ground dashes identically on the trip map and in the chart. (The home\n   * map's `routes-slope-unknown` carried it too, until SNOW-1017 drew\n   * routes there as legs.)\n   */\n  var UNKNOWN_DASH = [2, 1.5];\n\n  /**\n   * Great-circle distance between two WGS-84 points, in metres.\n   *\n   * @param {number} lon1 Longitude of the first point, degrees.\n   * @param {number} lat1 Latitude of the first point, degrees.\n   * @param {number} lon2 Longitude of the second point, degrees.\n   * @param {number} lat2 Latitude of the second point, degrees.\n   * @returns {number} Distance in metres.\n   */\n  function haversineM(lon1, lat1, lon2, lat2) {\n    var phi1 = (lat1 * Math.PI) / 180;\n    var phi2 = (lat2 * Math.PI) / 180;\n    var deltaPhi = phi2 - phi1;\n    var deltaLambda = ((lon2 - lon1) * Math.PI) / 180;\n    var a =\n      Math.sin(deltaPhi / 2) * Math.sin(deltaPhi / 2) +\n      Math.cos(phi1) * Math.cos(phi2) * Math.sin(deltaLambda / 2) * Math.sin(deltaLambda / 2);\n    return 2 * EARTH_RADIUS_M * Math.asin(Math.sqrt(a));\n  }\n\n  /**\n   * Read a LineString's coordinates into plottable profile data.\n   *\n   * Walks the track once, accumulating along-track distance for the\n   * x-axis and collecting elevation for the y-axis. Consecutive points\n   * that both carry an elevation join into a run; a point without one\n   * ENDS the current run and starts a new one after it, so the caller\n   * draws a broken line rather than bridging terrain the file never\n   * recorded.\n   *\n   * Distance keeps accumulating across a gap \u2014 the horizontal axis is\n   * along-track distance, which is known for every point regardless of\n   * whether its elevation is \u2014 so the runs either side of a gap sit at\n   * their true positions with a hole between them, not butted together.\n   *\n   * @param {Array<Array<number>>} coordinates `[lon, lat, ele]` triples;\n   *   `ele` may be null, undefined or absent.\n   * @returns {{\n   *   runs: Array<Array<{d: number, e: number}>>,\n   *   distanceM: number,\n   *   minEle: number|null,\n   *   maxEle: number|null,\n   *   hasElevation: boolean\n   * }} `runs` holds one entry per unbroken elevation run, each a list of\n   *   `{d: metres along track, e: metres above sea level}`. Runs of a\n   *   single point are kept: they are a real, if unplottable, fact, and\n   *   `buildPaths` decides what to do with them.\n   */\n  function readProfile(coordinates) {\n    var empty = {\n      runs: [], distanceM: 0, minEle: null, maxEle: null, hasElevation: false,\n    };\n    if (!Array.isArray(coordinates) || coordinates.length === 0) return empty;\n\n    var runs = [];\n    var current = [];\n    var distance = 0;\n    var minEle = Infinity;\n    var maxEle = -Infinity;\n    var previous = null;\n\n    for (var i = 0; i < coordinates.length; i += 1) {\n      var point = coordinates[i];\n      if (!Array.isArray(point) || point.length < 2) continue;\n\n      var lon = point[0];\n      var lat = point[1];\n      if (typeof lon !== 'number' || typeof lat !== 'number') continue;\n\n      if (previous) distance += haversineM(previous[0], previous[1], lon, lat);\n      previous = [lon, lat];\n\n      // `typeof` rather than a truthiness test: 0 m above sea level is a\n      // legitimate elevation, and `null` is the parser's explicit \"this\n      // point's <ele> was missing or unparseable\".\n      var ele = point.length > 2 ? point[2] : null;\n      if (typeof ele !== 'number' || !isFinite(ele)) {\n        // The gap ends the run. Distance carried on accumulating above,\n        // so whatever comes next starts at its true along-track position.\n        if (current.length) runs.push(current);\n        current = [];\n        continue;\n      }\n\n      if (ele < minEle) minEle = ele;\n      if (ele > maxEle) maxEle = ele;\n      current.push({ d: distance, e: ele });\n    }\n    if (current.length) runs.push(current);\n\n    if (!runs.length) return { ...empty, distanceM: distance };\n\n    return {\n      runs: runs,\n      distanceM: distance,\n      minEle: minEle,\n      maxEle: maxEle,\n      hasElevation: true,\n    };\n  }\n\n  /**\n   * The x/y projections for one profile in one box.\n   *\n   * @param {object} profile A `readProfile` result.\n   * @param {{width: number, height: number}} box The user-space box.\n   * @returns {{floor: number, x: function(number): number,\n   *   y: function(number): number}} The floor the areas close onto, and\n   *   the two scales.\n   */\n  function makeScales(profile, box) {\n    var range = profile.maxEle - profile.minEle;\n    var floor = box.height - PAD_Y;\n    var usable = box.height - PAD_Y * 2;\n\n    return {\n      floor: floor,\n      x: function (d) {\n        if (!profile.distanceM) return 0;\n        return (d / profile.distanceM) * box.width;\n      },\n      y: function (e) {\n        // No range: put the line down the middle rather than at 0/0.\n        if (!range) return box.height / 2;\n        return floor - ((e - profile.minEle) / range) * usable;\n      },\n    };\n  }\n\n  /**\n   * Project one list of profile points into a line and an area `d` string.\n   *\n   * The shared tail of both path builders: `buildPaths` calls it once per\n   * elevation run, `buildSlopePaths` once per piece of a run. Keeping it\n   * in one place is what guarantees a slope-coloured curve traces exactly\n   * the line an uncoloured one would.\n   *\n   * @param {Array<{d: number, e: number}>} points At least two of them.\n   * @param {{floor: number, x: function, y: function}} scales A\n   *   `makeScales` result.\n   * @returns {{line: string, area: string}} The stroked line, and the\n   *   region between it and the floor.\n   */\n  function projectPoints(points, scales) {\n    var line = '';\n    for (var i = 0; i < points.length; i += 1) {\n      var x = scales.x(points[i].d).toFixed(2);\n      var y = scales.y(points[i].e).toFixed(2);\n      line += (i === 0 ? 'M' : 'L') + x + ' ' + y;\n      if (i < points.length - 1) line += ' ';\n    }\n\n    var startX = scales.x(points[0].d).toFixed(2);\n    var endX = scales.x(points[points.length - 1].d).toFixed(2);\n    var base = scales.floor.toFixed(2);\n    // Down to the floor, back along it, and closed \u2014 the area under\n    // exactly these points and no further.\n    return { line: line, area: line + ' L' + endX + ' ' + base + ' L' + startX + ' ' + base + ' Z' };\n  }\n\n  /**\n   * Project profile data into SVG path `d` strings.\n   *\n   * Two paths per run: the stroked line itself, and a closed area between\n   * that line and the floor. The area is what makes the shape read as\n   * terrain at a glance rather than as a graph, and it is per-run so a\n   * gap leaves a genuine hole in the fill too.\n   *\n   * Scaling notes:\n   *\n   *   - **x** is along-track distance over the track's full length. A\n   *     zero-length track (every point in the same place) would divide by\n   *     zero, so it degenerates to a flat line across the full width.\n   *   - **y** is elevation over the track's own min-to-max range, NOT a\n   *     fixed or sea-level-anchored scale. A profile is read for its\n   *     shape, and anchoring at zero would flatten a 300 m tour into a\n   *     hairline. The range is labelled in the popup precisely because\n   *     the axis is relative.\n   *   - a track with **no vertical range at all** (genuinely flat, or one\n   *     single elevation repeated) would also divide by zero, and is\n   *     centred instead \u2014 a level line, which is what it is.\n   *\n   * @param {object} profile A `readProfile` result.\n   * @param {{width: number, height: number}} [geometry] User-space box.\n   *   Defaults to `VIEWBOX`.\n   * @returns {Array<{line: string, area: string}>} One entry per run with\n   *   at least two points. Single-point runs are dropped: a lone vertex\n   *   has no line to draw and would render as an invisible zero-width\n   *   area.\n   */\n  function buildPaths(profile, geometry) {\n    var box = geometry || VIEWBOX;\n    if (!profile || !profile.hasElevation) return [];\n\n    var scales = makeScales(profile, box);\n    var paths = [];\n    for (var i = 0; i < profile.runs.length; i += 1) {\n      var run = profile.runs[i];\n      if (run.length < 2) continue;\n      paths.push(projectPoints(run, scales));\n    }\n    return paths;\n  }\n\n  /**\n   * Read a route's wire slope record into bands of the track.\n   *\n   * Each segment becomes its SHARE of the track, and consecutive segments\n   * of the same class merge \u2014 a tour that skins for two kilometres of\n   * gentle valley arrives as 80 segments and leaves as one band.\n   *\n   * THE SHARE COMES FROM THE SEGMENT'S INDEX, NOT FROM MEASURING THE\n   * SAMPLE POINTS, and that is the whole subtlety of this function.\n   * `stride_distances` places every boundary but the last at a whole\n   * multiple of the stride ALONG THE TRACK, so segment i occupies exactly\n   * the i-th of N equal shares of it. Measuring instead the straight\n   * chords between consecutive sample coordinates answers a different\n   * question: wherever the track bends between two samples the chord cuts\n   * the corner and comes up short, and because a switchback loses more\n   * than a straight does, the shortfall accumulates unevenly and dividing\n   * by the shortened total cannot take it back out. Measured against both\n   * test tracks it displaced bands by up to 1.9 px of the 288-unit chart,\n   * concentrated exactly where a skin track zigzags \u2014 which is where the\n   * steep classes are. Index shares are off by at most 0.14 px, and that\n   * residue is the absorbed stub described below.\n   *\n   * The one irregular segment is the last: `stride_distances` absorbs a\n   * trailing remainder into it rather than appending a sliver, so it runs\n   * between half and one and a half strides. Treating it as an equal\n   * share is the only approximation here, it is bounded by half a stride\n   * over the whole track, and the last band is pinned to exactly 1 so no\n   * sliver of curve is left belonging to no class at all.\n   *\n   * @param {?{points?: Array<Array<number>>, angles?: Array<?number>}}\n   *   slope The compact record from the route feature's `slope` property.\n   *   `points` is not measured, but IS checked: N + 1 coordinates to N\n   *   angles is the record's own integrity claim, and a record failing it\n   *   is one whose segments would be placed against the wrong ground.\n   * @returns {Array<{classIndex: ?number, from: number, to: number}>}\n   *   Bands covering [0, 1], in track order. `classIndex` is null for a\n   *   segment the terrain had no answer for. Empty when there is nothing\n   *   to colour by: no record, a malformed one, or a page that has not\n   *   loaded `route_slope_core.js`.\n   */\n  function slopeBands(slope) {\n    // The palette and the bucketing both live there, and nothing here\n    // classifies an angle itself. A page that draws a profile without\n    // loading it (the trip map, today) simply gets no bands and the\n    // uncoloured curve, which is the honest rendering of \"this surface\n    // does not know about slope classes\".\n    var core = self.pwaRouteSlopeCore;\n    if (!core || !slope) return [];\n\n    var points = slope.points;\n    var angles = slope.angles;\n    if (!Array.isArray(points) || !Array.isArray(angles)) return [];\n    // The same pairing rule `route_slope_core.js` enforces: N + 1 to N, or\n    // the bands would be placed against the wrong ground.\n    if (points.length !== angles.length + 1) return [];\n\n    var count = angles.length;\n    if (!count) return [];\n\n    var bands = [];\n    for (var i = 0; i < count; i += 1) {\n      var index = core.classify(angles[i]);\n      var last = bands.length ? bands[bands.length - 1] : null;\n      // `===` covers the null case too, which is what merges a run of\n      // consecutive unknowns into one dashed stretch rather than one per\n      // 25 m of unsurveyed ground.\n      if (last && last.classIndex === index) {\n        last.to = (i + 1) / count;\n        continue;\n      }\n      bands.push({\n        classIndex: index,\n        from: i / count,\n        to: (i + 1) / count,\n      });\n    }\n\n    bands[bands.length - 1].to = 1;\n    return bands;\n  }\n\n  /**\n   * Where along a run's leg the class changes, as along-track distances.\n   *\n   * @param {Array<{classIndex: ?number, from: number, to: number}>} bands\n   *   A `slopeBands` result.\n   * @param {number} startD Distance at the start of the leg, metres.\n   * @param {number} endD Distance at its end, metres.\n   * @param {number} distanceM The track's full length on THIS module's\n   *   scale, which is what turns a band's fraction back into a distance.\n   * @returns {Array<number>} Ascending distances strictly inside the leg.\n   *   Usually empty: a leg of a simplified track is typically shorter\n   *   than a 25 m sample and falls wholly inside one band.\n   */\n  function cutsWithin(bands, startD, endD, distanceM) {\n    var cuts = [];\n    for (var i = 1; i < bands.length; i += 1) {\n      var at = bands[i].from * distanceM;\n      if (at > startD && at < endD) cuts.push(at);\n    }\n    return cuts;\n  }\n\n  /**\n   * Split a run at every class boundary, into contiguous same-class pieces.\n   *\n   * A leg of the track that crosses a boundary is CUT at it and the\n   * elevation interpolated there, so the colour changes exactly where the\n   * ground does rather than at whichever vertex happened to be nearest.\n   * On a simplified track a single leg can span several bands, so a leg\n   * may produce several cuts.\n   *\n   * Each resulting sub-segment takes the class of its MIDPOINT, which is\n   * the same rule the server sampled by \u2014 a segment's angle is the ground\n   * at its middle, never at an end it only touches.\n   *\n   * @param {Array<{d: number, e: number}>} run One elevation run.\n   * @param {Array<{classIndex: ?number, from: number, to: number}>} bands\n   *   A `slopeBands` result.\n   * @param {number} distanceM The track's full length on this scale.\n   * @returns {Array<{classIndex: ?number, points: Array<{d: number,\n   *   e: number}>}>} Pieces in track order, each with at least two points.\n   */\n  function splitRun(run, bands, distanceM) {\n    var pieces = [];\n    var current = null;\n\n    var classAt = function (distance) {\n      var fraction = distance / distanceM;\n      for (var i = 0; i < bands.length; i += 1) {\n        // `<` on the upper edge, so a point exactly on a boundary belongs\n        // to the band starting there \u2014 the raster's own convention, and\n        // the one `classify` follows for the angles themselves.\n        if (fraction < bands[i].to) return bands[i].classIndex;\n      }\n      return bands[bands.length - 1].classIndex;\n    };\n\n    var extend = function (from, to) {\n      var index = classAt((from.d + to.d) / 2);\n      if (current && current.classIndex === index) {\n        current.points.push(to);\n        return;\n      }\n      if (current) pieces.push(current);\n      current = { classIndex: index, points: [from, to] };\n    };\n\n    for (var i = 0; i < run.length - 1; i += 1) {\n      var a = run[i];\n      var b = run[i + 1];\n      var cuts = cutsWithin(bands, a.d, b.d, distanceM);\n      var previous = a;\n      for (var j = 0; j < cuts.length; j += 1) {\n        var span = b.d - a.d;\n        // A zero-length leg holds no cut (nothing is strictly inside it),\n        // so the division below is safe.\n        var t = (cuts[j] - a.d) / span;\n        var at = { d: cuts[j], e: a.e + (b.e - a.e) * t };\n        extend(previous, at);\n        previous = at;\n      }\n      extend(previous, b);\n    }\n\n    if (current) pieces.push(current);\n    return pieces;\n  }\n\n  /**\n   * Project a profile into one LINE path per slope class present.\n   *\n   * NO AREAS. The region beneath the curve is one shape in the route's\n   * own colour, built by `buildPaths` and drawn whether the track has\n   * been sampled or not \u2014 see the module comment on why a per-class tint\n   * was built and then taken back out.\n   *\n   * GROUPED BY CLASS, NOT ONE ELEMENT PER SEGMENT. A 16 km tour is about\n   * 640 sampled segments, and a popup that appended 640 `<path>` nodes to\n   * draw one curve would be paying for the colouring in layout rather\n   * than in ink. Every piece of the same class joins one path with as\n   * many subpaths as it has pieces, so the chart is at most seven\n   * elements however long the tour.\n   *\n   * Falls back to nothing \u2014 an empty list, and the caller draws the plain\n   * curve \u2014 whenever the colouring cannot be placed honestly: an\n   * unsampled route, a malformed record, or a track with no length to\n   * spread the bands along.\n   *\n   * @param {object} profile A `readProfile` result.\n   * @param {?object} slope The route feature's `slope` property.\n   * @param {{width: number, height: number}} [geometry] User-space box.\n   *   Defaults to `VIEWBOX`.\n   * @returns {Array<{classIndex: ?number, token: string, dashed: boolean,\n   *   line: string}>} One entry per class present, gentlest first with the\n   *   unknown treatment last. `token` is the `@theme` custom property the\n   *   class is painted with \u2014 the chart reads the palette from the same\n   *   place the legend swatches do, so the two cannot drift apart.\n   */\n  function buildSlopePaths(profile, slope, geometry) {\n    var core = self.pwaRouteSlopeCore;\n    var box = geometry || VIEWBOX;\n    if (!core || !profile || !profile.hasElevation || !profile.distanceM) return [];\n\n    var bands = slopeBands(slope);\n    if (!bands.length) return [];\n\n    var scales = makeScales(profile, box);\n\n    // Keyed by class index, with the unknown under its own key: a null\n    // must never share a bucket with class 0, which is the gentle one.\n    var collected = {};\n    for (var r = 0; r < profile.runs.length; r += 1) {\n      var run = profile.runs[r];\n      if (run.length < 2) continue;\n\n      var pieces = splitRun(run, bands, profile.distanceM);\n      for (var p = 0; p < pieces.length; p += 1) {\n        var piece = pieces[p];\n        var key = piece.classIndex === null ? 'unknown' : String(piece.classIndex);\n        if (!collected[key]) collected[key] = [];\n        collected[key].push(projectPoints(piece.points, scales).line);\n      }\n    }\n\n    var paths = [];\n    for (var c = 0; c < core.CLASSES.length; c += 1) {\n      var held = collected[String(c)];\n      if (!held) continue;\n      paths.push({\n        classIndex: c,\n        token: core.CLASSES[c].token,\n        dashed: false,\n        line: held.join(' '),\n      });\n    }\n    // Last, and always last: the unknown treatment is not a step on the\n    // scale and is not sorted into it.\n    if (collected.unknown) {\n      paths.push({\n        classIndex: null,\n        token: core.UNKNOWN_TOKEN,\n        dashed: true,\n        line: collected.unknown.join(' '),\n      });\n    }\n    return paths;\n  }\n\n  /**\n   * Build the profile `<svg>` element, or null when there is nothing to draw.\n   *\n   * Colour comes from `currentColor` against a design token\n   * (`text-route-line`, the same `--color-route-line` the map draws the\n   * track with), so the chart and the line on the map are self-evidently\n   * the same object, and both follow the theme. The baseline rule takes a\n   * token of its own.\n   *\n   * WITH A SLOPE RECORD the CURVE is painted by class instead, in the\n   * same palette the map line and the legend swatches use. The region\n   * beneath it stays the route's own colour either way \u2014 only its\n   * opacity changes, to keep a fuchsia ground from shouting over a\n   * class-coloured line. A route that has never been sampled carries no\n   * record and is drawn exactly as it was before SNOW-960: an unsampled\n   * route is not an unknown one, and must not be coloured as though\n   * someone looked.\n   *\n   * The element is inert to assistive tech beyond its label: a decorative\n   * curve with no readable structure is worse as a traversable tree than\n   * as one labelled image, and the figures it illustrates are already in\n   * the popup as text right beside it. That holds for the colouring too \u2014\n   * it restates the shape already drawn, and the steepness it encodes is\n   * the map's to explain, not this chart's.\n   *\n   * @param {object} profile A `readProfile` result.\n   * @param {{label?: string, slope?: object, doc?: Document}} [options]\n   *   `label` becomes the accessible name \u2014 the caller supplies it\n   *   already translated (`pwaStrings`), because this module has no\n   *   catalogue of its own. `slope` is the route feature's `slope`\n   *   property, absent for a route that has never been sampled. `doc`\n   *   overrides the document, for tests.\n   * @returns {SVGElement|null} The chart, or null when the track carries\n   *   no drawable elevation.\n   */\n  function createProfileSvg(profile, options) {\n    var opts = options || {};\n    var doc = opts.doc || (typeof document !== 'undefined' ? document : null);\n    if (!doc) return null;\n\n    var paths = buildPaths(profile);\n    if (!paths.length) return null;\n\n    var NS = 'http://www.w3.org/2000/svg';\n    var svg = doc.createElementNS(NS, 'svg');\n    svg.setAttribute('viewBox', '0 0 ' + VIEWBOX.width + ' ' + VIEWBOX.height);\n    svg.setAttribute('role', 'img');\n    svg.setAttribute('class', 'mt-1.5 block h-auto w-full text-route-line');\n    if (opts.label) svg.setAttribute('aria-label', opts.label);\n\n    // The floor the areas sit on, drawn first so the fill overlaps it.\n    var baseline = doc.createElementNS(NS, 'line');\n    baseline.setAttribute('x1', '0');\n    baseline.setAttribute('x2', String(VIEWBOX.width));\n    baseline.setAttribute('y1', String(VIEWBOX.height - PAD_Y));\n    baseline.setAttribute('y2', String(VIEWBOX.height - PAD_Y));\n    baseline.setAttribute('stroke', 'currentColor');\n    baseline.setAttribute('stroke-width', '1');\n    baseline.setAttribute('class', 'text-border');\n    svg.appendChild(baseline);\n\n    var slopePaths = buildSlopePaths(profile, opts.slope, VIEWBOX);\n    var coloured = slopePaths.length > 0;\n\n    // THE REGION FIRST, AND IT IS THE SAME REGION EITHER WAY: one shape\n    // per elevation run, in the route's own colour, from the projection\n    // `buildPaths` already returned. Only its strength answers to the\n    // curve above it.\n    for (var i = 0; i < paths.length; i += 1) {\n      var area = doc.createElementNS(NS, 'path');\n      area.setAttribute('d', paths[i].area);\n      area.setAttribute('fill', 'currentColor');\n      area.setAttribute(\n        'fill-opacity', String(coloured ? SLOPE_AREA_OPACITY : AREA_OPACITY),\n      );\n      area.setAttribute('stroke', 'none');\n      svg.appendChild(area);\n    }\n\n    // Then the curve \u2014 in the slope classes where the ground has been\n    // sampled, and in the route's colour where it has not.\n    var lines = coloured\n      ? slopePaths\n      : paths.map(function (path) {\n        return { token: null, dashed: false, line: path.line };\n      });\n\n    for (var j = 0; j < lines.length; j += 1) {\n      // `var(--token)` in a presentation attribute, which is a CSS\n      // property and resolves like one. The ds-lint hex rule's carve-out\n      // for MapLibre paint does not apply here: an SVG in the page CAN\n      // read a custom property, so it reads the same one the legend\n      // swatch is painted with rather than holding a copy of the value.\n      var colour = lines[j].token ? 'var(' + lines[j].token + ')' : 'currentColor';\n\n      var line = doc.createElementNS(NS, 'path');\n      line.setAttribute('d', lines[j].line);\n      line.setAttribute('fill', 'none');\n      line.setAttribute('stroke', colour);\n      line.setAttribute('stroke-width', String(STROKE_WIDTH));\n      line.setAttribute('stroke-linejoin', 'round');\n      line.setAttribute('stroke-linecap', 'round');\n      if (lines[j].dashed) {\n        line.setAttribute(\n          'stroke-dasharray',\n          UNKNOWN_DASH.map(function (part) {\n            return String(part * STROKE_WIDTH);\n          }).join(' '),\n        );\n      }\n      svg.appendChild(line);\n    }\n\n    return svg;\n  }\n\n  self.pwaElevationProfileCore = Object.freeze({\n    haversineM: haversineM,\n    readProfile: readProfile,\n    buildPaths: buildPaths,\n    slopeBands: slopeBands,\n    buildSlopePaths: buildSlopePaths,\n    createProfileSvg: createProfileSvg,\n    VIEWBOX: VIEWBOX,\n  });\n})();\n"],
  "mappings": "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",
  "names": ["EARTH_RADIUS_M", "VIEWBOX", "PAD_Y", "STROKE_WIDTH", "AREA_OPACITY", "SLOPE_AREA_OPACITY", "UNKNOWN_DASH", "haversineM", "lon1", "lat1", "lon2", "lat2", "phi1", "phi2", "deltaPhi", "deltaLambda", "a", "readProfile", "coordinates", "empty", "runs", "current", "distance", "minEle", "maxEle", "previous", "i", "point", "lon", "lat", "ele", "makeScales", "profile", "box", "range", "floor", "usable", "d", "e", "projectPoints", "points", "scales", "line", "x", "y", "startX", "endX", "base", "buildPaths", "geometry", "paths", "run", "slopeBands", "slope", "core", "angles", "count", "bands", "index", "last", "cutsWithin", "startD", "endD", "distanceM", "cuts", "at", "splitRun", "pieces", "classAt", "fraction", "extend", "from", "to", "b", "j", "span", "t", "buildSlopePaths", "collected", "r", "p", "piece", "key", "c", "held", "createProfileSvg", "options", "opts", "doc", "NS", "svg", "baseline", "slopePaths", "coloured", "area", "lines", "path", "colour", "part"]
}
