// ── Hosted map views ──────────────────────────────────────────────────────── // One property, one shortlink, more than one embed. A property manager pastes // the availability map on their Floor Plans page, the Amenity Map on their // Amenities page and the Nearby Map on their Neighborhood page, and all three // are the same property config — only the view differs: // // .../widget- availability (unchanged default) // .../widget-?view=amenities the amenity panel IS the sidebar // .../widget-?view=nearby the nearby panel IS the sidebar // // Each preset is applied LAST, over the property's own config, because a view // is a deliberate override of the defaults this file sets — showUnitList: true // below is exactly what an amenity view has to undo. var BEANS_VIEW_PRESETS = { // Amenities. No unit sidebar at all: the amenity panel takes the slot, // grouped by category. The footer dock is hidden because the panel is the // same information better presented, and showing both is just the list twice. // // The floor picker goes too — the panel labels each amenity's floor and // clicking a row flies there, so the picker is a second, slower way to do the // same job. keepFloorSlicing keeps the SLICING, which is not the same thing: // without it a rooftop amenity frames the roof of an uncut tower. Both hide // flags are needed because showHighriseFloorPicker is routinely set by the // property's server-side config, and the picker is a docked panel whose width // the map reserves. amenities: { showUnitList: false, amenityPanel: true, hidePOIsCard: true, showAmenityMode: true, // ...and start in it. Amenity mode dims the unit polygons to 20% so the // amenity shapes read as the subject rather than competing with a tower of // lit-up units. On an availability map that's a toggle you reach for; on a // map that is ONLY about amenities it's the right opening state, and making // the visitor find the button first was just a worse default. // // The button stays visible, so it becomes "bring the units back" for anyone // who wants to see where an amenity sits relative to the units. showAmenityModeOnLoad: true, showPOIButton: false, hideFloorSelector: true, showHighriseFloorPicker: false, keepFloorSlicing: true, // The filter bar filters UNITS — beds, baths, price. This view has no unit // list to filter, so on mobile it was a full-width bar across the top that // opened a sheet with nothing to do. Hiding it also gives the List View // card the top 40px back. hideFilters: true }, // Nearby. The panel becomes the sidebar and opens already searched, so the // embed answers the question its title implies instead of handing over an // empty search form. No floor picker: nearby results are places AROUND the // building, and slicing to floor 7 means nothing to a list of restaurants. nearby: { showUnitList: false, // Explicitly off, not merely unset. The amenity panel and the nearby panel // both parent into cardResult — they are two claims on the same sidebar — // so a property whose config turns the amenity panel on would otherwise get // its amenities in the sidebar while the nearby results it actually asked // for were pushed out of view. The view decides, and this view is nearby. amenityPanel: false, hidePOIsCard: true, showPOIButton: true, nearbyInSidebar: true, nearbyOpen: true, hideFloorSelector: true, showHighriseFloorPicker: false, // Same reasoning as the amenity view: unit filters against a view with no // unit list. The nearby panel has its own category chips and radius, which // are the filters that mean anything here. hideFilters: true } }; // Which view this page is. Read from the BROWSER's URL rather than from PHP on // purpose: `widget-` reaches clientmap.php through a server-side // rewrite that lives outside this repo, and an internal rewrite leaves the // address bar — and so location.search — intact whether or not it forwards the // query string to PHP. window.BEANS_VIEW is the belt-and-braces path for a // host that would rather set it explicitly. var getHostedView = function() { try { if (typeof window !== 'undefined' && window.BEANS_VIEW) return String(window.BEANS_VIEW).toLowerCase(); var v = new URLSearchParams(window.location.search).get('view'); return v ? String(v).toLowerCase() : ''; } catch (e) { return ''; } }; var beansUrlParameter = function(sParam) { console.log(sParam + ' ' + window.location.search.substring(1)); console.log(document.referrer); var sPageURL = window.location.search.substring(1), sURLVariables = sPageURL.split('&'), sParameterName, i; for (i = 0; i < sURLVariables.length; i++) { sParameterName = sURLVariables[i].split('='); if (sParameterName[0] === sParam) { return sParameterName[1] === undefined ? true : decodeURIComponent(sParameterName[1]); } } } var getBaseConfig = function(urlFromRequest) { return { address: '', useGroundElevation: false, elevation: 0, canonicalUrl: urlFromRequest }; } var convertUnitsArr2 = function(address, unitsArr, hideMarkers) { var convertedArr = []; for (var i = 0; i < unitsArr.length; i++) { var bedText = (unitsArr[i].bed && unitsArr[i].bed !== '0') ? (unitsArr[i].bed + ' Bed') : 'Studio'; var td = new Date().toISOString().substring(0, 10); var as = (unitsArr[i].availability ? ('on ' + unitsArr[i].availability) : 'Now'); if (unitsArr[i].availability && unitsArr[i].availability <= td) { as = 'Now'; } // The unit list / detail panel render onClickData.availability verbatim, so // an already-passed move-in date showed up as a bare past date ("2026-06-25") // where the preview line said "Available Now". Carry the same rule across: // once `as` has collapsed to 'Now' the unit is available today. Non-ISO // values (e.g. a literal 'Now') never satisfy the <= comparison and pass // through untouched. var availabilityStr = (as === 'Now') ? 'Available Now' : unitsArr[i].availability; var onPreviewData; if (unitsArr.floorplanImg) { onPreviewData = [ { value: bedText + ' / ' + unitsArr[i].bath + ' Bath / ' + (unitsArr[i].sqft ? unitsArr[i].sqft.toLocaleString() : '0') + ' sq. ft.' }, { value: 'img:' + unitsArr[i].floorplanImg }, { value: '$' + (unitsArr[i].rent ? unitsArr[i].rent.toLocaleString() : '0') + '/ mo' }, { value: 'Available ' + as } ]; } else { onPreviewData = [ { value: bedText + ' / ' + unitsArr[i].bath + ' Bath / ' + (unitsArr[i].sqft ? unitsArr[i].sqft.toLocaleString() : '0') + ' sq. ft.' }, { value: '$' + (unitsArr[i].rent ? unitsArr[i].rent.toLocaleString() : '0') + '/ mo' }, { value: 'Available ' + as } ]; } if (unitsArr[i].floorplan) { onPreviewData.push({ value: unitsArr[i].floorplan, icon: 'floorplan' }); } convertedArr.push({ address: address, unit: unitsArr[i].unit, options: { markers: { display: hideMarkers ? false : true }, poi: [ { name: "ALL", display: false }, { name: "CLUBHOUSE", display: true }, { name: "SWIMMINGPOOL", display: true }, { name: "OFFICE", display: true }, { name: "GYM", display: true }, { name: "MAILROOM", display: true }, { name: "LAUNDRY", display: true }, { name: "PLAYGROUND", display: true } ], onPreviewData: onPreviewData, onClickData: { name: unitsArr[i].unit ? unitsArr[i].unit.replaceAll('||', ' - ') : '', bed: unitsArr[i].bed, bath: unitsArr[i].bath, sqft: unitsArr[i].sqft, rent: unitsArr[i].rent, floorplan: unitsArr[i].floorplan, floorplanImg: unitsArr[i].floorplanImg, images: unitsArr[i].otherImg, availability: availabilityStr, link: unitsArr[i].link ? unitsArr[i].link.replaceAll('&', '&') : '' } } }); } return convertedArr; } var convertUnitsArr = function(config, unitsArr, hideMarkers, skipSort) { var addressAndUnitWithOptions; if (unitsArr.length === 0) { unitsArr.push({ unit: '' }); addressAndUnitWithOptions = convertUnitsArr2(config.address, unitsArr, hideMarkers); addressAndUnitWithOptions[0].isPlaceHolder = true; } else { if (!skipSort) { unitsArr.sort((a, b) => { var aa = a.unit ? a.unit : ''; var bb = b.unit ? b.unit : ''; return aa.localeCompare(bb); }); } addressAndUnitWithOptions = convertUnitsArr2(config.address, unitsArr, hideMarkers); } if (config.unitShapeConfig) { for (var i = 0; i < addressAndUnitWithOptions.length; i++) { addressAndUnitWithOptions[i].options.unitShape = config.unitShapeConfig(addressAndUnitWithOptions[i]); } } if (config.poi) { for (var i = 0; i < addressAndUnitWithOptions.length; i++) { for (var j = 0; j < config.poi.length; j++) { addressAndUnitWithOptions[i].options.poi.push(config.poi[j]); } } } return addressAndUnitWithOptions; } const commonImmersivesNormalized = [ { u: "FeatureServer/0", c: { offset: 0.1, opacity: 0.25, immersiveType: 'trees', options: [ { values: ['Apricot', 4, '4'], symbol: { type: "point-3d", symbolLayers: [ { type: "object", "height": 4, "anchor": "origin", "resource": { // https://static.arcgis.com/arcgis/styleItems/RealisticTrees/gltf/resource/PrunusArmeniaca.glb "href": 'https://static.arcgis.com/arcgis/styleItems/ThematicTrees/gltf/resource/SorbusAria.glb' } } ] } }, { values: ['Mountain Mahogany', 5, '5'], symbol: { type: "point-3d", symbolLayers: [ { type: "object", "height": 2, "anchor": "origin", "resource": { // https://static.arcgis.com/arcgis/styleItems/RealisticTrees/gltf/resource/CercocarpusMontanus.glb "href": 'https://static.arcgis.com/arcgis/styleItems/ThematicTrees/gltf/resource/AcerPlatanoides.glb' } } ] } }, { values: ['Rose', 6, '6'], symbol: { type: "point-3d", symbolLayers: [ { type: "object", "height": 2, "anchor": "origin", "resource": { // https://static.arcgis.com/arcgis/styleItems/RealisticTrees/gltf/resource/RosaGrandiflora.glb "href": 'https://static.arcgis.com/arcgis/styleItems/ThematicTrees/gltf/resource/AcerPlatanoides.glb' } } ] } }, { values: ['Fountain', 7, '7'], symbol: { type: "point-3d", symbolLayers: [ { type: "object", "height": 16, "anchor": "origin", "resource": { // https://static.arcgis.com/arcgis/styleItems/RealisticTrees/gltf/resource/RosaGrandiflora.glb "href": 'https://www.beans.ai/m/assets/fountain.glb' } } ] } } ] } }, { u: "FeatureServer/1", c: { offset: 0.2, options: [ { values: ['Walkway', 2, '2'], symbol: { type: "line-3d", symbolLayers: [ { type: "path", profile: "quad", material: { color: "#E3DDCA" }, width: 2, // the width in m height: 0.2, // the height in m profileRotation: "heading" } ] } }, { values: ['Road', 1, '1', '99'], symbol: { type: "line-3d", symbolLayers: [ { type: "path", profile: "quad", material: { color: "#aaaaaa" }, width: 6, // the width in m height: 0.1, // the height in m profileRotation: "heading" } ] } } ] } }, { u: "FeatureServer/2", c: { offset: 0.1, options: [ { values: ['Lawn', 1, '1', 6, '6'], symbol: { type: "simple-fill", outline: { width: 0 }, color: "rgba(178, 195, 136, 0.35)" } }, { values: ['Tree_Lawn', 2, '2', 11, '11'], symbol: { type: "simple-fill", outline: { width: 0 }, color: "rgba(130, 152, 77, 0.1)" } }, { values: [12, '12'], symbol: { type: "simple-fill", outline: { width: 0 }, color: "rgba(130, 152, 77, 0.35)" } }, { values: ['Nearmap_Concrete_Slab', 3, '3'], symbol: { type: "simple-fill", outline: { width: 0 }, color: "#CCCCCC" } }, { values: ['Mulch', 8, '8'], symbol: { type: "simple-fill", outline: { width: 0 }, color: "#DBCEA2" } }, { values: ['Eraser', 15, '15'], symbol: { type: "simple-fill", outline: { width: 0 }, color: "#EEEEEE" } } ] } }, { u: "FeatureServer/2", c: { offset: 0.2, options: [ { values: ['Water', 10, '10'], excludedAmenities: ['SWIMMINGPOOL'], symbol: { type: "polygon-3d", symbolLayers: [ { type: "water", waveDirection: 180, color: "#a5c2d1", waveStrength: "moderate", waterbodySize: "medium" } ] }, } ] } } ]; var commonImmersives = []; for (var k = 0; k < commonImmersivesNormalized.length; k++) { var u = commonImmersivesNormalized[k].u; var c = commonImmersivesNormalized[k].c; var t = commonImmersivesNormalized[k].t ? commonImmersivesNormalized[k].t : 'type'; var obj = { url: u, elevationInfo: { mode: "absolute-height", offset: c.offset }, opacity: c.opacity, immersiveType: c.immersiveType, renderer: { type: 'unique-value', field: t, uniqueValueInfos: [] }, isFeature: true }; for (var i = 0; i < c.options.length; i++) { for (var j = 0; j < c.options[i].values.length; j++) { obj.renderer.uniqueValueInfos.push({ value: c.options[i].values[j], symbol: c.options[i].symbol }); if (c.options[i].excludedAmenities && c.options[i].excludedAmenities.length) { obj.excludedAmenities = obj.excludedAmenities || {}; obj.excludedAmenities['' + c.options[i].values[j]] = c.options[i].excludedAmenities; } } } commonImmersives.push(obj); } // Hosted map view presets (BEANS_VIEW_PRESETS / getHostedView) live in // client/view-presets.js, included ahead of this file by every client bundle. // Kept separate so a dev page can load the REAL presets in a plain