{
  "_comment": "All styling and camera config for the Larnaca map. Hand-editable. Two independent axes: theme (light|dark) restyles the chrome and the vector map; basemap (map|satellite) swaps the ground. Brand values come from the Patsias Developments company profile - Montserrat; navy #103050, brand blue #1848a0, light blue #5080c8, sand #b8a898. Road red is the client's explicit ask.",
  "baseStyle": "https://tiles.openfreemap.org/styles/liberty",
  "defaultTheme": "light",
  "defaultBasemap": "satellite",
  "_interactiveComment": "One colour is reserved for things you can press, per theme. Nothing decorative may use it - that is the whole point.",
  "basemaps": {
    "map": {
      "label": "Map",
      "hint": "Drawn map with named streets"
    },
    "satellite": {
      "label": "Satellite",
      "hint": "Aerial imagery",
      "tiles": [
        "https://services.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}?blankTile=false"
      ],
      "tileSize": 256,
      "_maxzoomComment": "Esri serves real z18 and z19 tiles over the coast (probed 25 Sep 2026 over Finikoudes: 21 and 19 KB JPEGs, not the blank placeholder). The hero tier frames at z16-17 where the extra levels are the difference between a photo and a blur; the request count only grows at zooms the standard camera rarely reaches.",
      "maxzoom": 19,
      "attribution": "Imagery &copy; Esri, Maxar, Earthstar Geographics and the GIS User Community",
      "_licence": "Esri World Imagery is keyless and free to use with the attribution above. CONFIRM COMMERCIAL TERMS WITH ESRI BEFORE LAUNCH. Fully-open alternative (CC-BY 4.0, but 10 m resolution so it blurs past zoom 15): https://tiles.maps.eox.at/wmts/1.0.0/s2cloudless-2020_3857/default/g/{z}/{y}/{x}.jpg with attribution 'Sentinel-2 cloudless by EOX (CC BY 4.0)'."
    }
  },
  "themes": {
    "light": {
      "palette": {
        "primary": "#1848a0",
        "primaryDeep": "#103050",
        "accent": "#1848a0",
        "sand": "#75634f",
        "_sandNote": "The profile's sand is #b8a898. On white UI that fails 4.5:1 for the small uppercase labels it is used on, so the light theme carries a darkened version of the same hue; dark keeps the original.",
        "ink": "#0e2139",
        "inkMuted": "#5a6b80",
        "roadMajor": "#2f9e63",
        "roadMajorCasing": "#ffffff",
        "roadSecondary": "#c9d2dc",
        "roadSecondaryCasing": "#ffffff",
        "roadMinor": "#c9d2dc",
        "water": "#aecde3",
        "landDim": "#eef1f5"
      },
      "labels": {
        "color": "#22405f",
        "halo": "#ffffff",
        "haloWidth": 1.6
      },
      "buildings3d": {
        "color": "#d7dfea",
        "colorTall": "#a9bdd6",
        "opacity": 0.9,
        "highlightColor": "#1848a0"
      },
      "routes": {
        "color": "#1848a0",
        "headColor": "#ffffff",
        "casingColor": "#ffffff",
        "glowColor": "#5080c8"
      }
    },
    "dark": {
      "palette": {
        "primary": "#1848a0",
        "primaryDeep": "#103050",
        "accent": "#5080c8",
        "sand": "#b8a898",
        "ink": "#eaf1fa",
        "inkMuted": "#8ea6c4",
        "roadMajor": "#3fbf78",
        "roadMajorCasing": "#0e4a2c",
        "roadSecondary": "#1e3654",
        "roadSecondaryCasing": "#132741",
        "roadMinor": "#1e3654",
        "water": "#04101d",
        "landDim": "#0b2137"
      },
      "labels": {
        "color": "#c3d6ec",
        "halo": "#04101d",
        "haloWidth": 1.4
      },
      "buildings3d": {
        "color": "#123a63",
        "colorTall": "#2f6bb0",
        "opacity": 0.92,
        "highlightColor": "#5b9bf0"
      },
      "routes": {
        "color": "#63a4ff",
        "headColor": "#ffffff",
        "casingColor": "#08192b",
        "glowColor": "#5080c8"
      },
      "satellite": {
        "grade": {
          "saturation": 0.05,
          "contrast": 0.06,
          "brightnessMin": 0,
          "brightnessMax": 0.72,
          "hueRotate": 0
        }
      }
    }
  },
  "_satelliteComment": "Over imagery the drawn ground (landuse/water fills) is hidden, labels take a heavier halo, and the imagery itself is graded - saturation up, brightness slightly down - so the blue interactive chrome reads clearly on top. Buildings render as translucent grey blocks; project massing keeps the highlight.",
  "satellite": {
    "labels": {
      "color": "#ffffff",
      "halo": "#1a2430",
      "haloWidth": 1.9
    },
    "roadOpacity": 0.95,
    "hideGenericBuildings": false,
    "buildingColor": "#a3b1bf",
    "buildingColorTall": "#c3ced9",
    "buildingOpacity": 0.5,
    "grade": {
      "saturation": 0.22,
      "contrast": 0.08,
      "brightnessMin": 0.03,
      "brightnessMax": 0.92,
      "hueRotate": 0
    },
    "_loadingComment": "Fresh pans at deep zoom used to flash the pale drawn map until sharp tiles arrived, tile by tile. Two fixes: a coarse underlay (same imagery capped at underlayMaxzoom - those few tiles are always cached from the overview) gives instant blurry-real ground that sharpens; and voidColor darkens the ground beneath the imagery so anything still loading reads as shadow, not a white map flash.",
    "underlayMaxzoom": 13,
    "voidColor": "#1b2733",
    "_gradeComment": "Colour grading for the Esri imagery, applied as raster paint on both the sharp layer and the coarse underlay. These are the LIGHT values; themes.dark.satellite.grade overrides them. brightnessMin lifts the blacks the way atmospheric haze does in an aerial photograph, so raised contrast does not crush the shadows. hueRotate is an unused knob kept for a future imagery source."
  },
  "_models3dComment": "Real architect models (delivered as ArchiCAD/SketchUp exports, converted to meshopt GLB by scripts/blender_convert.py + gltfpack). Each is anchored by footprint center at lngLat, grounded at its lowest vertex, rotated by rotateDeg (clockwise from north, tuned against the satellite footprint). origin is the shared scene origin - do not move it once models are aligned, all placements are relative to the projection at that latitude.",
  "models3d": {
    "origin": [
      33.641,
      34.946
    ],
    "light": {
      "sky": "#e8f1ff",
      "ground": "#c9bda8",
      "sun": "#fff4e0",
      "hemiIntensity": 1.35,
      "sunIntensity": 2,
      "_sunNote": "From the south-east, matching the shadows baked into the Esri imagery (they fall NW) - two shadow directions in one scene is an instant fake tell.",
      "sunPosition": [
        700,
        -550,
        1100
      ],
      "exposure": 1.15,
      "_exposureNote": "ACES filmic tone mapping on the Three.js renderer (js/models3d.js). Sunlit faces used to clip at white and flatten into poster colour; the filmic curve rolls them off into highlight detail. It compresses the midtones too, hence the lifted hemi/sun intensities below."
    },
    "_placementNote": "lngLat may differ from the project pin: it anchors the model on the actual construction footprint read off the satellite imagery (the Sirens pin, for instance, sits by the road while the buildings rise NW of it). rotateDeg tuned by eye against plot boundaries and visible skeletons.",
    "models": [
      {
        "id": "larnaca-castle",
        "file": "assets/models/larnaca-castle.glb",
        "lngLat": [33.6376827, 34.9102827],
        "rotateDeg": 174.4,
        "color": "#d8c29a",
        "dropLines": true,
        "flood": true,
        "offParam": "fort=old",
        "_fit": "Larnaca Castle 3D by M.A. StruTec Engineers (Sketchfab, CC BY 4.0; Zenodo 10298508), a DXF survey in metres. Footprint fitted to the OSM fort ring, IoU 0.944 (scripts/fit-castle.py); the half-turn is read off the DLS orthophoto: rampart to the sea, roofed range to the town. gltfpack -cc: 2.4 MB -> 264 KB."
      },
      {
        "id": "artemis",
        "file": "assets/models/artemis.glb",
        "lngLat": [
          33.6460266,
          34.950166
        ],
        "rotateDeg": -22,
        "sinkM": 0.9
      },
      {
        "id": "hermes",
        "file": "assets/models/hermes.glb",
        "lngLat": [
          33.6454642,
          34.9536067
        ],
        "rotateDeg": -21,
        "sinkM": 22.6,
        "_fit": "rotation and position fitted 26 Sep 2026 from the developer's bmby scene (context matched to buildings, score 0.785)"
      },
      {
        "id": "sirens",
        "file": "assets/models/sirens.glb",
        "lngLat": [
          33.6388859,
          34.9443033
        ],
        "rotateDeg": -6,
        "sinkM": 1.7,
        "_fit": "rotation and position fitted 26 Sep 2026 from the developer's bmby scene (context matched to roads, score 0.687)"
      },
      {
        "id": "nereids",
        "file": "assets/models/nereids.glb",
        "lngLat": [
          33.6344574,
          34.9370568
        ],
        "rotateDeg": -24,
        "sinkM": 13,
        "cropM": true,
        "cropMarginM": 18,
        "_fit": "rotation and position fitted 26 Sep 2026 from the developer's bmby scene (context matched to roads, score 0.72)"
      },
      {
        "id": "saint-savvas",
        "file": "assets/models/saint-savvas.glb",
        "lngLat": [
          33.6347844,
          34.9476966
        ],
        "rotateDeg": 0.25,
        "georef": true,
        "clearRadius": 28,
        "_source": "Developer's bmby 3D viewer (saint-savvas.dreams.bmby.com): building + landscape extracted from bundle.glb, baked AO textures. Origin/rotation fitted by matching the bmby context buildings to DLS footprints (50% of vertices within 3 m)."
      }
    ],
    "_tileNote": "The 46 MB photogrammetry tile (larnaca-tile-12210313133212.glb, 128 px textures, 1,089 draw calls) is out of the hero fork: it is a reference for the styling pass, not part of the look. Its 1,420 m clear zone went with it, so data/trees.json is regenerated without the hole."
  },
  "_salesComment": "Unit sales (branch nspire, internal demo). Each project id has an nspire content package in data/projects/<id>/ (manifest, inventory, config) and its unit volumes in scene/units.json (scripts/extract-bmby-units.py + scripts/units-bind.mjs). The volumes are the developer's bmby selection boxes: internal demo only, see data/projects/RIGHTS.md.",
  "sales": {
    "projects": ["sirens"]
  },
  "_landmarksComment": "Third-party area developments shown as GHOST volumes - translucent schematic prisms, deliberately non-architectural because no masterplan geometry was provided. Facts live in the matching municipal-planned POIs; every popup marks volumes as indicative. Heights = published floor counts x 3.1 m. greenery = the future-park treatment over the Land of Tomorrow zone (the old oil-tank site): polygon traced against E301/B3 on the imagery, matching the red area marked by the client.",
  "landmarks3d": {
    "_volumesNote": "false: no schematic prisms for the third-party developments in any tier (grey blocks on the skyline; owner asked to remove them for now, 27 Sep 2026). Pins and popups stay.",
    "volumes": false,
    "volumeColor": "#5080c8",
    "volumeOpacity": 0.32,
    "edgeColor": "#dbe7f7",
    "edgeOpacity": 0.85,
    "landmarks": [
      {
        "id": "land-of-tomorrow",
        "lngLat": [
          33.649,
          34.94924
        ],
        "rotDeg": 20,
        "volumes": [
          {
            "lngLat": [
              33.6493,
              34.9515
            ],
            "w": 26,
            "d": 20,
            "h": 46
          },
          {
            "lngLat": [
              33.6488,
              34.9502
            ],
            "w": 26,
            "d": 20,
            "h": 25
          },
          {
            "lngLat": [
              33.6478,
              34.948
            ],
            "w": 26,
            "d": 20,
            "h": 37
          },
          {
            "lngLat": [
              33.6469,
              34.9461
            ],
            "w": 26,
            "d": 20,
            "h": 25
          },
          {
            "lngLat": [
              33.6461,
              34.9447
            ],
            "w": 26,
            "d": 20,
            "h": 22
          }
        ]
      },
      {
        "id": "aqua-residences",
        "lngLat": [
          33.65002,
          34.95196
        ],
        "rotDeg": -30,
        "volumes": [
          {
            "dx": -18,
            "w": 22,
            "d": 30,
            "h": 62
          },
          {
            "dx": 18,
            "w": 22,
            "d": 30,
            "h": 62
          }
        ]
      },
      {
        "id": "lumina-mare",
        "lngLat": [
          33.637893,
          34.938902
        ],
        "rotDeg": -35,
        "volumes": [
          {
            "w": 26,
            "d": 34,
            "h": 55
          }
        ]
      },
      {
        "id": "horizon",
        "lngLat": [
          33.6377019,
          34.9399646
        ],
        "rotDeg": -35,
        "volumes": [
          {
            "dx": -60,
            "dy": 32,
            "w": 22,
            "d": 22,
            "h": 50
          },
          {
            "dx": 0,
            "dy": 38,
            "w": 22,
            "d": 22,
            "h": 56
          },
          {
            "dx": 60,
            "dy": 44,
            "w": 22,
            "d": 22,
            "h": 62
          },
          {
            "dx": -55,
            "dy": 95,
            "w": 22,
            "d": 22,
            "h": 62
          },
          {
            "dx": 5,
            "dy": 101,
            "w": 22,
            "d": 22,
            "h": 56
          },
          {
            "dx": 65,
            "dy": 107,
            "w": 22,
            "d": 22,
            "h": 50
          }
        ]
      }
    ],
    "greenery": {
      "groundColor": "#6da45e",
      "groundOpacity": 0.24,
      "treeColor": "#4e7a45",
      "treeEveryM2": 260,
      "maxTrees": 900,
      "_polygonNote": "Traced between the B3 (west side) and the shoreline (east side), from the B3/E301 roundabout down to the port pier - the old oil-tank pads are visible on the imagery inside it.",
      "polygon": [
        [
          33.6485,
          34.9541
        ],
        [
          33.6461,
          34.9513
        ],
        [
          33.6443,
          34.9486
        ],
        [
          33.6429,
          34.9459
        ],
        [
          33.6424,
          34.944
        ],
        [
          33.6461,
          34.9432
        ],
        [
          33.6467,
          34.9449
        ],
        [
          33.6479,
          34.9466
        ],
        [
          33.6494,
          34.9487
        ],
        [
          33.6503,
          34.9511
        ],
        [
          33.6502,
          34.9533
        ]
      ]
    }
  },
  "_surroundingsComment": "The Three.js city around the architect models: extruded OSM footprints (data/osm-buildings.json, fetched at build time by scripts/fetch-osm-3d.mjs) and instanced trees seeded from OSM greenery. Replaces the flat vector extrusions whenever Buildings = 3D massing. clearRadius keeps mapped remnants from poking through the real models.",
  "surroundings3d": {
    "buildingColor": "#d3ccbf",
    "darkTint": "#7d93b5",
    "treeColor": "#4e7a45",
    "clearRadius": 70,
    "treeEveryM2": 1600,
    "chunkMeters": 800,
    "lightTint": "#fbf6ee",
    "_tintNote": "One multiplier over the baked vertex colours of the OSM city and trees: a warm off-white in light theme so the extruded city sits in the same light as the graded imagery, a cool blue-grey (darkTint) at night."
  },
  "labels": {
    "_comment": "Shared label options merged under every theme. boostPlaces pulls suburb/village names in earlier and larger so each area reads named at the home zoom.",
    "boostPlaces": {
      "minzoom": 10.5,
      "sizeFactor": 1.25
    }
  },
  "camera": {
    "home": {
      "center": [
        33.6435,
        34.9235
      ],
      "zoom": 12.7,
      "pitch": 48,
      "bearing": -20
    },
    "topDown": {
      "pitch": 0,
      "bearing": 0
    },
    "intro": {
      "from": {
        "center": [
          33.3,
          35.05
        ],
        "zoom": 7.2,
        "pitch": 0,
        "bearing": 0
      },
      "durationMs": 4500
    },
    "projectZoom": 15.4,
    "_maxZoomComment": "Esri imagery is native to z19 here (see basemaps.satellite); the camera stops half a level short of it so the last tiles never overzoom.",
    "maxZoom": 18.5,
    "fadeDurationMs": 120,
    "maxBounds": [
      [
        33.45,
        34.76
      ],
      [
        33.92,
        35.1
      ]
    ]
  },
  "_roadsComment": "Liberty splits every road class three ways by brunnel (surface / bridge / tunnel) and pulls motorway ramps into their own layers. All variants must be listed or a junction shows a red motorway with orange ramps, and a bridged section changes colour mid-road. applyStyleOverrides warns in the console about ids that do not exist in the base style.",
  "roads": {
    "major": [
      "road_motorway",
      "bridge_motorway",
      "tunnel_motorway",
      "road_trunk_primary",
      "bridge_trunk_primary",
      "tunnel_trunk_primary",
      "road_motorway_link",
      "bridge_motorway_link",
      "tunnel_motorway_link"
    ],
    "majorCasing": [
      "road_motorway_casing",
      "bridge_motorway_casing",
      "tunnel_motorway_casing",
      "road_trunk_primary_casing",
      "bridge_trunk_primary_casing",
      "tunnel_trunk_primary_casing",
      "road_motorway_link_casing",
      "bridge_motorway_link_casing",
      "tunnel_motorway_link_casing"
    ],
    "secondary": [
      "road_secondary_tertiary",
      "bridge_secondary_tertiary",
      "tunnel_secondary_tertiary"
    ],
    "secondaryCasing": [
      "road_secondary_tertiary_casing",
      "bridge_secondary_tertiary_casing",
      "tunnel_secondary_tertiary_casing"
    ],
    "minor": [
      "road_minor",
      "tunnel_minor",
      "bridge_street",
      "road_link",
      "tunnel_link",
      "bridge_link",
      "road_service_track",
      "tunnel_service_track",
      "bridge_service_track",
      "road_path_pedestrian",
      "tunnel_path_pedestrian",
      "bridge_path_pedestrian",
      "road_minor_casing",
      "tunnel_street_casing",
      "bridge_street_casing",
      "road_link_casing",
      "tunnel_link_casing",
      "bridge_link_casing",
      "road_service_track_casing",
      "tunnel_service_track_casing",
      "bridge_service_track_casing",
      "bridge_path_pedestrian_casing"
    ],
    "minorOpacity": 0.45
  },
  "buildings3d": {
    "enabled": true,
    "sourceLayer": "building",
    "tallAtM": 40,
    "massingOpacity": 0.95,
    "projectEmphasisRadiusM": 55,
    "projectMinHeightM": 26,
    "minzoom": 13,
    "massingMinzoom": 11
  },
  "routes": {
    "_comment": "Animated drive routes. Geometry is precomputed into data/routes.json by scripts/compute-routes.mjs - nothing is routed at runtime.",
    "width": 4.5,
    "casingWidth": 10,
    "glowWidth": 22,
    "revealMs": 1400,
    "flowMs": 2600,
    "revealFps": 30,
    "flowFps": 20,
    "flowMaxMs": 25000,
    "cameraMs": 900,
    "maxZoom": 15.5,
    "pitch": 40
  },
  "poiCategories": {
    "_labelRuComment": "labelEl and labelRu keep category names admin-extendable: a category without one falls back to its English label instead of a missing dictionary key.",
    "transport": {
      "label": "Transport",
      "labelRu": "Транспорт",
      "labelEl": "Μεταφορές",
      "color": "#2f6bb0",
      "colorDark": "#5b9bf0",
      "icon": "plane"
    },
    "food": {
      "label": "Cafes & dining",
      "labelRu": "Кафе и рестораны",
      "labelEl": "Καφέ και εστιατόρια",
      "color": "#b3541e",
      "colorDark": "#f09a5e",
      "icon": "cafe"
    },
    "beach": {
      "label": "Beaches",
      "labelRu": "Пляжи",
      "labelEl": "Παραλίες",
      "color": "#12897c",
      "colorDark": "#38c0b0",
      "icon": "beach"
    },
    "education": {
      "label": "Schools",
      "labelRu": "Школы",
      "labelEl": "Σχολεία",
      "color": "#7250c8",
      "colorDark": "#a98cf0",
      "icon": "school"
    },
    "health": {
      "label": "Health",
      "labelRu": "Медицина",
      "labelEl": "Υγεία",
      "color": "#c94f4f",
      "colorDark": "#f07b7b",
      "icon": "health"
    },
    "shopping": {
      "label": "Shopping",
      "labelRu": "Шопинг",
      "labelEl": "Αγορές",
      "color": "#a97a2e",
      "colorDark": "#d9b072",
      "icon": "cart"
    },
    "leisure": {
      "label": "Leisure",
      "labelRu": "Досуг",
      "labelEl": "Αναψυχή",
      "color": "#2e9b5c",
      "colorDark": "#5fd48a",
      "icon": "star"
    },
    "municipal-planned": {
      "label": "City projects",
      "labelRu": "Городские проекты",
      "labelEl": "Αστικά έργα",
      "color": "#647a94",
      "colorDark": "#8fa6c0",
      "icon": "planned"
    }
  },
  "_storyComment": "Guided tour. One stop per id in order (intro and outro stops are built in). Camera anchors come from models3d entries when present, else the project pin. Bearings tuned per stop so each model shows its best face; dwellMs is the auto-advance timer, rotateDegPerSec the slow drift while dwelling.",
  "story": {
    "zoom": 16.1,
    "pitch": 57,
    "flyMs": 3800,
    "dwellMs": 12000,
    "rotateDegPerSec": 1.4,
    "placeDwellMs": 9000,
    "_chaptersNote": "After the projects the tour walks the destination chapters (js/chapters.js tourStops): neighbourhood ids and beach POI ids from data/chapters.json; future takes plan-zones, plan-transport and planned-project POI ids (Dmitry, 28 Sep 2026: seller-led tour).",
    "chapters": {
      "hoods": ["livadia"],
      "seaside": ["finikoudes", "mackenzie-beach"],
      "future": ["plan-zones", "plan-transport", "land-of-tomorrow"]
    },
    "order": [
      "artemis",
      "hermes",
      "sirens",
      "nereids",
      "saint-savvas"
    ],
    "bearings": {
      "artemis": -35,
      "hermes": -18,
      "sirens": -46,
      "nereids": -10,
      "saint-savvas": -26
    }
  },
  "_heroComment": "Hero visual tier (branch hero-visual). Everything the hero renderer needs sits here so the look is tuned in JSON, not code: sun and sky, shadows, grade, and the framed shots the preview opens on. Shots are #m= cameras: zoom / pitch / bearing around a lng,lat centre. Sun is a golden MORNING from the east-south-east - the seafront facades face that way and stay lit; an evening sun would put every one of them in shadow.",
  "hero": {
    "sun": {
      "color": "#ffc27a",
      "intensity": 2.6,
      "direction": [
        940,
        -250,
        310
      ],
      "_directionNote": "Scene metres east, north, up. Azimuth about 105 degrees, elevation about 18 degrees."
    },
    "hemi": {
      "sky": "#b7c9ea",
      "ground": "#cdb48c",
      "intensity": 0.32
    },
    "sky": {
      "turbidity": 6,
      "rayleigh": 1.8,
      "mieCoefficient": 0.006,
      "mieDirectionalG": 0.85
    },
    "environmentIntensity": 0.28,
    "daylight": {
      "_note": "Time of day (js/hero/daylight.js). Keyframes by local hour on 21 Sep; everything between is lerped, the sun direction slerped. Before 5.5 and after 21 = night. Sun directions are scene metres E, N, U from the solar model for 34.905 N 33.635 E; morning holds the values of hero.sun/hemi/fog/exposure verbatim so the default view is unchanged. ?t=21 opens at that hour, &h=21 in the hash shares it.",
      "default": 15.5, "_defaultNote": "15:30 chosen by Dmitry from the golden-hour previews (4 Oct 2026), with hero.atmo haze blending the strip into the city. Before: 11:00, not 7:45: under the low morning sun the detailed strip (lit walls, shade, shadows) went blue-grey and showed a hard edge against the flat-lit city around it (QA 28 Sep 2026); by 10-12 the two match.",
      "tweenMs": 2000,
      "lit": { "warm": "#ffd08a", "cool": "#c4d6ff", "coolShare": 0.28 },
      "keys": [
        { "h": 5.5, "as": "night" },
        { "h": 7.75, "name": "morning",
          "sun": { "dir": [940, -250, 310], "color": "#ffc27a", "intensity": 2.6 },
          "hemi": { "sky": "#b7c9ea", "ground": "#cdb48c", "intensity": 0.32 },
          "env": { "intensity": 0.28, "hdri": "assets/hero/tex/sky-morning.hdr", "meanLum": 0.6, "sunCap": 3 },
          "fog": { "color": "#e9d3b4", "density": 0.00013 }, "exposure": 0.85, "shadowOpacity": 0.42,
          "basemap": { "brightnessMax": 0.92, "saturation": 0.22, "contrast": 0.08 },
          "tint": { "color": "#f0cfa0", "opacity": 0.14 }, "haze": [233, 211, 180],
          "water": {}, "litFrac": 0.0, "glow": 0.0 },
        { "h": 12.5, "name": "midday",
          "sun": { "dir": [20, -566, 824], "color": "#fff4e6", "intensity": 3.0 },
          "hemi": { "sky": "#cfe0ff", "ground": "#d9c7a6", "intensity": 0.45 },
          "env": { "intensity": 0.35 }, "fog": { "color": "#dfe6ee", "density": 0.0001 },
          "exposure": 0.8, "shadowOpacity": 0.38,
          "basemap": { "brightnessMax": 0.95, "saturation": 0.15, "contrast": 0.05 },
          "tint": { "color": "#ffffff", "opacity": 0.0 }, "haze": [206, 228, 241],
          "water": { "skyZenith": "#6fa3e6", "skyHorizon": "#dfe7ee" }, "litFrac": 0.0, "glow": 0.0 },
        { "h": 15.5, "name": "afternoon", "_note": "15:30, Dmitry's pick from the golden-hour previews (4 Oct 2026). Sun at its real early-October position for Larnaca (az 232, el 30), warm-white light, near-neutral haze and almost no tint on the photo, so walls stay white and shadows cool instead of the sepia the evening key gave this hour.",
          "sun": { "dir": [-682, -533, 500], "color": "#ffe4c2", "intensity": 3.1 },
          "hemi": { "sky": "#c8daf7", "ground": "#d8c4a2", "intensity": 0.52 },
          "env": { "intensity": 0.32 },
          "fog": { "color": "#e6e0d6", "density": 0.00012 },
          "exposure": 0.95, "shadowOpacity": 0.38,
          "basemap": { "brightnessMax": 0.97, "saturation": 0.2, "contrast": 0.07 },
          "tint": { "color": "#f5d9b4", "opacity": 0.05 },
          "haze": [226, 222, 214],
          "water": { "glintStrength": 1.9, "glintMax": 2.3 } },
        { "h": 17.5, "name": "evening",
          "sun": { "dir": [-955, -160, 245], "color": "#ffb066", "intensity": 2.2 },
          "hemi": { "sky": "#9fb3d8", "ground": "#c8a37c", "intensity": 0.42 },
          "env": { "intensity": 0.25, "hdri": "assets/hero/tex/sky-evening.hdr", "meanLum": 0.5, "sunCap": 3 }, "fog": { "color": "#f0c9a0", "density": 0.00016 },
          "exposure": 0.98, "shadowOpacity": 0.4,
          "basemap": { "brightnessMax": 0.80, "saturation": 0.25, "contrast": 0.06 },
          "tint": { "color": "#f2b27a", "opacity": 0.14 }, "haze": [236, 190, 150],
          "water": { "deep": "#092a3d", "mid": "#0e3a4e", "shallow": "#154b5b", "sand": "#35686d",
                     "skyZenith": "#6d93cc", "skyHorizon": "#e3c7ad", "sunColor": "#ffd9a0", "glintStrength": 1.6 },
          "sea": "#123f55", "litFrac": 0.10, "glow": 0.15 },
        { "h": 19.0, "name": "dusk",
          "sun": { "dir": [-995, 87, 52], "color": "#ff9a5c", "intensity": 0.2 },
          "hemi": { "sky": "#5a6c9a", "ground": "#5c5a6a", "intensity": 1.8 },
          "env": { "intensity": 0.12, "hdri": "assets/hero/tex/sky-dusk.hdr", "meanLum": 0.35, "sunCap": 2 }, "fog": { "color": "#58507a", "density": 0.00018 },
          "exposure": 0.88, "shadowOpacity": 0.15,
          "basemap": { "brightnessMax": 0.45, "saturation": -0.1, "contrast": -0.05 },
          "tint": { "color": "#3a3f7a", "opacity": 0.30 }, "haze": [88, 80, 122],
          "water": { "deep": "#0a1322", "mid": "#0d192b", "shallow": "#112033", "sand": "#1d2c3c",
                     "skyZenith": "#2a3a6e", "skyHorizon": "#c98a6a", "sunColor": "#ffb583", "glintStrength": 0.9, "foamColor": "#b9c1c6" },
          "sea": "#0a1524", "litFrac": 0.45, "glow": 0.8 },
        { "h": 21.0, "name": "night",
          "sun": { "dir": [500, -500, 707], "color": "#8fa4c8", "intensity": 0.85, "_note": "moon stand-in, az 135 el 45" },
          "hemi": { "sky": "#33456c", "ground": "#1f2838", "intensity": 1.35 },
          "env": { "intensity": 0.24, "hdri": "assets/hero/tex/sky-night.hdr", "meanLum": 0.06, "sunCap": 1, "skyDir": [-990, 50, -140] }, "fog": { "color": "#0a1220", "density": 0.0002 },
          "exposure": 1.32, "shadowOpacity": 0.1,
          "basemap": { "brightnessMax": 0.46, "saturation": -0.45, "contrast": -0.25 },
          "tint": { "color": "#16223f", "opacity": 0.42 }, "haze": [12, 20, 38],
          "water": { "deep": "#02070c", "mid": "#030a10", "shallow": "#051018", "sand": "#0a161c",
                     "skyZenith": "#070c16", "skyHorizon": "#121829", "sunColor": "#9fb0c9", "glintStrength": 0.5, "glintMax": 0.6, "foamColor": "#3c4a52" },
          "sea": "#03090f", "litFrac": 0.4, "glow": 1.0 }
      ]
    },
    "hdri": {
      "_note": "Photographed morning sky (Poly Haven rustig_koppie_puresky, CC0, sun 28 deg) replacing the procedural sky once loaded; desktop only. meanLum sets exposure, sunCap caps the sun disc (the DirectionalLight carries direct sun). Remove url to keep the procedural sky.",
      "url": "assets/hero/tex/sky-morning.hdr",
      "meanLum": 0.6,
      "sunCap": 3
    },
    "exposure": 0.85,
    "groundTint": {
      "color": "#f0cfa0",
      "opacity": 0.14
    },
    "massingColor": "#d9cfbd",
    "massingOpacity": 0.9,
    "modules": {
      "water": true,
      "trees": true,
      "buildings": true,
      "landmarks": true,
      "facadekit": true,
      "beach": true,
      "greenery": true,
      "roads": true,
      "streetkit": true,
      "ground": true,
      "storefronts": true,
      "amenities": true,
      "life": true
    },
    "shadow": {
      "mapSize": 4096,
      "mapSizeTablet": 2048,
      "halfMin": 220,
      "halfMax": 900,
      "color": "#2b2d55",
      "opacity": 0.42,
      "bias": -0.0002,
      "normalBias": 0.4
    },
    "fog": {
      "color": "#e9d3b4",
      "density": 0.00013
    },
    "hazeColor": "233 211 180",
    "materials": {
      "cityRoughness": 0.85,
      "glass": {
        "match": "glass|vitr|glaz|window|fenetre|стекл",
        "roughness": 0.06,
        "color": "#b8d3e2",
        "opacity": 0.55,
        "envMapIntensity": 1.6
      },
      "metal": {
        "match": "metal|steel|alu|inox|chrome",
        "roughness": 0.35,
        "metalness": 0.9
      },
      "concrete": {
        "match": "concrete|beton|render|plaster|stucco|wall",
        "roughness": 0.8
      }
    },
    "contactShadow": {
      "scale": 1.15,
      "opacity": 0.35
    },
    "landmarks": {
      "solid": true,
      "color": "#e4dccb"
    },
    "shots": {
      "open": { "center": [33.63711, 34.94004], "zoom": 13.85, "pitch": 60, "bearing": -70, "_note": "3D home: all five projects from the sea side beside the panel at 1330x578 (QA 28 Sep 2026, D046; cameraForBounds with the panel as padding)" },
      "s1": {
        "center": [
          33.6368,
          34.9142
        ],
        "zoom": 17,
        "pitch": 62,
        "bearing": 25,
        "label": "Fort, Finikoudes and the marina"
      },
      "s2": {
        "center": [
          33.639,
          34.9444
        ],
        "zoom": 18.2,
        "pitch": 60,
        "bearing": -75,
        "label": "Sirens from the sea"
      },
      "s3": {
        "center": [
          33.6459,
          34.9518
        ],
        "zoom": 17.5,
        "pitch": 60,
        "bearing": 118,
        "label": "Artemis and Hermes into the sun"
      },
      "s4": {
        "center": [
          33.637,
          34.944
        ],
        "zoom": 15.3,
        "pitch": 66,
        "bearing": -152,
        "label": "The whole strip, looking back to the fort"
      },
      "street": {
        "center": [
          33.6362,
          34.9155
        ],
        "zoom": 18.5,
        "pitch": 68,
        "bearing": 20,
        "label": "Street level, Zinonos Kitieos"
      },
      "finikoudes": {
        "center": [
          33.638,
          34.9152
        ],
        "zoom": 18.5,
        "pitch": 64,
        "bearing": -72,
        "label": "Finikoudes hotel row, from the sea"
      },
      "mackenzie": {
        "center": [
          33.6377,
          34.8935
        ],
        "zoom": 17.6,
        "pitch": 63,
        "bearing": -78,
        "label": "Mackenzie beach road, from the sea"
      },
      "kastella": {
        "center": [
          33.6378,
          34.8975
        ],
        "zoom": 17.8,
        "pitch": 62,
        "bearing": -70,
        "label": "Kastella, north end of Mackenzie"
      },
      "mackenzieClose": {
        "center": [
          33.6389,
          34.8932
        ],
        "zoom": 18.4,
        "pitch": 66,
        "bearing": -70,
        "label": "Mackenzie hotels and beach, close"
      },
      "mackenzieStreet": {
        "center": [
          33.6386,
          34.8938
        ],
        "zoom": 18.5,
        "pitch": 70,
        "bearing": -95,
        "label": "Mackenzie, LIV and Flamingo from the beach"
      },
      "parkSalina": {
        "center": [
          33.6262,
          34.916
        ],
        "zoom": 17.4,
        "pitch": 62,
        "bearing": 40,
        "label": "Municipal Park and Salina gardens"
      },
      "parkMitsopoulos": {
        "center": [
          33.6494,
          34.9551
        ],
        "zoom": 18,
        "pitch": 62,
        "bearing": -60,
        "label": "Tasos Mitsopoulos Park"
      },
      "nightFinikoudes": { "center": [33.638, 34.9152], "zoom": 18.3, "pitch": 64, "bearing": -72, "time": 21, "label": "Finikoudes at night" },
      "nightMackenzie": { "center": [33.6377, 34.8935], "zoom": 17.6, "pitch": 63, "bearing": -78, "time": 21, "label": "Mackenzie at night" },
      "duskMackenzie": { "center": [33.6389, 34.8932], "zoom": 18.2, "pitch": 64, "bearing": -70, "time": 19, "label": "Mackenzie at dusk" },
      "eveningFinikoudes": { "center": [33.6386, 34.9145], "zoom": 18.2, "pitch": 64, "bearing": -76, "time": 17.5, "label": "Finikoudes, golden evening" },
      "planeMackenzie": { "center": [33.6428, 34.8903], "zoom": 16.9, "pitch": 62, "bearing": 128, "label": "Landing over Mackenzie beach (runway 22 approach)" },
      "projSirens": {
        "center": [
          33.6388859,
          34.9443033
        ],
        "zoom": 18.2,
        "pitch": 62,
        "bearing": -70,
        "label": "Sirens from the sea, close"
      },
      "projArtemis": {
        "center": [
          33.6460266,
          34.950166
        ],
        "zoom": 18.0,
        "pitch": 62,
        "bearing": -65,
        "label": "Artemis from the sea, close"
      },
      "projHermes": {
        "center": [
          33.6454642,
          34.9536067
        ],
        "zoom": 18.0,
        "pitch": 62,
        "bearing": -65,
        "label": "Hermes from the sea, close"
      },
      "fkA": {
        "center": [
          33.6375,
          34.9104
        ],
        "zoom": 17.9,
        "pitch": 64,
        "bearing": -60,
        "label": "Finikoudes clip: south end"
      },
      "fkB": {
        "center": [
          33.638,
          34.9122
        ],
        "zoom": 18.3,
        "pitch": 66,
        "bearing": -70,
        "label": "Finikoudes clip: promenade"
      },
      "fkC": {
        "center": [
          33.6386,
          34.9145
        ],
        "zoom": 18.4,
        "pitch": 66,
        "bearing": -76,
        "label": "Finikoudes clip: hotel row"
      },
      "fkD": {
        "center": [
          33.6391,
          34.916
        ],
        "zoom": 18.1,
        "pitch": 63,
        "bearing": -48,
        "label": "Finikoudes clip: bank toward the marina"
      }
    },
    "sites": [
      { "id": "sirens", "sizeM": [78.8, 136.2], "padM": 14, "floors": 5,
        "footprintM": [-28.6, 28.9, -46.6, 41.6], "plotM": [-31.6, 36.9, -47.1, 72.6], "parking": { "side": "south", "fill": 0.65, "apronM": 1.2, "marginM": 0.8 },
        "_plotNote": "28 Sep 2026: the padded GLB box ran 31 m south over the E301. footprintM = the unit volumes' box in model axes; plotM = the site walls measured against the five foundation pads on Esri World Imagery (2024-25): 3 m west, 8 m east, south wall on the road verge, 31 m north." },
      { "id": "nereids", "sizeM": [33.6, 134.2], "padM": 12,
        "footprintM": [-17.5, 16.1, -62.7, 71.5], "plotM": [-29.5, 16.1, -66.7, 75.5],
        "_plotNote": "3 Oct 2026: footprintM = the architect model incl. its front pools and planters (measured in the app, model axes; long axis along y). The road runs along the east face, where the model's own lawn already reaches the verge; the plot adds lawn and hedge round the rest, as in the developer's bmby model." },
      { "id": "artemis", "sizeM": [43.0, 27.2], "padM": 32, "cover": [[33.6454, 34.95075], [33.64625, 34.95235], [33.64765, 34.95125], [33.6470, 34.9500]], "floors": 5 },
      { "id": "hermes", "sizeM": [97.4, 63.1], "padM": 18, "floors": 6 }
    ],
    "tourIdleS": 0,
    "_tourIdleNote": "0 = no idle roaming: it took the camera from people reading the map (Dmitry, 29 Sep 2026; was 60 s, before that 25 s). ?tour=1 still starts it on purpose, for an unattended sales-office screen.",
    "landmarksProcedural": { "fortModel": "larnaca-castle", "_note": "js/hero/landmarks.js: the surveyed castle model replaces the extruded fort block; ?fort=old shows the block for comparison." },
    "_sitesNote": "Landscaped plots under the developer's buildings (js/hero/sites.js): the 2019 photo shows them as building sites. sizeM = model bounding box in model axes (Blender), padM = plot margin.",
    "life": {
      "aircraft": {
        "_note": "Runway 22 arrivals at Larnaca (js/hero/life.js): heading 221 true from the NE over the sea, crossing Mackenzie beach ~1.1-1.5 km before the threshold at N34 52.92 E33 38.04 on a 3 deg glide. One aircraft on a loop keyed to the clock; phaseS puts it over the beach about 25 s after load.",
        "threshold": [33.634, 34.882], "trackDeg": 221, "glideDeg": 3, "startM": 8000, "crossM": 15, "rolloutM": 1500,
        "speedMps": 72, "gapS": 20, "phaseS": 70, "model": "plane"
      }
    },
    "clouds": { "_note": "Cloud shadows (js/hero/detail.js patchCloud): a noise field drifting with the SW wind darkens only the sun term by up to k. Desktop only. enabled:false switches it off. Off since 28 Sep 2026: clear-sky mode (Dmitry).", "enabled": false, "k": 0.2, "photo": 0.22, "tileM": 900, "driftMps": 4.2, "towardDeg": 45 },
    "floorView": { "_note": "js/hero/floorview.js: floors per residence in hero.sites (from the GLB heights; correct from the sales sheets). Sunset azimuth 273 deg = late September at Larnaca.", "floorM": 3.1, "eyeM": 1.6, "pitch": 82, "sunriseAzimuthDeg": 89, "sunsetAzimuthDeg": 273, "standoffM": 2 },
    "props": {
      "_note": "Real models from assets/props (Poly Pizza, see assets/props/CREDITS.md). Desktop only: phones keep the box shapes. Remove a list to fall back.",
      "cars": ["car-sedan", "car-estate", "car-suv", "car-van"],
      "boats": ["boat-rib", "boat-yacht", "boat-sail"],
      "umbrella": "umbrella",
      "sunbed": "sunbed",
      "bench": "bench",
      "lamp": "lamp",
      "bin": "bin",
      "bike": "bike"
    },
    "orbitDegPerSec": 0.8,
    "flight": [
      "open",
      "s1",
      "s2",
      "s3",
      "s4"
    ],
    "story": {
      "_note": "Overrides for the guided tour in the hero tier: slower flights, slower drift, and a tighter camera per project so a 40 m building fills a third of the frame instead of a speck at zoom 16.",
      "flyMs": 5200,
      "rotateDegPerSec": 0.8,
      "zoom": 17.6,
      "pitch": 60,
      "stops": {
        "sirens": {
          "zoom": 18,
          "pitch": 60,
          "bearing": -75
        },
        "nereids": {
          "zoom": 17.8,
          "pitch": 58,
          "bearing": -30
        },
        "artemis": {
          "zoom": 17.8,
          "pitch": 60,
          "bearing": 118
        },
        "hermes": {
          "zoom": 17.8,
          "pitch": 60,
          "bearing": 100
        },
        "saint-savvas": {
          "zoom": 17.6,
          "pitch": 58,
          "bearing": -40
        }
      }
    },
    "styledIndices": [6839, 6899, 7003, 7092, 7426, 8113, 8838],
    "_styledNote": "Seafront hotel row on Finikoudes modelled in Blender against Street View (assets/models/styled/*.blend kept as source). Their procedural boxes and kit pieces are skipped by index; the models load in the hero tier only.",
    "styled": [
      {
        "id": "finikoudes-5570",
        "file": "assets/models/styled/finikoudes-5570.glb",
        "lngLat": [
          33.6383167,
          34.9148069
        ],
        "rotateDeg": 0,
        "georef": true,
        "clearRadius": 0
      },
      {
        "id": "finikoudes-5730",
        "file": "assets/models/styled/finikoudes-5730.glb",
        "lngLat": [
          33.6374331,
          34.915471
        ],
        "rotateDeg": 0,
        "georef": true,
        "clearRadius": 0
      },
      {
        "id": "finikoudes-5587",
        "file": "assets/models/styled/finikoudes-5587.glb",
        "lngLat": [
          33.6373811,
          34.91347
        ],
        "rotateDeg": 0,
        "georef": true,
        "clearRadius": 0
      },
      {
        "id": "finikoudes-5578",
        "file": "assets/models/styled/finikoudes-5578.glb",
        "lngLat": [
          33.6374418,
          34.914224
        ],
        "rotateDeg": 0,
        "georef": true,
        "clearRadius": 0
      },
      {
        "id": "finikoudes-5561",
        "file": "assets/models/styled/finikoudes-5561.glb",
        "lngLat": [
          33.6391285,
          34.917122
        ],
        "rotateDeg": 0,
        "georef": true,
        "clearRadius": 0
      },
      {
        "id": "finikoudes-5557",
        "file": "assets/models/styled/finikoudes-5557.glb",
        "lngLat": [
          33.6392965,
          34.9181813
        ],
        "rotateDeg": 0,
        "georef": true,
        "clearRadius": 0
      },
      {
        "id": "district-seafront",
        "file": "assets/models/district-seafront.glb",
        "lngLat": [
          33.6377,
          34.91123
        ],
        "rotateDeg": 0,
        "georef": true,
        "clearRadius": 0
      },
      {
        "id": "district-mackenzie",
        "file": "assets/models/district-mackenzie.glb",
        "lngLat": [
          33.6383,
          34.8935
        ],
        "rotateDeg": 0,
        "georef": true,
        "clearRadius": 0
      }
    ],
    "_mackenzieNote": "Mackenzie beach road, 56 buildings modelled in Blender (scripts/blender_styled_build.py) from per-building specs read off Street View (data/mackenzie-specs.json, frames docs/hero/refs/mackenzie). clearRadius 6 drops the matching OSM box from the standard city by centroid.",
    "clearPoints": [
      [
        33.6375442,
        34.8909996,
        6
      ],
      [
        33.6381651,
        34.8914082,
        6
      ],
      [
        33.6378027,
        34.8914503,
        6
      ],
      [
        33.6383873,
        34.8916329,
        6
      ],
      [
        33.6378134,
        34.8917894,
        6
      ],
      [
        33.6377254,
        34.8922443,
        6
      ],
      [
        33.6384787,
        34.8922652,
        6
      ],
      [
        33.6379946,
        34.8922751,
        6
      ],
      [
        33.6383032,
        34.8926106,
        6
      ],
      [
        33.6378779,
        34.8927806,
        6
      ],
      [
        33.6383269,
        34.8928074,
        6
      ],
      [
        33.6383508,
        34.8929427,
        6
      ],
      [
        33.6380096,
        34.8929886,
        6
      ],
      [
        33.6383846,
        34.8931182,
        6
      ],
      [
        33.6383459,
        34.8932608,
        6
      ],
      [
        33.6382877,
        34.8934379,
        6
      ],
      [
        33.6389766,
        34.8934955,
        6
      ],
      [
        33.638304,
        34.8936595,
        6
      ],
      [
        33.6383506,
        34.8938231,
        6
      ],
      [
        33.6383164,
        34.8939891,
        6
      ],
      [
        33.6388759,
        34.8942292,
        6
      ],
      [
        33.6389715,
        34.8944483,
        6
      ],
      [
        33.6389865,
        34.8948586,
        6
      ],
      [
        33.6381167,
        34.895197,
        6
      ],
      [
        33.6383295,
        34.8953853,
        6
      ],
      [
        33.6382742,
        34.895783,
        6
      ],
      [
        33.6386156,
        34.8966712,
        6
      ],
      [
        33.63858,
        34.8971913,
        6
      ],
      [
        33.6386032,
        34.8975751,
        6
      ],
      [
        33.6380836,
        34.8977569,
        6
      ],
      [
        33.6386683,
        34.8977676,
        6
      ],
      [
        33.6379871,
        34.8979332,
        6
      ],
      [
        33.6384542,
        34.897977,
        6
      ],
      [
        33.6379278,
        34.8981642,
        6
      ],
      [
        33.6376381,
        34.8982646,
        6
      ],
      [
        33.6380434,
        34.8983214,
        6
      ],
      [
        33.6384007,
        34.8983544,
        6
      ],
      [
        33.6387704,
        34.898608,
        6
      ],
      [
        33.6379683,
        34.8986129,
        6
      ],
      [
        33.6383127,
        34.8986497,
        6
      ],
      [
        33.6379597,
        34.8987681,
        6
      ],
      [
        33.638317,
        34.8988259,
        6
      ],
      [
        33.6385613,
        34.8989205,
        6
      ],
      [
        33.6382443,
        34.8990491,
        6
      ],
      [
        33.6373925,
        34.899271,
        6
      ],
      [
        33.6383676,
        34.8993408,
        6
      ],
      [
        33.6377608,
        34.8993598,
        6
      ],
      [
        33.6387599,
        34.8993724,
        6
      ],
      [
        33.6376387,
        34.8995056,
        6
      ],
      [
        33.6382931,
        34.8995722,
        6
      ],
      [
        33.6373914,
        34.8996646,
        6
      ],
      [
        33.6375804,
        34.8997411,
        6
      ],
      [
        33.6378374,
        34.8998255,
        6
      ],
      [
        33.6373723,
        34.8999507,
        6
      ],
      [
        33.6375695,
        34.9000377,
        6
      ],
      [
        33.6374392,
        34.9002234,
        6
      ],
      [
        33.636281,
        34.8873573,
        6
      ],
      [
        33.6369094,
        34.8892551,
        6
      ],
      [
        33.6367846,
        34.8889566,
        6
      ],
      [
        33.6363502,
        34.8875287,
        6
      ],
      [
        33.6365167,
        34.8877615,
        6
      ],
      [
        33.6366371,
        34.8885327,
        6
      ],
      [
        33.6371254,
        34.8898463,
        6
      ],
      [
        33.6360028,
        34.8882501,
        6
      ],
      [
        33.6347498,
        34.8856531,
        6
      ],
      [
        33.6378279,
        34.8899218,
        6
      ],
      [
        33.6371825,
        34.8880562,
        6
      ],
      [
        33.6375096,
        34.8899411,
        6
      ],
      [
        33.6370546,
        34.8896436,
        6
      ],
      [
        33.6367193,
        34.8887696,
        6
      ],
      [
        33.63662,
        34.8883388,
        6
      ],
      [
        33.6372002,
        34.8888885,
        6
      ],
      [
        33.6361945,
        34.887176,
        6
      ]
    ],
    "trees": {
      "palmLines": {
        "url": "data/mackenzie-landscape.json",
        "key": "road",
        "rows": [
          {
            "offsetM": 7.5,
            "spacingM": 9
          },
          {
            "offsetM": -7,
            "spacingM": 15
          }
        ]
      },
      "extraUrl": "data/worldcover-green.json",
      "precisionUrl": "data/mackenzie-green.json"
    },
    "_districtNote": "Mackenzie is one district model (scripts/blender_district_build.py): 73 buildings, 8 shared materials, colour and baked shading in vertex colours. The pattern for the whole city: one GLB per district, a handful of draw calls each, the per-building detail in geometry, not in materials.",
    "greenery": {
      "extraUrl": "data/worldcover-green.json",
      "precisionUrl": "data/mackenzie-green.json",
      "wildUrl": "data/mackenzie-wild.json"
    },
    "facadeKit": { "heaterShare": 0, "railModuleM": 3.0, "acPerFloor": 0.3 },
    "_facadeKitNote": "Solar heaters come from js/hero/buildings.js (was doubled); rails every 3 m and fewer AC units: the kit was 4.5 M triangles at Kastella.",
    "camera": { "fovDeg": 36.87, "pitchFullToZoom": 18.5, "pitchLossPerZoom": 8, "nearMaxPitch": 76, "nearFromZoom": 16.5, "nearFullZoom": 17.3, "_nearNote": "Close-up tilt up to 76 deg so the horizon and the skyline hills show (3 Oct 2026, Dmitry: like bmby's opening view).", "_note": "32 deg (drone lens) pulled the camera back and cost ~6 fps on the Intel iGPU; the MapLibre default is kept, the pitch-by-zoom rule stays (js/app.js wireHeroCamera)." },
    "roofPhotos": false,
    "_roofPhotosNote": "Photo-draped roofs (js/hero/roofphoto.js, assets/hero/roof-atlas.jpg) tried 26 Sep 2026 and rejected by the owner: read as stains. Roofs are modelled: grey concrete + instanced heaters, AC units, dishes, tanks.",
    "surfaceMaps": {
      "sirens": {"Material22": "render", "Material27": "render", "Material44": "lead", "Material30": "render", "Material9": "taupe", "xRoof": "roofMembrane", "xDeck": "decking", "Material8": "glass", "Material73": "glass", "Material20": "glass", "Material33": "glass", "Material13": "soffit", "Material14": "terraceTile", "Material38": "louvre", "Material7": "anthracite", "Material4": "aluminium", "Material24": "taupe", "Material10": "darkMetal", "Material52": "darkMetal", "Material16": "decking", "Material35": "stone"},
      "hermes": {"Material31": "glass", "Material14": "glass", "Material62": "glass", "Material18": "glass", "Material80": "glass", "Material65": "glass", "Material13": "render", "Material67": "render", "Material19": "render", "Material29": "soffit", "Material66": "soffit", "Material39": "anthracite", "Material34": "darkMetal", "Material15": "darkMetal", "Material20": "pool", "Material49": "stone"},
      "artemis": {"Material17": "glass", "Material21": "glass", "Material60": "glass", "Material62": "glass", "Material1": "render", "Material40": "render", "Material20": "soffit", "Material35": "soffit", "Material11": "darkMetal", "Material41": "darkMetal"}
    },
    "maxRenderScale": 1.6, "_maxRenderScaleNote": "js/device.js: hero render scale ceiling on dense desktop panels, chosen once at boot (a runtime step blinked, 4 Oct 2026). ?scale= still pins it.",
    "governor": { "enabled": false, "minFps": 40, "windowMs": 3000, "cooldownMs": 5000, "scale1": 1.6, "maxLevel": 2,
      "_note": "js/hero/governor.js: while the camera moves, a 3 s median under minFps steps the hero level down once (1: render scale -> scale1 at the next idle, 2: shadow map 2048); never back up. ?governor=0/1, ?level=n." },
    "water": { "skyHdr": 0.35, "skyGain": 0.8, "swell": 0.3, "_note": "js/hero/water.js: skyHdr = share of the photographed sky (models3d.onSkyTex) in the reflections; a full share washed the far sea milky (the 28 Sep cream-band complaint), 0.35 keeps it deep blue with sky variation. swell = ripples 7x larger that survive distance." },
    "sitesStyle": { "lawn": "#62813f", "lawnDry": "#9a9c5c", "_note": "js/hero/sites.js plot lawns in the hero.lush meadow colours, so a plot reads as the garden inside the meadow, not a pasted square (4 Oct 2026)" },
    "roads": {"promenades": [{"_note": "js/hero/roads.js: Finikoudes promenade, Fort to marina, sea side: one slab band from the kerbside pavement to the sand along the OSM sea-side footway (match = its first point). Colours sampled from Street View 2017-22: light stone slabs, terracotta bands across.", "match": [33.63831, 34.91767], "seaM": 6.0, "maxGapM": 14, "slab": "#d0c9bb", "band": "#c39587", "bandEveryM": 12, "bandM": 1.6}, {"_note": "hotel side: from the kerbside pavement back to the cafe and hotel frontages along the OSM west footway; seaM here is the building side (footprints hide the overlap).", "match": [33.63784, 34.91755], "seaM": 6.0, "maxGapM": 10, "slab": "#d0c9bb", "band": "#c39587", "bandEveryM": 12, "bandM": 1.6}]},
    "industrial": {"_note": "scripts/industrial-sites.py + js/hero/tanks.js (Dmitry, 4 Oct 2026). cleared: traced on Esri 2024-25, where the 2019 photo still shows the old refinery and the tank farm north of the port (demolished for Land of Tomorrow). tankBoxes: tanks still standing (the port).", "cleared": [{"name": "old-refinery", "ring": [[33.649185, 34.951071], [33.650284, 34.950808], [33.649453, 34.949819], [33.648997, 34.948984], [33.648004, 34.94872], [33.647924, 34.949027], [33.648433, 34.949687], [33.648755, 34.950214], [33.648997, 34.950786]]}, {"name": "north-port-tanks", "ring": [[33.640028, 34.933021], [33.641019, 34.932911], [33.641502, 34.933659], [33.641958, 34.935329], [33.64244, 34.936912], [33.641288, 34.936912], [33.641154, 34.935945], [33.641073, 34.935329], [33.640349, 34.935373], [33.640108, 34.935153]]}, {"name": "refinery-yard-tank", "ring": [[33.6466, 34.9472], [33.647, 34.9472], [33.647, 34.9475], [33.6466, 34.9475]]}], "tankBoxes": [[33.637, 34.925, 33.6405, 34.9275]], "groundColor": "#bfb39c"},
    "lush": {
      "enabled": true,
      "_note": "js/hero/lush.js + greenery.js: bmby-style landscaping (Dmitry, 4 Oct 2026): meadow over the vacant land round each project out toward the roads (plot + padM, ragged edge over edgeM), the scrub there replaced by blossom-shaped flowerbeds and shrub groups. ?lush=0 / ?lush=1.",
      "meadow": "#62813f", "meadowDry": "#9a9c5c", "bedEveryM2": 2600, "shrubGroupEveryM2": 260, "oliveClusterEveryM2": 1500, "cypressSites": ["sirens", "nereids", "hermes", "artemis"], "cypressEveryM": 5,
      "zones": [
        { "site": "sirens", "padM": 60, "edgeM": 26 },
        { "site": "nereids", "padM": 45, "edgeM": 22 },
        { "site": "hermes", "padM": 50, "edgeM": 22 },
        { "site": "artemis", "padM": 50, "edgeM": 22 },
        { "center": [33.6347844, 34.9476966], "halfM": [55, 50], "rotDeg": 0, "edgeM": 20, "_site": "saint-savvas (no hero.sites plot)" }
      ]
    },
    "interiors": {
      "_note": "js/hero/glazing.js: windows of the architect models get rooms behind them (interior mapping in the building's axes), panels lower than railMaxM stay clear balustrade glass. floorM/baseM from the floor plans (Sirens ground floor at the slab, 3.1 m storeys); ?rooms=0 turns it off.",
      "sirens": { "floorM": 3.1, "baseM": 0.3, "cellM": 4.2, "railMaxM": 1.5 },
      "hermes": { "floorM": 3.2, "baseM": 0.3, "cellM": 4.0, "railMaxM": 1.5 },
      "artemis": { "floorM": 3.2, "baseM": 0.3, "cellM": 4.0, "railMaxM": 1.5 }
    },
    "atmo": {
      "enabled": true,
      "_note": "Aerial perspective + colour grade in the materials' fog step (js/hero/atmosphere.js); ?atmo=1 / ?atmo=0 overrides. d0 = haze extinction per metre at ground level, scaleHeightM = haze layer height, ext = per-channel extinction (blue fades first), mie = brightening toward the sun. Grade keys by hour, display space after tone mapping: lift/gamma/gain per channel, saturation, shadows/highlights = split-tone offsets.",
      "d0": 0.00016, "scaleHeightM": 420, "ext": [0.78, 1.0, 1.28], "mie": 1.1, "sunHaze": "#ffc890",
      "keys": [
        { "h": 7.75, "d0": 0.00018, "grade": { "lift": [0.01, 0.008, 0.012], "gain": [1.02, 1.0, 0.97], "saturation": 1.05, "shadows": [-0.01, 0.0, 0.015], "highlights": [0.02, 0.01, -0.01] } },
        { "h": 12.5, "d0": 0.00014, "grade": { "gain": [1.0, 1.0, 0.99], "saturation": 1.04, "shadows": [0.0, 0.0, 0.01] } },
        { "h": 15.5, "d0": 0.00015, "grade": { "gain": [1.01, 1.0, 0.985], "saturation": 1.06, "shadows": [-0.008, 0.0, 0.022], "highlights": [0.012, 0.006, -0.004] } },
        { "h": 17.5, "d0": 0.0002, "grade": { "lift": [0.012, 0.008, 0.01], "gain": [1.035, 1.0, 0.95], "saturation": 1.07, "shadows": [-0.01, 0.0, 0.03], "highlights": [0.025, 0.012, -0.01] } },
        { "h": 19, "d0": 0.0002, "grade": { "gain": [1.02, 0.97, 0.95], "saturation": 1.0, "shadows": [0.0, 0.0, 0.03] } },
        { "h": 21, "d0": 0.0001, "grade": { "lift": [0.0, 0.005, 0.015], "gain": [0.98, 1.0, 1.04], "saturation": 0.95, "shadows": [0.0, 0.005, 0.03], "highlights": [0.03, 0.015, 0.0] } }
      ]
    },
    "bakedSky": ["sirens", "nereids", "hermes", "artemis"],
    "projectRoofs": { "sirens": { "color": "#6b6d70", "solar": { "rowM": 3.2, "tiltDeg": 12, "marginM": 1.6, "cover": 0.6 }, "terraces": { "reachM": 4.2, "setM": 5.5, "padM": 1.5 } } },
    "_projectRoofsNote": "Roof slabs from roofs.json (js/hero/units.js): the developer's finish per project; Sirens dark membrane and PV rows as in its bmby model.",
    "_bakedSkyNote": "Models whose COLOR_0 is the Cycles sky-light bake (scripts/blender_skylight_bake.py): occlusion and bounce scale the live sky light; the sun stays live.",
    "_surfaceMapsNote": "Per-model surface kinds for architect exports with anonymous material names (js/hero/surfaces.js). Sirens map from the WebGPU spike highlight renders, 30 Sep 2026. Hermes and Artemis (3 Oct 2026) by SketchUp palette colour against the Sirens key: glass, render, soffit, metals and pool only, the rest keeps the default.",
    "precisionRegions": ["mackenzie", "finikoudes", "residences"],
    "_precisionNote": "Measured regions: data/<region>-green|wild|cars|streetkit|ground*.json from scripts/dls/mackenzie_*.py with PRECISION_REGION=<region> and scripts/mackenzie-streetkit.mjs <region>. Inside each box the photo is covered and every object is placed from the DLS 2019 orthophoto and height model.",
    "districtIndices": [4646,4653,4652,4661,4662,4685,4690,4694,4714,4738,4749,4755,4757,4761,4783,4785,4791,4803,4817,4861,4892,4914,4964,4985,5005,5013,5029,5076,5080,5088,5101,5107,5109,5139,5148,5181,8766,5248,5301,5310,5361,5377,5386,5380,5406,5440,5469,5511,5583,5601,5627,5677,5715,5736,5766,5775,5812,5841,5878,5898,5953,6043,6090,6073,6164,6197,6227,6239,6243,6307,6328,8655,6392,8656,6458,6506,8657,6563,8658,6637,6655,8659,8654,8660,8649,8651,8650,8652,8653,6911,6936,8111,7060,7102,8645,8648,7242,8646,7315,8647,7340,7381,8665,7417,8666,7493,7525,7557,7564,7579,7582,8667,7637,7736,8644,7809,7859,7866,7888,8671,8668,7938,7963,7995,8670,8643,8067,81,8354,8357,8356,8355],
    "_districtIndicesNote": "hero-buildings.json indices of the seafront first line (data/seafront-buildings.json heroIndex), built as assets/models/district-seafront.glb from Street View specs; re-run scripts/seafront-plan.py if hero-buildings.json is regenerated.",
    "toneMapping": "neutral",
    "_toneNote": "PBR Neutral over ACES (25 Sep 2026 A/B, docs/hero/tm-compare.png): ACES pushed the sea to candy turquoise and saturated the walls, the toy look. ?tm=aces|agx|neutral overrides for comparison.",
    "lite": { "modules": { "facadekit": false }, "_note": "Phones with WebGL2 get the hero scene with these modules off, 1024 px shadows (shadow.mapSizePhone) and render scale 1.5 (js/device.js heroLite)." },
    "beach": {
      "photoUrls": ["data/mackenzie-beach-photo.json", "data/finikoudes-beach-photo.json"]
    },
    "_worldcoverNote": "Extra trees and shrubs where ESA WorldCover 2021 (10 m, CC-BY 4.0) sees tree cover or shrubland that OSM does not map. Grassland pixels are ignored: in Larnaca they are mostly dry vacant land. Attribution required: ESA WorldCover 2021 (CC-BY 4.0).",
    "imagery": {
      "tiles": [
        "https://eservices.dls.moi.gov.cy/arcgis/rest/services/BASEMAPS/Orthoimagery_2019_WebMercator/MapServer/tile/{z}/{y}/{x}"
      ],
      "tileSize": 256,
      "maxzoom": 19,
      "cameraMaxZoom": 19.5,
      "proxyTiles": "/tiles/dls/{z}/{y}/{x}",
      "attribution": "Orthophoto 2019 &copy; Department of Lands and Surveys, Republic of Cyprus; gaps &copy; Esri, Maxar; Larnaca Castle model: M.A. StruTec Engineers (CC BY 4.0); 3D props: PuKkBuMXDD, Alex Safayan, S. Paul Michael, Poly by Google (CC-BY 3.0), Quaternius (CC0) via <a href=\"https://poly.pizza\">Poly Pizza</a>",
      "_note": "DLS 2019 orthophoto, cached Web Mercator tiles z0-21, native about 10 cm. Licence per INSPIRE metadata CC BY-SA 4.0; written confirmation from dls_portal_badmin@dls.moi.gov.cy needed before public launch. Covers Cyprus only; the coarse underlay keeps the same source."
    }
  },
  "heroBuildings": { "interiors": true, "_note": "js/hero/buildings.js: interiors = rooms behind the procedural facade windows (interior mapping), ?rooms=0/1. Other heroBuildings keys keep the code defaults." },
  "_ambientComment": "Ambient life in the Three.js scene: an animated sea and instanced traffic. One rAF ticker drives them, gated by the Map data eye, tab visibility, reduced-motion and the 3D zoom gate. Counts and speeds are the perf budget - raise with care on iPad.",
  "ambient": {
    "_budgetNote": "Counts tuned on an Intel UHD iGPU: 160 cars + 5 boats keep the 30 fps floor in verify-3d. (Clouds were removed 2026-09-14 - the client wants the clear-sky look of Cyprus aerials.)",
    "_fpsNote": "Update cadence for the ambient ticker. Drift and swell read identically at 30 and 60 Hz; 30 halves what the scene spends while idle. User interaction renders at full rate regardless.",
    "fps": 30,
    "sea": {
      "_lookNote": "THE sea colour, and the only one: js/style-overrides.js paints every ocean polygon with it at every zoom, and the animated overlay in js/ambient3d.js draws the same colour on top so its polygon boundary is invisible. Deliberately NO shore-distance gradient (a deeper colour offshore read as a stain mid-bay), NO sun glints and NO sine-wave bands (both read as a cartoon). What you set here is what you see - the shader encodes sRGB on the way out.",
      "color": "#50aeb8",
      "colorDark": "#124b60",
      "opacity": 0.85,
      "opacityDark": 0.85,
      "shimmer": 0.1,
      "driftMps": 1.2,
      "windDeg": 35,
      "_textureNote": "Two scales of surface texture, both drawn over the flat fill so neither changes the sea colour. shimmer is the fine wind streaks (tens of metres, faded out below ~z13 where they go sub-pixel and would sparkle). mottle is broad kilometre-scale patches and is what stops the bay reading as one flat chip - it is never faded, since a 1 km patch is still tens of pixels at the lowest reachable zoom. driftMps sets how fast both drift; windDeg the direction they stretch along, clockwise from north - Larnaca bay gets its wind from the north-east.",
      "textureFadeZoom": [
        15,
        16.3
      ],
      "_textureFadeNote": "Zooms across which the drifting texture fades out. Only the texture: the colour is the satellite-sea fill and is identical either side. The mesh is the fetched coastline polygon, which knows nothing about piers or breakwaters, so at street zoom it would otherwise wash turquoise over the marina moles.",
      "mottle": 0.16,
      "shoreColor": "#8adde2",
      "shoreColorDark": "#1d6f86",
      "shoreInlandM": 34,
      "shorePlateauM": 30,
      "shoreSeawardM": 220,
      "_shoreNote": "The soft edge where water meets land. The sea colour is painted on the vector ocean polygon, which ends in an exact line - one pixel of turquoise, then one pixel of roof - and nothing in an aerial photograph does that. A ribbon is laid along the real shoreline instead: wet-sand colour and transparent at its landward edge, opaque shallow water across the boundary itself (so the hard edge sits underneath it), then fading over shoreSeawardM into the open colour. Raise shoreSeawardM for a wider shelf; drop shoreInlandM toward 0 if it ever reads as flooding the promenade.",
      "shoreSandColor": "#d9d2bd",
      "shoreSandColorDark": "#2c3b4a"
    },
    "cars": {
      "count": 160,
      "speedMajorMps": 13,
      "speedMinorMps": 7.5,
      "lengthM": 4.4,
      "widthM": 1.9,
      "heightM": 1.45,
      "palette": [
        "#c94040",
        "#2f4f8c",
        "#22303f",
        "#e8e8e8",
        "#8c2f2f",
        "#b8bcc2",
        "#3c6b4f"
      ],
      "minPx": 5,
      "maxScale": 6.5,
      "_scaleNote": "Cars keep a minimum on-screen size (minPx) like map symbols, capped at maxScale x real size; true scale when the camera can resolve them."
    },
    "boats": {
      "_note": "Example boats drifting offshore; lanes are the shoreline pushed seaward by offsetM. Same minimum-pixel rule as cars.",
      "count": 5,
      "speedMps": 3,
      "lengthM": 9.5,
      "widthM": 3.2,
      "offsetM": [
        280,
        1100
      ],
      "minPx": 5,
      "maxScale": 6,
      "palette": [
        "#f2f2f2",
        "#e8e4da",
        "#dfe7ee",
        "#1d3a5f",
        "#f2f2f2"
      ]
    }
  }
}
