{
  "version": 3,
  "sources": ["map_viewport_core.js"],
  "sourcesContent": ["/*\n * static/js/map_viewport_core.js \u2014 the map camera the visitor left behind,\n * and the validation that decides whether it is still usable.\n *\n * SNOW-737. Every other piece of map chrome already survives a cold boot \u2014\n * the basemap under `snowdesk.map.basemap`, every overlay under the\n * `snowdesk.map.overlay.*` table \u2014 so a returning visitor got their layers\n * and their basemap back and then had to pan and zoom to their own mountains\n * again. The camera was the one thing thrown away, because `map.js` handed\n * the MapLibre constructor a hardcoded frame around Switzerland.\n *\n * WHY THE VALIDATION IS THE INTERESTING HALF. A stored camera is not data\n * this codebase produced this week: it was written by whatever build the\n * visitor last ran, and the camera limits are constructor options that have\n * been retuned before \u2014 SNOW-442 moved `maxZoom` from 12 to 18, and\n * `maxBounds` east went from 17\u00B0 to 23\u00B0 when the Austrian and Slovenian arc\n * was brought into frame. So a viewport written months ago can name a zoom\n * or a centre the current map will not accept.\n *\n * MapLibre would absorb most of that silently: `maxBounds` clamps the centre\n * without complaint, and an out-of-range zoom is pinned to the nearest limit.\n * Silent absorption is exactly the failure worth avoiding \u2014 it turns \"your\n * stored viewport is stale\" into \"the map opened somewhere you have never\n * been\", with nothing anywhere to say why. `restore` therefore rejects\n * outright and returns null, and the caller falls back to the default frame,\n * which is a place the visitor at least recognises.\n *\n * The five numbers are stored as ONE JSON blob rather than five keys because\n * they are only meaningful together. A half-written camera \u2014 a longitude from\n * this session and a zoom from the last \u2014 is worse than no camera at all, and\n * five independent keys make that state representable.\n *\n * Exports (frozen `self.pwaViewportCore`):\n *\n *   serialise(camera)      \u2014 {lng, lat, zoom, bearing, pitch} \u2192 string\n *   restore(raw, limits)   \u2014 string \u2192 a MapLibre camera, or null\n */\n\n// @ts-check\n\n(function () {\n  'use strict';\n\n  /**\n   * Serialise a camera for storage.\n   *\n   * Takes the five numbers rather than a MapLibre `Map`, so that both this\n   * function and its test are free of the library: the caller in `map.js`\n   * does the `getCenter()` / `getZoom()` reading, which is the part there is\n   * nothing to get wrong about.\n   *\n   * @param {{lng: number, lat: number, zoom: number, bearing: number,\n   *          pitch: number}} camera - the current view.\n   * @returns {string} A JSON string suitable for localStorage.\n   */\n  function serialise(camera) {\n    return JSON.stringify({\n      lng: camera.lng,\n      lat: camera.lat,\n      zoom: camera.zoom,\n      bearing: camera.bearing,\n      pitch: camera.pitch,\n    });\n  }\n\n  /**\n   * Whether a value is a number that can be used as a coordinate.\n   *\n   * `typeof NaN` is 'number' and `JSON.parse` will hand back null, strings\n   * and objects for any of the five fields, so both halves are load-bearing.\n   *\n   * @param {*} value - the parsed field.\n   * @returns {boolean} True when it is a finite number.\n   */\n  function isFiniteNumber(value) {\n    return typeof value === 'number' && Number.isFinite(value);\n  }\n\n  /**\n   * Parse and validate a stored camera against the map's current limits.\n   *\n   * Returns null \u2014 never a partially-trusted camera \u2014 whenever anything is\n   * wrong: absent, malformed, missing a field, non-finite, or naming a zoom\n   * or centre outside what this build of the map allows. The caller treats\n   * null as \"no stored viewport\" and uses the default frame.\n   *\n   * `bearing` and `pitch` are validated for finiteness but not for range:\n   * MapLibre normalises bearing modulo 360 and clamps pitch to its own\n   * `maxPitch`, and neither can put the visitor somewhere unrecognisable the\n   * way a bad centre can. Rejecting a whole camera over a pitch of 61\u00B0 would\n   * throw away a good position for a field that cannot mislead.\n   *\n   * @param {?string} raw - the stored string, or null when the key is absent.\n   * @param {{minZoom: number, maxZoom: number,\n   *          maxBounds: number[][]}} limits - the map's constructor limits,\n   *     `maxBounds` as MapLibre's [[west, south], [east, north]].\n   * @returns {?{center: number[], zoom: number, bearing: number,\n   *             pitch: number}} A camera, or null.\n   */\n  function restore(raw, limits) {\n    if (!raw) return null;\n\n    let stored;\n    try {\n      stored = JSON.parse(raw);\n    } catch (_) {\n      return null;\n    }\n    // A JSON document can legitimately be a number, a string or null, none of\n    // which have the fields below \u2014 reading `.lng` off them would throw for\n    // null and yield undefined for the rest.\n    if (!stored || typeof stored !== 'object') return null;\n\n    const { lng, lat, zoom, bearing, pitch } = stored;\n    if (![lng, lat, zoom, bearing, pitch].every(isFiniteNumber)) return null;\n\n    if (zoom < limits.minZoom || zoom > limits.maxZoom) return null;\n\n    const [[west, south], [east, north]] = limits.maxBounds;\n    if (lng < west || lng > east || lat < south || lat > north) return null;\n\n    return { center: [lng, lat], zoom: zoom, bearing: bearing, pitch: pitch };\n  }\n\n  self.pwaViewportCore = Object.freeze({\n    serialise: serialise,\n    restore: restore,\n  });\n})();\n"],
  "mappings": "CAwCC,UAAY,CACX,aAcA,SAASA,EAAUC,EAAQ,CACzB,OAAO,KAAK,UAAU,CACpB,IAAKA,EAAO,IACZ,IAAKA,EAAO,IACZ,KAAMA,EAAO,KACb,QAASA,EAAO,QAChB,MAAOA,EAAO,KAChB,CAAC,CACH,CAWA,SAASC,EAAeC,EAAO,CAC7B,OAAO,OAAOA,GAAU,UAAY,OAAO,SAASA,CAAK,CAC3D,CAuBA,SAASC,EAAQC,EAAKC,EAAQ,CAC5B,GAAI,CAACD,EAAK,OAAO,KAEjB,IAAIE,EACJ,GAAI,CACFA,EAAS,KAAK,MAAMF,CAAG,CACzB,MAAY,CACV,OAAO,IACT,CAIA,GAAI,CAACE,GAAU,OAAOA,GAAW,SAAU,OAAO,KAElD,KAAM,CAAE,IAAAC,EAAK,IAAAC,EAAK,KAAAC,EAAM,QAAAC,EAAS,MAAAC,CAAM,EAAIL,EAG3C,GAFI,CAAC,CAACC,EAAKC,EAAKC,EAAMC,EAASC,CAAK,EAAE,MAAMV,CAAc,GAEtDQ,EAAOJ,EAAO,SAAWI,EAAOJ,EAAO,QAAS,OAAO,KAE3D,KAAM,CAAC,CAACO,EAAMC,CAAK,EAAG,CAACC,EAAMC,CAAK,CAAC,EAAIV,EAAO,UAC9C,OAAIE,EAAMK,GAAQL,EAAMO,GAAQN,EAAMK,GAASL,EAAMO,EAAc,KAE5D,CAAE,OAAQ,CAACR,EAAKC,CAAG,EAAG,KAAMC,EAAM,QAASC,EAAS,MAAOC,CAAM,CAC1E,CAEA,KAAK,gBAAkB,OAAO,OAAO,CACnC,UAAWZ,EACX,QAASI,CACX,CAAC,CACH,GAAG",
  "names": ["serialise", "camera", "isFiniteNumber", "value", "restore", "raw", "limits", "stored", "lng", "lat", "zoom", "bearing", "pitch", "west", "south", "east", "north"]
}
