{"id":3013,"date":"2026-03-30T03:10:42","date_gmt":"2026-03-30T03:10:42","guid":{"rendered":"https:\/\/3kf.031.myftpupload.com\/?page_id=3013"},"modified":"2026-03-30T03:36:46","modified_gmt":"2026-03-30T03:36:46","slug":"locations","status":"publish","type":"page","link":"https:\/\/wavemaxlaundry.com\/es\/locations\/","title":{"rendered":"locations"},"content":{"rendered":"<div class=\"et_pb_section_0 et_pb_section et_section_regular et_flex_section\">\n<div class=\"et_pb_row_0 et_pb_row et_flex_row\">\n<div class=\"et_pb_column_0 et_pb_column et-last-child et_flex_column et_pb_css_mix_blend_mode_passthrough et_flex_column_24_24 et_flex_column_24_24_tablet et_flex_column_24_24_phone et_flex_column_24_24_phoneWide et_flex_column_24_24_tabletWide et_flex_column_24_24_widescreen et_flex_column_24_24_ultraWide\">\n<div class=\"et_pb_code_0 et_pb_code et_pb_module\"><div class=\"et_pb_code_inner\"><!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n<title>WaveMAX Locations<\/title>\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Poppins:wght@300;400;500;600;700&display=swap\" rel=\"stylesheet\">\n<style>\n\n\/* \u2500\u2500\u2500 Reset \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }\n\n\/* \u2500\u2500\u2500 Tokens on :root so every element can access them \u2500\u2500 *\/\n:root {\n  --font: 'Poppins', sans-serif;\n  --navy:       #0B1F4A;\n  --navy-mid:   #1A3B7A;\n  --brand-blue: #1D6FBF;\n  --cyan:       #2BBDE0;\n  --cyan-hover: #1FAAC9;\n  --cyan-soft:  #E4F7FC;\n  --cyan-mid:   #A3E4F5;\n  --teal:       #0FB8A4;\n  --teal-soft:  #E0F7F5;\n  --orange:     #F76B35;\n  --orange-soft:#FEF0E8;\n  --green:      #16A163;\n  --green-soft: #E4F7EE;\n  --purple:     #6B4EE6;\n  --purple-soft:#F0EDFF;\n  --amber:      #D4820A;\n  --amber-soft: #FEF3E0;\n  --gray-50:    #F5F7FB;\n  --gray-100:   #EAECF5;\n  --gray-200:   #D2D6E8;\n  --gray-300:   #B0B6D0;\n  --gray-400:   #7B84A8;\n  --gray-600:   #424B72;\n  --gray-900:   #0E1330;\n  --white:      #FFFFFF;\n  --radius-sm:  8px;\n  --radius-md:  12px;\n  --radius-lg:  18px;\n  --shadow-card:  0 2px 16px rgba(11,31,74,0.08);\n  --shadow-hover: 0 10px 32px rgba(29,111,191,0.18);\n  --transition: 0.2s ease;\n}\n\nhtml, body {\n  font-family: var(--font);\n  font-size: 14px;\n  background: var(--gray-50);\n  color: var(--gray-900);\n  -webkit-font-smoothing: antialiased;\n  width: 100%;\n  overflow-x: hidden;\n}\n\n\/* \u2500\u2500\u2500 Hero Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-hero {\n  background: linear-gradient(135deg, var(--navy) 0%, var(--navy-mid) 50%, var(--brand-blue) 100%);\n  padding: 28px 24px 24px;\n  text-align: center;\n  position: relative;\n  overflow: hidden;\n}\n.wm-hero::before {\n  content: '';\n  position: absolute;\n  bottom: -50px; left: 50%; transform: translateX(-50%);\n  width: 700px; height: 160px;\n  background: radial-gradient(ellipse, rgba(43,189,224,0.20) 0%, transparent 70%);\n  pointer-events: none;\n}\n.wm-hero h2 {\n  font-size: 26px;\n  font-weight: 800;\n  color: #fff;\n  margin-bottom: 6px;\n  letter-spacing: -0.3px;\n  line-height: 1.2;\n}\n.wm-hero h2 span { color: var(--cyan); }\n.wm-hero-logo {\n  height: 1em;          \/* matches the current line height of the h2 exactly *\/\n  width: auto;\n  vertical-align: middle;\n  position: relative;\n  top: -2px;            \/* optical alignment with the surrounding text *\/\n  display: inline-block;\n}\n.wm-hero p {\n  font-size: 13px;\n  font-weight: 400;\n  color: rgba(255,255,255,0.68);\n  margin-bottom: 16px;\n}\n\n.wm-zip-row {\n  display: flex;\n  max-width: 480px;\n  margin: 0 auto;\n  border-radius: 50px;\n  overflow: hidden;\n  box-shadow: 0 8px 32px rgba(0,0,0,0.30);\n  border: 1.5px solid rgba(255,255,255,0.15);\n}\n.wm-zip-input {\n  flex: 1;\n  padding: 13px 20px;\n  font-family: var(--font);\n  font-size: 14px;\n  font-weight: 500;\n  border: none;\n  outline: none;\n  background: rgba(255,255,255,0.97);\n  color: var(--gray-900);\n}\n.wm-zip-input::placeholder { color: var(--gray-400); font-weight: 400; }\n.wm-zip-btn {\n  padding: 13px 22px;\n  background: var(--cyan);\n  color: var(--white);\n  font-family: var(--font);\n  font-size: 13px;\n  font-weight: 700;\n  border: none;\n  cursor: pointer;\n  transition: background var(--transition);\n  white-space: nowrap;\n  letter-spacing: 0.3px;\n}\n.wm-zip-btn:hover { background: var(--cyan-hover); }\n.wm-zip-status {\n  font-size: 12px;\n  font-weight: 500;\n  color: rgba(255,255,255,0.70);\n  margin-top: 14px;\n  min-height: 22px;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 8px;\n}\n\n.wm-clock {\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  flex-shrink: 0;\n}\n.wm-clock circle { fill: none; stroke: rgba(255,255,255,0.35); stroke-width: 1.5; }\n.wm-clock .clock-face { stroke: rgba(255,255,255,0.70); stroke-width: 1.5; fill: none; }\n.wm-clock .clock-hour {\n  stroke: #fff;\n  stroke-width: 2;\n  stroke-linecap: round;\n  transform-origin: 10px 10px;\n  animation: clockHour 6s linear infinite;\n}\n.wm-clock .clock-min {\n  stroke: var(--cyan);\n  stroke-width: 1.8;\n  stroke-linecap: round;\n  transform-origin: 10px 10px;\n  animation: clockMin 1.5s linear infinite;\n}\n.wm-clock-pulse {\n  animation: statusPulse 1.8s ease-in-out infinite;\n}\n@keyframes clockHour {\n  from { transform: rotate(0deg); }\n  to   { transform: rotate(360deg); }\n}\n@keyframes clockMin {\n  from { transform: rotate(0deg); }\n  to   { transform: rotate(360deg); }\n}\n@keyframes statusPulse {\n  0%, 100% { opacity: 0.70; }\n  50%       { opacity: 1; }\n}\n\n\/* \u2500\u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-toolbar {\n  background: var(--white);\n  border-bottom: 1px solid var(--gray-100);\n  padding: 8px 16px;\n  display: flex;\n  align-items: center;\n  gap: 6px;\n  flex-wrap: wrap;\n  position: sticky;\n  top: 0;\n  z-index: 100;\n  box-shadow: 0 2px 8px rgba(0,0,0,0.05);\n}\n\n.wm-filter-select {\n  padding: 6px 10px;\n  border: 1.5px solid var(--gray-200);\n  border-radius: 50px;\n  font-family: var(--font);\n  font-size: 12px;\n  font-weight: 500;\n  background: var(--gray-50);\n  color: var(--gray-600);\n  outline: none;\n  cursor: pointer;\n  transition: border-color var(--transition);\n  max-width: 110px;\n}\n.wm-filter-select:focus { border-color: var(--cyan); }\n\n.wm-toggle-btn {\n  padding: 6px 10px;\n  border-radius: 50px;\n  border: 1.5px solid var(--gray-200);\n  background: var(--gray-50);\n  color: var(--gray-600);\n  font-family: var(--font);\n  font-size: 11px;\n  font-weight: 600;\n  cursor: pointer;\n  transition: all var(--transition);\n  display: flex;\n  align-items: center;\n  gap: 4px;\n  white-space: nowrap;\n}\n.wm-toggle-btn.active, .wm-toggle-btn:hover {\n  background: var(--cyan-soft);\n  border-color: var(--cyan);\n  color: #0878A0;\n}\n\n.wm-results-count {\n  margin-left: auto;\n  font-size: 12px;\n  font-weight: 600;\n  color: var(--gray-400);\n  white-space: nowrap;\n}\n.wm-results-count strong { color: var(--brand-blue); font-weight: 700; }\n\n\/* \u2500\u2500\u2500 Split Layout \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-body {\n  display: grid;\n  grid-template-columns: minmax(0, 48%) 1fr;\n  height: 700px;\n  width: 100%;\n  overflow: hidden;\n}\n\n\/* \u2500\u2500\u2500 Cards Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-panel {\n  overflow-y: auto;\n  background: var(--gray-50);\n  padding: 12px 12px 48px;\n  scroll-behavior: smooth;\n  height: 700px;\n}\n.wm-panel::-webkit-scrollbar { width: 5px; }\n.wm-panel::-webkit-scrollbar-track { background: transparent; }\n.wm-panel::-webkit-scrollbar-thumb { background: var(--gray-200); border-radius: 10px; }\n\n.wm-grid {\n  display: grid;\n  grid-template-columns: 1fr;\n  gap: 10px;\n}\n\n\/* \u2500\u2500\u2500 Map Panel \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-map-panel {\n  position: relative;\n  border-left: 1px solid var(--gray-200);\n  height: 700px;\n  overflow: hidden;\n}\n#wm-map {\n  width: 100%;\n  height: 700px;\n  display: block;\n  background: #e8efe0;\n}\n\n\/* \u2500\u2500\u2500 Map Overlay UI \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-map-overlay {\n  position: absolute;\n  top: 0; left: 0; right: 0; bottom: 0;\n  pointer-events: none;\n  z-index: 10;\n}\n\n.wm-nearest-btn {\n  position: absolute;\n  bottom: 24px;\n  left: 50%;\n  transform: translateX(-50%);\n  pointer-events: all;\n  background: var(--navy);\n  color: white;\n  font-family: var(--font);\n  font-size: 13px;\n  font-weight: 700;\n  padding: 13px 24px;\n  border-radius: 50px;\n  border: none;\n  cursor: pointer;\n  display: flex;\n  align-items: center;\n  gap: 9px;\n  box-shadow: 0 4px 20px rgba(11,31,74,0.40);\n  transition: all 0.2s ease;\n  white-space: nowrap;\n  letter-spacing: 0.2px;\n}\n.wm-nearest-btn:hover {\n  background: var(--brand-blue);\n  box-shadow: 0 6px 24px rgba(29,111,191,0.45);\n  transform: translateX(-50%) translateY(-2px);\n}\n.wm-nearest-btn svg {\n  flex-shrink: 0;\n}\n\n.wm-map-status {\n  position: absolute;\n  top: 14px;\n  left: 50%;\n  transform: translateX(-50%);\n  pointer-events: none;\n  background: rgba(11,31,74,0.88);\n  color: rgba(255,255,255,0.90);\n  font-family: var(--font);\n  font-size: 11px;\n  font-weight: 600;\n  padding: 6px 14px;\n  border-radius: 50px;\n  white-space: nowrap;\n  opacity: 0;\n  transition: opacity 0.4s ease;\n}\n.wm-map-status.visible { opacity: 1; }\n\n.wm-map-legend {\n  position: absolute;\n  top: 14px;\n  left: 14px;\n  pointer-events: none;\n  background: rgba(11,31,74,0.88);\n  border-radius: 10px;\n  padding: 9px 13px;\n  display: flex;\n  flex-direction: column;\n  gap: 5px;\n}\n.wm-map-legend-row {\n  display: flex;\n  align-items: center;\n  gap: 7px;\n  font-family: var(--font);\n  font-size: 11px;\n  font-weight: 600;\n  color: rgba(255,255,255,0.85);\n}\n.leg-dot {\n  width: 10px; height: 10px; border-radius: 50%;\n  border: 1.5px solid rgba(255,255,255,0.5);\n  flex-shrink: 0;\n}\n.leg-blue { background: #2BBDE0; }\n.leg-red  { background: #E53E3E; }\n.leg-gray { background: #A0A8C0; }\n\n\/* \u2500\u2500\u2500 Card \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-card {\n  background: var(--white);\n  border-radius: var(--radius-lg);\n  border: 1.5px solid var(--gray-100);\n  box-shadow: var(--shadow-card);\n  overflow: hidden;\n  display: flex;\n  flex-direction: column;\n  transition: box-shadow var(--transition), transform var(--transition), border-color var(--transition);\n}\n.wm-card:hover {\n  box-shadow: var(--shadow-hover);\n  transform: translateY(-3px);\n  border-color: var(--cyan-mid);\n}\n\n\/* top accent bar \u2014 colored by distance tier *\/\n.wm-card-accent {\n  height: 4px;\n  background: var(--gray-200);\n}\n.wm-card.near .wm-card-accent   { background: linear-gradient(90deg, var(--brand-blue), var(--cyan)); }\n.wm-card.medium .wm-card-accent { background: linear-gradient(90deg, var(--cyan), var(--teal)); }\n.wm-card.far .wm-card-accent    { background: linear-gradient(90deg, var(--gray-200), var(--gray-300)); }\n\n.wm-card-body {\n  padding: 12px 14px 8px;\n  flex: 1;\n}\n\n.wm-card-top {\n  display: flex;\n  justify-content: space-between;\n  align-items: flex-start;\n  gap: 8px;\n  margin-bottom: 6px;\n}\n\n.wm-card-name {\n  font-size: 13px;\n  font-weight: 700;\n  color: var(--navy);\n  line-height: 1.3;\n}\n\n.wm-dist-pill {\n  flex-shrink: 0;\n  font-size: 10px;\n  font-weight: 700;\n  padding: 2px 8px;\n  border-radius: 50px;\n  background: var(--cyan-soft);\n  color: #0878A0;\n  white-space: nowrap;\n}\n.wm-dist-pill.far-pill {\n  background: var(--gray-100);\n  color: var(--gray-400);\n}\n\n.wm-card-address {\n  font-size: 11.5px;\n  font-weight: 400;\n  color: var(--gray-600);\n  line-height: 1.5;\n  margin-bottom: 6px;\n}\n\n\/* \u2500\u2500\u2500 Info Row \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-info-row {\n  display: flex;\n  flex-direction: column;\n  gap: 3px;\n  margin-bottom: 8px;\n}\n\n.wm-info-item {\n  display: flex;\n  align-items: center;\n  gap: 5px;\n  font-size: 11px;\n  font-weight: 500;\n  color: var(--gray-600);\n}\n.wm-info-item svg { flex-shrink: 0; opacity: 0.6; }\n\n\/* \u2500\u2500\u2500 Service Chips \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-chips {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 4px;\n  margin-bottom: 8px;\n}\n\n.chip {\n  display: inline-flex;\n  align-items: center;\n  gap: 4px;\n  font-size: 10px;\n  font-weight: 700;\n  padding: 3px 8px;\n  border-radius: 50px;\n  line-height: 1;\n  letter-spacing: 0.1px;\n}\n\n.chip-wdf {\n  background: var(--cyan-soft);\n  color: #0878A0;\n}\n.chip-wdf-min {\n  background: #EEF3FF;\n  color: var(--navy-mid);\n}\n.chip-sameday {\n  background: var(--orange-soft);\n  color: #B84D1A;\n}\n.chip-24hr {\n  background: var(--teal-soft);\n  color: #0A7A6E;\n}\n.chip-hours {\n  background: var(--green-soft);\n  color: #0D7048;\n}\n\n\/* \u2500\u2500\u2500 Card Footer \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-card-footer {\n  padding: 8px 14px 10px;\n  border-top: 1px solid var(--gray-100);\n  display: flex;\n  gap: 6px;\n  flex-wrap: wrap;\n}\n\n.wm-btn {\n  flex: 1;\n  padding: 7px 8px;\n  border-radius: 50px;\n  font-family: var(--font);\n  font-size: 11px;\n  font-weight: 700;\n  text-align: center;\n  text-decoration: none;\n  border: none;\n  cursor: pointer;\n  transition: all var(--transition);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 4px;\n  letter-spacing: 0.1px;\n  white-space: nowrap;\n}\n.wm-btn svg { width: 10px; height: 10px; flex-shrink: 0; }\n.wm-btn-primary {\n  background: var(--cyan);\n  color: var(--white);\n  box-shadow: 0 2px 8px rgba(43,189,224,0.25);\n}\n.wm-btn-primary:hover {\n  background: var(--cyan-hover);\n  transform: translateY(-1px);\n}\n.wm-btn-secondary {\n  background: var(--gray-50);\n  color: var(--gray-600);\n  border: 1.5px solid var(--gray-200);\n}\n.wm-btn-secondary:hover {\n  background: var(--gray-100);\n  color: var(--navy);\n  border-color: var(--gray-300);\n}\n\n\/* \u2500\u2500\u2500 Closest Location Banner \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-closest-banner {\n  grid-column: 1 \/ -1;\n  background: linear-gradient(135deg, var(--navy) 0%, var(--navy-mid) 60%, var(--brand-blue) 100%);\n  border-radius: var(--radius-md);\n  padding: 12px 14px;\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  margin-bottom: 4px;\n  box-shadow: 0 4px 20px rgba(11,31,74,0.20);\n  animation: bannerFadeIn 0.4s ease;\n}\n@keyframes bannerFadeIn {\n  from { opacity: 0; transform: translateY(-8px); }\n  to   { opacity: 1; transform: translateY(0); }\n}\n.wm-closest-icon {\n  width: 34px; height: 34px;\n  background: rgba(255,255,255,0.12);\n  border-radius: 50%;\n  display: flex; align-items: center; justify-content: center;\n  flex-shrink: 0;\n}\n.wm-closest-icon svg { color: var(--cyan); width: 16px; height: 16px; }\n.wm-closest-body { flex: 1; min-width: 0; }\n.wm-closest-label {\n  font-size: 9px;\n  font-weight: 700;\n  letter-spacing: 0.8px;\n  text-transform: uppercase;\n  color: var(--cyan);\n  margin-bottom: 2px;\n}\n.wm-closest-name {\n  font-size: 13px;\n  font-weight: 700;\n  color: #fff;\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n  margin-bottom: 1px;\n}\n.wm-closest-addr {\n  font-size: 11px;\n  color: rgba(255,255,255,0.65);\n  white-space: nowrap;\n  overflow: hidden;\n  text-overflow: ellipsis;\n}\n.wm-closest-dist { text-align: right; flex-shrink: 0; }\n.wm-closest-miles {\n  font-size: 18px;\n  font-weight: 800;\n  color: #fff;\n  line-height: 1;\n  letter-spacing: -0.5px;\n}\n.wm-closest-miles-label {\n  font-size: 9px;\n  font-weight: 600;\n  color: rgba(255,255,255,0.55);\n  letter-spacing: 0.5px;\n  text-transform: uppercase;\n  margin-top: 2px;\n}\n\n\/* \u2500\u2500\u2500 No Results \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-empty {\n  grid-column: 1 \/ -1;\n  text-align: center;\n  padding: 60px 20px;\n  color: var(--gray-400);\n}\n.wm-empty svg { opacity: 0.25; margin-bottom: 14px; display: block; margin-left: auto; margin-right: auto; }\n.wm-empty p { font-size: 15px; font-weight: 500; }\n.wm-empty span { font-size: 13px; font-weight: 400; display: block; margin-top: 4px; }\n\n\/* \u2500\u2500\u2500 Coming Soon Cards \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n.wm-card.coming-soon {\n  opacity: 0.80;\n  border-style: dashed;\n  border-color: var(--gray-200);\n}\n.wm-card.coming-soon .wm-card-accent {\n  background: repeating-linear-gradient(90deg,#c8cfe8 0px,#c8cfe8 8px,transparent 8px,transparent 16px);\n}\n.wm-card.coming-soon:hover { opacity:1; box-shadow:0 4px 18px rgba(18,22,43,0.08); }\n.chip-soon      { background: var(--purple-soft); color: var(--purple); }\n.chip-soon-city { background: var(--amber-soft);  color: var(--amber); }\n\n.wm-section-label {\n  grid-column: 1 \/ -1;\n  display: flex;\n  align-items: center;\n  gap: 10px;\n  padding: 8px 0 2px;\n}\n.wm-section-label span {\n  font-size: 10px;\n  font-weight: 800;\n  letter-spacing: 1.2px;\n  text-transform: uppercase;\n  color: var(--brand-blue);\n  white-space: nowrap;\n}\n.wm-section-label::after {\n  content: '';\n  flex: 1;\n  height: 1px;\n  background: linear-gradient(90deg, var(--cyan-mid), transparent);\n}\n\n\/* \u2500\u2500\u2500 Responsive \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n@media (max-width: 1200px) {\n  .wm-body { grid-template-columns: minmax(0, 45%) 1fr; }\n}\n@media (max-width: 860px) {\n  .wm-body { grid-template-columns: 1fr; height: auto; overflow: visible; }\n  .wm-panel { height: auto; max-height: none; }\n  .wm-map-panel { order: -1; height: 360px; border-left: none; border-bottom: 1px solid var(--gray-200); }\n  #wm-map { height: 360px; }\n}\n@media (max-width: 600px) {\n  .wm-hero { padding: 20px 16px 18px; }\n  .wm-hero h2 { font-size: 20px; }\n  .wm-toolbar { padding: 6px 12px; }\n  .wm-panel { padding: 10px 10px 40px; }\n  .wm-map-panel { height: 280px; }\n  #wm-map { height: 280px; }\n  .wm-zip-row { border-radius: 10px; flex-direction: column; }\n  .wm-zip-input, .wm-zip-btn { border-radius: 0; }\n  .wm-toggle-btn span { display: none; }\n}\n\n<\/style>\n<\/head>\n<body>\n\n<!-- \u2500\u2500 Hero Search \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<div class=\"wm-hero\">\n  <h2>Find a\n    <!-- LOGO: update src to \/wp-content\/uploads\/2026\/03\/logo-wavemax.png when going live -->\n    <img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/wavemaxlaundry.com\/wp-content\/uploads\/2026\/03\/logo-wavemax.png\"\n         alt=\"WaveMAX\"\n         class=\"wm-hero-logo\"\n         onerror=\"this.style.display='none';document.getElementById('wm-logo-fallback').style.display='inline';\">\n    <span id=\"wm-logo-fallback\" style=\"display:none;color:var(--cyan);\">WaveMAX<\/span>\n    Near You\n  <\/h2>\n  <p>Professional laundry service \u00b7 7am to 10pm Daily<\/p>\n  <div class=\"wm-zip-row\">\n    <input class=\"wm-zip-input\" id=\"zipInput\" type=\"text\" maxlength=\"10\"\n      placeholder=\"Enter your zip code\u2026\"\n      oninput=\"onZipInput(this.value)\"\n      onkeydown=\"if(event.key==='Enter')searchByZip()\">\n    <button class=\"wm-zip-btn\" onclick=\"searchByZip()\">Find Locations<\/button>\n  <\/div>\n  <div class=\"wm-zip-status\" id=\"statusMsg\">\n    <span class=\"wm-clock\" id=\"statusClock\">\n      <svg width=\"18\" height=\"18\" viewBox=\"0 0 20 20\">\n        <circle cx=\"10\" cy=\"10\" r=\"8.5\"\/>\n        <circle class=\"clock-face\" cx=\"10\" cy=\"10\" r=\"8.5\"\/>\n        <line class=\"clock-hour\" x1=\"10\" y1=\"10\" x2=\"10\" y2=\"5.5\"\/>\n        <line class=\"clock-min\"  x1=\"10\" y1=\"10\" x2=\"10\" y2=\"3.5\"\/>\n        <circle cx=\"10\" cy=\"10\" r=\"1\" fill=\"white\"\/>\n      <\/svg>\n    <\/span>\n    <span class=\"wm-clock-pulse\" id=\"statusText\">Finding your nearest location\u2026<\/span>\n  <\/div>\n<\/div>\n\n<!-- \u2500\u2500 Toolbar \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<div class=\"wm-toolbar\">\n  <select class=\"wm-filter-select\" id=\"stateFilter\" onchange=\"renderGrid()\">\n    <option value=\"\">All States<\/option>\n  <\/select>\n\n  <button class=\"wm-toggle-btn\" id=\"wdfBtn\" onclick=\"toggleFilter('wdf')\">\n    <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 14.5v-9l6 4.5-6 4.5z\"\/><\/svg>\n    WDF Available\n  <\/button>\n\n  <button class=\"wm-toggle-btn\" id=\"samedayBtn\" onclick=\"toggleFilter('sameday')\">\n    <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><circle cx=\"12\" cy=\"12\" r=\"10\"\/><polyline points=\"12 6 12 12 16 14\"\/><\/svg>\n    Same-Day Pickup\n  <\/button>\n\n  <button class=\"wm-toggle-btn\" id=\"hideSoonBtn\" onclick=\"toggleFilter('hidesoon')\">\n    <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M17.94 17.94A10.07 10.07 0 0 1 12 20c-7 0-11-8-11-8a18.45 18.45 0 0 1 5.06-5.94\"\/><path d=\"M9.9 4.24A9.12 9.12 0 0 1 12 4c7 0 11 8 11 8a18.5 18.5 0 0 1-2.16 3.19\"\/><line x1=\"1\" y1=\"1\" x2=\"23\" y2=\"23\"\/><\/svg>\n    Hide Coming Soon\n  <\/button>\n\n  <div class=\"wm-results-count\" id=\"resultsCount\"><\/div>\n<\/div>\n\n<!-- \u2500\u2500 Split Body \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 -->\n<div class=\"wm-body\">\n  <!-- Left: scrollable cards -->\n  <div class=\"wm-panel\">\n    <div class=\"wm-grid\" id=\"locationGrid\"><\/div>\n  <\/div>\n  <!-- Right: map -->\n  <div class=\"wm-map-panel\">\n    <div id=\"wm-map\"><\/div>\n    <div class=\"wm-map-overlay\">\n      <div class=\"wm-map-status\" id=\"mapStatus\"><\/div>\n      <div class=\"wm-map-legend\">\n        <div class=\"wm-map-legend-row\"><span class=\"leg-dot leg-blue\"><\/span> Open location<\/div>\n        <div class=\"wm-map-legend-row\"><span class=\"leg-dot leg-red\"><\/span> Nearest to you<\/div>\n        <div class=\"wm-map-legend-row\"><span class=\"leg-dot leg-gray\"><\/span> Coming soon<\/div>\n      <\/div>\n      <button class=\"wm-nearest-btn\" id=\"nearestBtn\" onclick=\"goToNearest()\">\n        <svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n          <circle cx=\"12\" cy=\"12\" r=\"3\"\/>\n          <path d=\"M12 2v3M12 19v3M2 12h3M19 12h3\"\/>\n          <path d=\"M5.64 5.64l2.12 2.12M16.24 16.24l2.12 2.12M5.64 18.36l2.12-2.12M16.24 7.76l2.12-2.12\"\/>\n        <\/svg>\n        Find My Nearest Location\n      <\/button>\n    <\/div>\n  <\/div>\n  <\/div>\n<\/div>\n\n<script>\n\/* ============================================================\n   LOCATIONS\n   Easy to update: each object = one CSV row.\n   Fields: n=name, a=address, c=city, s=state, z=zip,\n   p=phone, lat\/lng=coords, sl=slug,\n   wdf=price\/lb, wdfMin=min order lbs,\n   sdp=same-day price\/lb, turnaround=turnaround string\n   pickup = true if same-day available (set from turnaround field)\n   ============================================================ *\/\nconst LOCATIONS = [\n  {n:\"Arlington TX\",        a:\"2823 W Park Row Dr\",                  c:\"Arlington\",       s:\"TX\",z:\"76013\",p:\"(682) 323-4466\",  lat:32.7221437, lng:-97.1523044,  sl:\"arlington-tx\",                  wdf:1.79, wdfMin:25,   sdp:null, pickup:true},\n  {n:\"Aurora CO\",           a:\"15087 E Mississippi Ave\",              c:\"Aurora\",          s:\"CO\",z:\"80012\",p:\"(303) 997-9465\",  lat:39.6979138, lng:-104.8125625, sl:\"aurora-co\",                     wdf:1.59, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Austin TX\",           a:\"825 E Rundberg Ln f1\",                 c:\"Austin\",          s:\"TX\",z:\"78753\",p:\"(512) 553-1674\",  lat:30.3564789, lng:-97.6858016,  sl:\"austin-tx\",                     wdf:1.20, wdfMin:null, sdp:null, pickup:true},\n  {n:\"Bedford TX\",          a:\"2301 N Central Drive, Suite I\",        c:\"Bedford\",         s:\"TX\",z:\"76021\",p:\"(817) 494-1599\",  lat:32.844017,  lng:-97.1430671,  sl:\"bedford-tx\",                    wdf:1.25, wdfMin:null, sdp:null, pickup:true},\n  {n:\"Boulder CO\",          a:\"2614 Broadway Ave.\",                   c:\"Boulder\",         s:\"CO\",z:\"80304\",p:\"\",               lat:40.0246504, lng:-105.2811771, sl:\"boulder-co\",                    wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Boynton Beach FL\",    a:\"1550 North Federal Highway, Suite 1\",  c:\"Boynton Beach\",   s:\"FL\",z:\"33435\",p:\"(561) 389-0432\",  lat:26.5405885, lng:-80.0567903,  sl:\"boynton-fl\",                    wdf:1.55, wdfMin:15,   sdp:null, pickup:false},\n  {n:\"Phoenix AZ Camelback\",a:\"1615 W Camelback Rd Suite 101\",        c:\"Phoenix\",         s:\"AZ\",z:\"85015\",p:\"(480) 284-4001\",  lat:33.509219,  lng:-112.094323,  sl:\"camelback-phoenix-az\",           wdf:1.49, wdfMin:null, sdp:null, pickup:true},\n  {n:\"Carrollton TX\",       a:\"2760 E Trinity Mills Rd. STE 112\",     c:\"Carrollton\",      s:\"TX\",z:\"75006\",p:\"(214) 731-6575\",  lat:32.9858894, lng:-96.8573072,  sl:\"carrollton-tx\",                 wdf:1.39, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Chandler AZ\",         a:\"2081 N Arizona Ave #129\",              c:\"Chandler\",        s:\"AZ\",z:\"85225\",p:\"(480) 275-5535\",  lat:33.337383,  lng:-111.8411181, sl:\"chandler-az\",                   wdf:1.99, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Charlotte NC\",        a:\"2915 The Plaza Suite B\",               c:\"Charlotte\",       s:\"NC\",z:\"28205\",p:\"(704) 910-1587\",  lat:35.2377778, lng:-80.800706,   sl:\"charlotte-nc\",                  wdf:1.75, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Chicago IL\",          a:\"4830 W Diversey Ave\",                  c:\"Chicago\",         s:\"IL\",z:\"60639\",p:\"(773) 853-0246\",  lat:41.9318183, lng:-87.7481109,  sl:\"chicago-il\",                    wdf:1.90, wdfMin:null, sdp:null, pickup:true},\n  {n:\"Cincinnati OH\",       a:\"8449 Colerain Ave\",                    c:\"Cincinnati\",      s:\"OH\",z:\"45239\",p:\"(513) 445-9480\",  lat:39.2228567, lng:-84.5877329,  sl:\"cincinnati-oh\",                 wdf:1.25, wdfMin:15,   sdp:1.70, pickup:true},\n  {n:\"Colorado Springs CO\", a:\"609 South Circle Drive\",               c:\"Colorado Springs\",s:\"CO\",z:\"80910\",p:\"(719) 639-9013\",  lat:38.8231559, lng:-104.7800747, sl:\"colorado-springs-co\",            wdf:1.25, wdfMin:12.50,sdp:null, pickup:false},\n  {n:\"Dallas TX\",           a:\"3220 West Davis St.\",                  c:\"Dallas\",          s:\"TX\",z:\"75211\",p:\"\",               lat:32.7491405, lng:-96.8735156,  sl:\"dallas-tx\",                     wdf:1.25, wdfMin:null, sdp:1.75, pickup:true},\n  {n:\"Denver CO E Hampden\", a:\"10005-A E Hampden Ave\",                c:\"Denver\",          s:\"CO\",z:\"80231\",p:\"\",               lat:39.6536749, lng:-104.8710931, sl:\"e-hampden-denver-co\",            wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Dundalk MD\",          a:\"1774 Merritt Blvd\",                    c:\"Dundalk\",         s:\"MD\",z:\"21222\",p:\"(443) 216-7091\",  lat:39.2677417, lng:-76.5046847,  sl:\"dundalk-md\",                    wdf:1.35, wdfMin:15,   sdp:null, pickup:true},\n  {n:\"Durham NC\",           a:\"3222 Guess Road\",                      c:\"Durham\",          s:\"NC\",z:\"27705\",p:\"(919) 748-3180\",  lat:36.0374563, lng:-78.9289568,  sl:\"durham-nc\",                     wdf:1.50, wdfMin:15,   sdp:null, pickup:false},\n  {n:\"Fargo ND\",            a:\"2551 45th St S, Suite 147\",            c:\"Fargo\",           s:\"ND\",z:\"58104\",p:\"(701) 707-0621\",  lat:46.8771863, lng:-96.7898034,  sl:\"fargo-nd\",                      wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Fort Worth TX\",       a:\"6538 Meadowbrook Dr\",                  c:\"Fort Worth\",      s:\"TX\",z:\"76112\",p:\"(682) 301-1112\",  lat:32.7454183, lng:-97.217446,   sl:\"fort-worth-tx\",                 wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Ft. Walton Beach FL\", a:\"\",                                     c:\"Walton Beach\",    s:\"FL\",z:\"\",     p:\"\",               lat:27.6648274, lng:-81.5157535,  sl:\"ft-walton-beach-fl\",             wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Gaithersburg MD\",     a:\"215 Muddy Branch Rd\",                  c:\"Gaithersburg\",    s:\"MD\",z:\"20878\",p:\"(240) 477-4467\",  lat:39.128241,  lng:-77.204965,   sl:\"gaithersburg-md\",               wdf:1.35, wdfMin:15,   sdp:1.75, pickup:true},\n  {n:\"Garland TX\",          a:\"1311 Marketplace Dr, Suite 100\",       c:\"Garland\",         s:\"TX\",z:\"75041\",p:\"(214) 954-7063\",  lat:32.8598327, lng:-96.6463165,  sl:\"garland-tx\",                    wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Phoenix AZ N 35th\",   a:\"6767 N 35th Ave\",                      c:\"Phoenix\",         s:\"AZ\",z:\"85017\",p:\"(602) 246-3352\",  lat:33.5365912, lng:-112.1338481, sl:\"glendale-phoenix-az\",            wdf:1.89, wdfMin:15,   sdp:2.29, pickup:true},\n  {n:\"Phoenix AZ Greenway\", a:\"3202 E Greenway Rd #1307\",             c:\"Phoenix\",         s:\"AZ\",z:\"85032\",p:\"(480) 788-8246\",  lat:33.6284103, lng:-112.0129283, sl:\"greenway-phoenix-az\",            wdf:1.75, wdfMin:null, sdp:null, pickup:true},\n  {n:\"Haltom City TX\",      a:\"3534 Denton Hwy.\",                     c:\"Haltom City\",     s:\"TX\",z:\"76117\",p:\"\",               lat:32.8145157, lng:-97.2639357,  sl:\"haltomcity-tx\",                 wdf:1.39, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Houston TX\",          a:\"2611 Fulton Street\",                   c:\"Houston\",         s:\"TX\",z:\"77009\",p:\"(713) 485-4726\",  lat:29.7861988, lng:-95.3616853,  sl:\"houston-tx\",                    wdf:0.99, wdfMin:12.50,sdp:null, pickup:false},\n  {n:\"Indianapolis IN\",     a:\"2814 W 71st Street\",                   c:\"Indianapolis\",    s:\"IN\",z:\"46268\",p:\"(317) 388-5954\",  lat:39.886495,  lng:-86.2090994,  sl:\"indianapolis-in\",               wdf:1.50, wdfMin:null, sdp:1.75, pickup:false},\n  {n:\"Inkster MI\",          a:\"27125 Cherry Hill Rd.\",                c:\"Inkster\",         s:\"MI\",z:\"48141\",p:\"(313) 914-7611\",  lat:42.3112796, lng:-83.306822,   sl:\"inkster-mi\",                    wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"San Antonio TX Jackson Keller\",a:\"2525 Jackson Keller Rd.\",     c:\"San Antonio\",     s:\"TX\",z:\"78230\",p:\"(210) 314-3699\",  lat:29.5195343, lng:-98.533009,   sl:\"jackson-keller-san-antonio-tx\",  wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Jacksonville FL\",     a:\"929 McDuff Ave S\",                     c:\"Jacksonville\",    s:\"FL\",z:\"32205\",p:\"(904) 388-1717\",  lat:30.3141253, lng:-81.7046708,  sl:\"jacksonvillefl\",                wdf:1.35, wdfMin:15,   sdp:null, pickup:false},\n  {n:\"Kent WA\",             a:\"25642 Pacific Hwy S #16\",              c:\"Kent\",            s:\"WA\",z:\"98032\",p:\"(206) 212-6062\",  lat:47.3716584, lng:-122.301707,  sl:\"kent-wa\",                       wdf:null, wdfMin:null, sdp:null, pickup:true},\n  {n:\"Knoxville TN\",        a:\"5210A Schubert Rd.\",                   c:\"Knoxville\",       s:\"TN\",z:\"37912\",p:\"(865) 985-0152\",  lat:36.0072664, lng:-83.9753982,  sl:\"knoxville-tn\",                  wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Lakewood CO\",         a:\"1090 S Wadsworth Blvd\",                c:\"Lakewood\",        s:\"CO\",z:\"80226\",p:\"\",               lat:39.6971718, lng:-105.0811776, sl:\"lakewood-co\",                   wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Lansing MI\",          a:\"4929 S Cedar St, St 1\",                c:\"Lansing\",         s:\"MI\",z:\"48910\",p:\"(517) 300-9283\",  lat:42.6844838, lng:-84.547404,   sl:\"lansing-mi\",                    wdf:1.35, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Long Beach CA\",       a:\"601 E 7th Street\",                     c:\"Long Beach\",      s:\"CA\",z:\"90813\",p:\"(562) 269-0509\",  lat:33.7756678, lng:-118.1846958, sl:\"long-beach-ca\",                 wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Louisville KY\",       a:\"1056 E Oak St\",                        c:\"Louisville\",      s:\"KY\",z:\"40204\",p:\"(502) 292-8895\",  lat:38.2330933, lng:-85.7343741,  sl:\"louisville-ky\",                 wdf:1.25, wdfMin:12.50,sdp:null, pickup:false},\n  {n:\"Maple Heights OH\",    a:\"20980 Libby Rd\",                       c:\"Maple Heights\",   s:\"OH\",z:\"44137\",p:\"(216) 800-1900\",  lat:41.4161747, lng:-81.5335666,  sl:\"maple-heights-oh\",              wdf:1.39, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Marietta GA\",         a:\"270 Cobb Pkwy S, Suite 90\",            c:\"Marietta\",        s:\"GA\",z:\"30060\",p:\"(770) 871-1870\",  lat:33.9452971, lng:-84.5193516,  sl:\"marietta-ga\",                   wdf:1.35, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Mesa AZ Broadway\",    a:\"2639 E Broadway Rd, St. 101\",          c:\"Mesa\",            s:\"AZ\",z:\"85204\",p:\"(480) 687-7330\",  lat:33.4064206, lng:-111.7724587, sl:\"mesa-az-broadway\",              wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Mesa AZ West Main\",   a:\"1932 West Main Street #B103\",          c:\"Mesa\",            s:\"AZ\",z:\"85201\",p:\"(480) 687-5193\",  lat:33.4162402, lng:-111.8724942, sl:\"mesa-az-west-main\",             wdf:1.49, wdfMin:15,   sdp:1.99, pickup:true},\n  {n:\"Mesquite TX\",         a:\"529 N. Galloway Ave ste. 16\",          c:\"Mesquite\",        s:\"TX\",z:\"75149\",p:\"(214) 758-0802\",  lat:32.7724933, lng:-96.6006767,  sl:\"mesquite-tx\",                   wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Midvale UT\",          a:\"48 W 7200 S\",                          c:\"Midvale\",         s:\"UT\",z:\"84047\",p:\"(385) 557-4665\",  lat:40.6208923, lng:-111.8924364, sl:\"midvale-ut\",                    wdf:1.49, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Mission TX\",          a:\"301 W Griffin Pkwy\",                   c:\"Mission\",         s:\"TX\",z:\"78572\",p:\"(214) 914-3134\",  lat:26.2294058, lng:-98.3262445,  sl:\"mission-tx\",                    wdf:1.25, wdfMin:12.50,sdp:null, pickup:false},\n  {n:\"Moreno Valley CA\",    a:\"22275 Alessandro Blvd\",                c:\"Moreno Valley\",   s:\"CA\",z:\"92553\",p:\"(951) 221-6435\",  lat:33.9166352, lng:-117.2736279, sl:\"moreno-valley-ca\",              wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"New Haven CT\",        a:\"466 Foxon Blvd\",                       c:\"New Haven\",       s:\"CT\",z:\"06513\",p:\"(203) 745-3477\",  lat:41.3205721, lng:-72.8703168,  sl:\"new-haven-ct\",                  wdf:null, wdfMin:null, sdp:null, pickup:true},\n  {n:\"New Orleans LA\",      a:\"4001 Frenchmen St\",                    c:\"New Orleans\",     s:\"LA\",z:\"70122\",p:\"(504) 881-7239\",  lat:29.9991365, lng:-90.0602455,  sl:\"new-orleans-la\",                wdf:1.49, wdfMin:15,   sdp:null, pickup:true},\n  {n:\"Oklahoma City OK\",    a:\"6125 S. May Avenue\",                   c:\"Oklahoma City\",   s:\"OK\",z:\"73159\",p:\"(405) 724-9935\",  lat:35.4048182, lng:-97.5672801,  sl:\"oklahoma-city-ok\",              wdf:0.99, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Omaha NE\",            a:\"5092 Ames Ave\",                        c:\"Omaha\",           s:\"NE\",z:\"68104\",p:\"(531) 466-2847\",  lat:41.2998846, lng:-95.9924104,  sl:\"omaha\",                         wdf:1.10, wdfMin:15,   sdp:null, pickup:true},\n  {n:\"Peoria AZ\",           a:\"7575 W Peoria Ave, A102\",              c:\"Peoria\",          s:\"AZ\",z:\"85345\",p:\"(602) 368-6227\",  lat:33.5799649, lng:-112.2217737, sl:\"peoria-az\",                     wdf:1.49, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Phoenix AZ Thomas\",   a:\"6544 West Thomas Road Suite #23\",      c:\"Phoenix\",         s:\"AZ\",z:\"85033\",p:\"(623) 302-7226\",  lat:33.4814063, lng:-112.2017913, sl:\"phoenix-az\",                    wdf:1.89, wdfMin:15,   sdp:2.29, pickup:true},\n  {n:\"Phoenix AZ N 16th\",   a:\"2825 N 16th St Suite 1\",               c:\"Phoenix\",         s:\"AZ\",z:\"85006\",p:\"(602) 314-5706\",  lat:33.4790384, lng:-112.04698,   sl:\"phoenix-az-n-16\",               wdf:1.49, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Plano TX Parker\",     a:\"930 Parker Rd., Suite 520\",            c:\"Plano\",           s:\"TX\",z:\"75075\",p:\"(214) 764-8811\",  lat:33.0400202, lng:-96.7154002,  sl:\"plano-parker-tx\",               wdf:2.09, wdfMin:null, sdp:null, pickup:true},\n  {n:\"Plano TX\",            a:\"3611 14th Street, Suite 170\",          c:\"Plano\",           s:\"TX\",z:\"75074\",p:\"(469) 298-3230\",  lat:33.0145486, lng:-96.6641983,  sl:\"plano-tx\",                      wdf:1.25, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Dallas TX Preston & Alpha\",a:\"13536 Preston Rd, Suite 100\",     c:\"Dallas\",          s:\"TX\",z:\"75240\",p:\"(214) 272-3252\",  lat:32.9317851, lng:-96.8026865,  sl:\"preston-alpha-dallas-tx\",        wdf:1.25, wdfMin:12.50,sdp:null, pickup:false},\n  {n:\"Raleigh NC\",          a:\"1100 North Raleigh Blvd, Suite 143\",   c:\"Raleigh\",         s:\"NC\",z:\"27610\",p:\"(919) 301-8054\",  lat:35.7939581, lng:-78.6113447,  sl:\"raleigh-nc\",                    wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Rockville MD\",        a:\"180 Rollins Ave\",                      c:\"Rockville\",       s:\"MD\",z:\"20852\",p:\"(667) 481-7581\",  lat:39.0584031, lng:-77.1259547,  sl:\"rockville-md\",                  wdf:1.39, wdfMin:null, sdp:1.79, pickup:true},\n  {n:\"Sacramento CA\",       a:\"2428 Watt Ave\",                        c:\"Sacramento\",      s:\"CA\",z:\"95825\",p:\"(916) 873-3017\",  lat:38.608961,  lng:-121.3816339, sl:\"sacramento-ca\",                 wdf:1.39, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Saint Petersburg FL\", a:\"11138 4th St N\",                       c:\"St. Petersburg\",  s:\"FL\",z:\"33716\",p:\"(727) 800-2188\",  lat:27.8735748, lng:-82.6401272,  sl:\"saint-petersburg-fl\",            wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"San Antonio TX Babcock\",a:\"1552 Babcock Rd\",                    c:\"San Antonio\",     s:\"TX\",z:\"78229\",p:\"(210) 236-5035\",  lat:29.4915459, lng:-98.5698986,  sl:\"san-antonio-tx\",                wdf:1.25, wdfMin:15,   sdp:null, pickup:false},\n  {n:\"San Antonio TX Marbach\",a:\"7519 Marbach St, Ste 102\",           c:\"San Antonio\",     s:\"TX\",z:\"78227\",p:\"\",               lat:29.4183503, lng:-98.6369858,  sl:\"san-antonio-tx-marbach-st\",      wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Southgate MI\",        a:\"13248 Fort St\",                        c:\"Southgate\",       s:\"MI\",z:\"48195\",p:\"(734) 258-8026\",  lat:42.2116841, lng:-83.1813753,  sl:\"southgate-mi\",                  wdf:1.50, wdfMin:null, sdp:2.10, pickup:true},\n  {n:\"St. Paul MN\",         a:\"1320 Maryland Ave E\",                  c:\"St. Paul\",        s:\"MN\",z:\"55106\",p:\"(651) 528-6972\",  lat:44.9772765, lng:-93.044675,   sl:\"st-paul-mn\",                    wdf:1.49, wdfMin:15,   sdp:null, pickup:true},\n  {n:\"Tempe AZ\",            a:\"222 E Baseline Rd\",                    c:\"Tempe\",           s:\"AZ\",z:\"85283\",p:\"(480) 572-1048\",  lat:33.3786313, lng:-111.9354421, sl:\"tempe-az\",                      wdf:0.99, wdfMin:12.50,sdp:null, pickup:false},\n  {n:\"Tempe AZ Broadway\",   a:\"1845 E Broadway Rd #114\",              c:\"Tempe\",           s:\"AZ\",z:\"85282\",p:\"\",               lat:33.4067448, lng:-111.907583,  sl:\"tempe-az-broadway\",             wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Temple TX\",           a:\"1516 S 31st St B\",                     c:\"Temple\",          s:\"TX\",z:\"76504\",p:\"(254) 228-5959\",  lat:31.0861423, lng:-97.3634679,  sl:\"temple-tx\",                     wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Temple TX Olaf Dr\",   a:\"10 Olaf Dr.\",                          c:\"Temple\",          s:\"TX\",z:\"76504\",p:\"(254) 295-0239\",  lat:31.1102376, lng:-97.3861205,  sl:\"temple-tx-olaf-dr\",             wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"The Colony TX\",       a:\"6805 Main St\",                         c:\"The Colony\",      s:\"TX\",z:\"75056\",p:\"(469) 200-8400\",  lat:33.0968985, lng:-96.8942962,  sl:\"thecolony\",                     wdf:1.39, wdfMin:null, sdp:null, pickup:true},\n  {n:\"Thornton CO\",         a:\"720 W 84th Ave.\",                      c:\"Thornton\",        s:\"CO\",z:\"80260\",p:\"\",               lat:39.8481973, lng:-104.9954769, sl:\"thornton-co\",                   wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Tucson AZ\",           a:\"4054 E. 22nd Street\",                  c:\"Tucson\",          s:\"AZ\",z:\"85711\",p:\"(520) 333-2053\",  lat:32.2059548, lng:-110.9057272, sl:\"tucson-az\",                     wdf:1.79, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Tustin CA\",           a:\"412 E 1st St\",                         c:\"Tustin\",          s:\"CA\",z:\"92780\",p:\"(855) 492-8362\",  lat:33.7454074, lng:-117.8198178, sl:\"tustin-ca\",                     wdf:null, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Vancouver WA\",        a:\"8109 NE Vancouver Mall Dr\",            c:\"Vancouver\",       s:\"WA\",z:\"98662\",p:\"(360) 433-9864\",  lat:45.6579932, lng:-122.5905622, sl:\"vancouver-wa\",                  wdf:1.69, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Westminster CO\",      a:\"8879 N Harlan Street\",                 c:\"Westminster\",     s:\"CO\",z:\"80031\",p:\"(303) 993-7153\",  lat:39.8579946, lng:-105.0651862, sl:\"westminster-co\",                wdf:1.59, wdfMin:null, sdp:null, pickup:false},\n  {n:\"Wyoming MI\",          a:\"1633 28th St SW unit G\",               c:\"Wyoming\",         s:\"MI\",z:\"49519\",p:\"(616) 288-6929\",  lat:42.9144945, lng:-85.7061212,  sl:\"wyoming-mi\",                    wdf:1.50, wdfMin:null, sdp:null, pickup:false},\n];\n\n\/* ============================================================\n   COMING SOON LOCATIONS\n   \"Opening Soon\" = has a real address. \"Coming Soon\" = city only.\n   To add more: append to COMING_SOON array below.\n   Fields: n=display name, a=address (blank if city-only),\n           c=city, s=state, z=zip, lat\/lng (approx city center),\n           type: \"opening\" | \"soon\"\n============================================================ *\/\nconst COMING_SOON = [\n  \/\/ Opening Soon \u2014 have real addresses\n  {n:\"Dallas TX Preston & Alpha\", a:\"13536 Preston Rd, Suite 100\", c:\"Dallas\",      s:\"TX\",z:\"75240\", lat:32.9317851, lng:-96.8026865, type:\"opening\"},\n  {n:\"Mission TX\",                a:\"301 W Griffin Pkwy\",          c:\"Mission\",     s:\"TX\",z:\"78572\", lat:26.2294058, lng:-98.3262445, type:\"opening\"},\n  {n:\"Sacramento CA\",             a:\"2428 Watt Ave\",               c:\"Sacramento\",  s:\"CA\",z:\"95825\", lat:38.608961,  lng:-121.381634, type:\"opening\"},\n  {n:\"Tempe AZ Broadway\",         a:\"1845 E Broadway Rd #114\",     c:\"Tempe\",       s:\"AZ\",z:\"85282\", lat:33.4067448, lng:-111.907583, type:\"opening\"},\n  \/\/ Coming Soon \u2014 city level only\n  {n:\"Denver CO\",        a:\"\", c:\"Denver\",              s:\"CO\",z:\"\", lat:39.7392358, lng:-104.990251,  type:\"soon\"},\n  {n:\"Nashville TN\",     a:\"\", c:\"Nashville\",           s:\"TN\",z:\"\", lat:36.1626638, lng:-86.7816016,  type:\"soon\"},\n  {n:\"Oak Lawn IL\",      a:\"\", c:\"Oak Lawn\",            s:\"IL\",z:\"\", lat:41.7197765, lng:-87.7479568,  type:\"soon\"},\n  {n:\"San Antonio TX\",   a:\"\", c:\"San Antonio\",         s:\"TX\",z:\"\", lat:29.4241219, lng:-98.4936282,  type:\"soon\"},\n  {n:\"Walton Beach FL\",  a:\"\", c:\"Fort Walton Beach\",   s:\"FL\",z:\"\", lat:30.4057083, lng:-86.6187197,  type:\"soon\"},\n  {n:\"Chicago IL\",       a:\"\", c:\"Chicago\",             s:\"IL\",z:\"\", lat:41.8781136, lng:-87.6297982,  type:\"soon\"},\n  {n:\"Cleveland OH\",     a:\"\", c:\"Cleveland\",           s:\"OH\",z:\"\", lat:41.4993428, lng:-81.6943605,  type:\"soon\"},\n  {n:\"Detroit MI\",       a:\"\", c:\"Detroit\",             s:\"MI\",z:\"\", lat:42.331427,  lng:-83.0457538,  type:\"soon\"},\n  {n:\"Kansas City MO\",   a:\"\", c:\"Kansas City\",         s:\"MO\",z:\"\", lat:39.0997265, lng:-94.5785667,  type:\"soon\"},\n  {n:\"Los Angeles CA\",   a:\"\", c:\"Los Angeles\",         s:\"CA\",z:\"\", lat:34.0522342, lng:-118.2436849, type:\"soon\"},\n  {n:\"McAllen TX\",       a:\"\", c:\"McAllen\",             s:\"TX\",z:\"\", lat:26.2034071, lng:-98.2300124,  type:\"soon\"},\n  {n:\"McKinney TX\",      a:\"\", c:\"McKinney\",            s:\"TX\",z:\"\", lat:33.1972465, lng:-96.6397777,  type:\"soon\"},\n  {n:\"Odenton MD\",       a:\"\", c:\"Odenton\",             s:\"MD\",z:\"\", lat:39.0817693, lng:-76.6996937,  type:\"soon\"},\n  {n:\"Pittsburgh PA\",    a:\"\", c:\"Pittsburgh\",          s:\"PA\",z:\"\", lat:40.4406248, lng:-79.9958864,  type:\"soon\"},\n  {n:\"Round Rock TX\",    a:\"\", c:\"Round Rock\",          s:\"TX\",z:\"\", lat:30.5082551, lng:-97.678896,   type:\"soon\"},\n  {n:\"Salt Lake City UT\",a:\"\", c:\"Salt Lake City\",      s:\"UT\",z:\"\", lat:40.7607793, lng:-111.8910474, type:\"soon\"},\n  {n:\"San Diego CA\",     a:\"\", c:\"San Diego\",           s:\"CA\",z:\"\", lat:32.715738,  lng:-117.1610838, type:\"soon\"},\n  {n:\"San Francisco Bay Area CA\",a:\"\",c:\"San Francisco\",s:\"CA\",z:\"\", lat:37.7749295, lng:-122.4194155, type:\"soon\"},\n  {n:\"Seattle WA\",       a:\"\", c:\"Seattle\",             s:\"WA\",z:\"\", lat:47.6062095, lng:-122.3320708, type:\"soon\"},\n];\n\n\/* ============================================================\n   APP \u2014 no need to edit below\n============================================================ *\/\nconst BASE = \"https:\/\/www.wavemaxlaundry.com\/\";\nlet uLat = null, uLng = null, uZip = \"\", uCity = \"\";\nlet filterWdf = false, filterPickup = false, filterHideSoon = false;\nlet nearestHighlighted = false; \/\/ only true after \"Find My Nearest Location\" is clicked\n\nfunction haversine(a,b,c,d){\n  const R=3958.8, dL=(c-a)*Math.PI\/180, dN=(d-b)*Math.PI\/180;\n  const x=Math.sin(dL\/2)**2+Math.cos(a*Math.PI\/180)*Math.cos(c*Math.PI\/180)*Math.sin(dN\/2)**2;\n  return R*2*Math.atan2(Math.sqrt(x),Math.sqrt(1-x));\n}\n\nfunction distStr(d){\n  if(d===null) return null;\n  if(d < 0.2) return Math.round(d*5280)+\" ft\";\n  return d.toFixed(1)+\" mi\";\n}\n\nfunction distClass(d){\n  if(d === null) return \"\";\n  if(d < 25) return \"near\";\n  if(d < 100) return \"medium\";\n  return \"far\";\n}\n\nfunction toggleFilter(type){\n  if(type==='wdf'){      filterWdf=!filterWdf;           document.getElementById('wdfBtn').classList.toggle('active',filterWdf); }\n  if(type==='sameday'){  filterPickup=!filterPickup;     document.getElementById('samedayBtn').classList.toggle('active',filterPickup); }\n  if(type==='hidesoon'){ filterHideSoon=!filterHideSoon; document.getElementById('hideSoonBtn').classList.toggle('active',filterHideSoon); }\n  renderGrid();\n}\n\nfunction onZipInput(v){\n  const clean = v.trim();\n  if(\/^\\d{5}$\/.test(clean)) searchByZip();\n}\n\nasync function searchByZip(){\n  const val = document.getElementById('zipInput').value.trim().replace(\/\\D\/g,'').slice(0,5);\n  if(val.length < 5){ setStatus(\"Please enter a 5-digit zip code.\"); return; }\n  setStatus(\"Finding nearest locations\u2026\", true);\n\n  \/\/ Try multiple geocoding sources in order until one works\n  const attempts = [\n\n    \/\/ 1. Zippopotam \u2014 fast, US-focused\n    async () => {\n      const r = await fetch(`https:\/\/api.zippopotam.us\/us\/${val}`, {signal: AbortSignal.timeout(5000)});\n      if(!r.ok) throw new Error('not found');\n      const d = await r.json();\n      return {\n        lat: parseFloat(d.places[0].latitude),\n        lng: parseFloat(d.places[0].longitude),\n        city: d.places[0]['place name'] + ', ' + d.places[0]['state abbreviation']\n      };\n    },\n\n    \/\/ 2. OpenStreetMap Nominatim \u2014 broad coverage\n    async () => {\n      const r = await fetch(`https:\/\/nominatim.openstreetmap.org\/search?postalcode=${val}&countrycodes=us&format=json&limit=1&addressdetails=1`, {signal: AbortSignal.timeout(6000)});\n      const d = await r.json();\n      if(!d.length) throw new Error('not found');\n      const a = d[0].address;\n      return {\n        lat: parseFloat(d[0].lat),\n        lng: parseFloat(d[0].lon),\n        city: (a.city || a.town || a.village || a.county || '') + (a.state ? ', ' + a.state : '')\n      };\n    },\n\n    \/\/ 3. geocode.maps.co \u2014 no key required, good US coverage\n    async () => {\n      const r = await fetch(`https:\/\/geocode.maps.co\/search?q=${val}&countrycodes=US&format=json&limit=1`, {signal: AbortSignal.timeout(6000)});\n      const d = await r.json();\n      if(!d.length) throw new Error('not found');\n      return {\n        lat: parseFloat(d[0].lat),\n        lng: parseFloat(d[0].lon),\n        city: d[0].display_name.split(',').slice(0,2).join(',').trim()\n      };\n    },\n\n    \/\/ 4. Direct distance match \u2014 find nearest location by zip prefix if all APIs fail\n    \/\/ This uses our own LOCATIONS data to find the nearest known zip as a fallback\n    async () => {\n      const prefix = val.slice(0,3);\n      \/\/ Find a location whose zip starts with the same 3-digit prefix\n      const match = LOCATIONS.find(l => l.z && l.z.startsWith(prefix) && l.lat);\n      if(!match) throw new Error('no prefix match');\n      return { lat: match.lat, lng: match.lng, city: `near ${match.c}, ${match.s}` };\n    }\n  ];\n\n  for(const attempt of attempts){\n    try {\n      const loc = await attempt();\n      if(!loc.lat || !loc.lng) continue;\n      uLat = loc.lat;\n      uLng = loc.lng;\n      uZip = val;\n      uCity = loc.city;\n      document.getElementById('zipInput').value = val;\n      setStatus(`Showing locations nearest to ${uCity} (${val})`);\n      renderGrid();\n      return;\n    } catch(e){ \/* try next *\/ }\n  }\n\n  setStatus(\"Zip code not found \u2014 try a nearby zip or browse all locations below.\");\n}\n\nasync function autoDetect(){\n  setStatus(\"Finding your nearest location\u2026\", true);\n\n  \/\/ Try browser GPS\/WiFi geolocation first \u2014 much more accurate than IP\n  if(navigator.geolocation){\n    navigator.geolocation.getCurrentPosition(\n      async (pos) => {\n        uLat = pos.coords.latitude;\n        uLng = pos.coords.longitude;\n        \/\/ Reverse geocode to get city\/zip for display\n        try {\n          const r = await fetch(`https:\/\/api.zippopotam.us\/us\/${uLat},${uLng}`);\n          \/\/ zippopotam doesn't support lat\/lng \u2014 use ipapi just for label\n        } catch(e){}\n        try {\n          const r = await fetch(`https:\/\/nominatim.openstreetmap.org\/reverse?lat=${uLat}&lon=${uLng}&format=json`);\n          const d = await r.json();\n          uZip = d.address?.postcode || \"\";\n          uCity = (d.address?.city || d.address?.town || d.address?.village || \"\") + (d.address?.state ? \", \" + d.address.state : \"\");\n        } catch(e){\n          uCity = \"your location\";\n        }\n        if(uZip) document.getElementById('zipInput').value = uZip;\n        setStatus(`Location detected \u00b7 click Find My Nearest Location for directions`);\n        renderGrid();\n      },\n      async (err) => {\n        \/\/ Browser geolocation denied or failed \u2014 fall back to IP\n        await detectByIP();\n      },\n      { timeout: 8000, maximumAge: 60000 }\n    );\n  } else {\n    await detectByIP();\n  }\n}\n\nasync function detectByIP(){\n  \/\/ Try 3 different free IP geolocation APIs in order \u2014 if one is blocked, next is tried\n  const apis = [\n    async () => {\n      const r = await fetch('https:\/\/ipapi.co\/json\/', {signal: AbortSignal.timeout(4000)});\n      const d = await r.json();\n      if(!d.latitude) throw new Error('no lat');\n      return { lat: d.latitude, lng: d.longitude, zip: d.postal||'', city: (d.city||'')+(d.region_code?', '+d.region_code:'') };\n    },\n    async () => {\n      const r = await fetch('https:\/\/ipwho.is\/', {signal: AbortSignal.timeout(4000)});\n      const d = await r.json();\n      if(!d.latitude) throw new Error('no lat');\n      return { lat: d.latitude, lng: d.longitude, zip: d.zip||'', city: (d.city||'')+(d.region_code?', '+d.region_code:'') };\n    },\n    async () => {\n      const r = await fetch('https:\/\/freeipapi.com\/api\/json\/', {signal: AbortSignal.timeout(4000)});\n      const d = await r.json();\n      if(!d.latitude) throw new Error('no lat');\n      return { lat: d.latitude, lng: d.longitude, zip: d.zipCode||'', city: (d.cityName||'')+(d.regionCode?', '+d.regionCode:'') };\n    }\n  ];\n\n  for(const api of apis){\n    try {\n      const loc = await api();\n      uLat = loc.lat; uLng = loc.lng;\n      uZip = loc.zip; uCity = loc.city;\n      if(uZip) document.getElementById('zipInput').value = uZip;\n      setStatus(`Location detected \u00b7 click Find My Nearest Location for directions`);\n      renderGrid();\n      return;\n    } catch(e){ \/* try next *\/ }\n  }\n\n  \/\/ All APIs failed \u2014 map already showing, just update the status message\n  setStatus(\"Enter your zip code above to find your nearest store \u2192\");\n}\n\nfunction setStatus(msg, searching = false){\n  const clock = document.getElementById('statusClock');\n  const text  = document.getElementById('statusText');\n  if(!text) return;\n  text.textContent = msg;\n  if(clock){\n    clock.style.display = searching ? 'inline-flex' : 'none';\n  }\n  \/\/ Stop pulse animation once we have a result\n  text.classList.toggle('wm-clock-pulse', searching);\n}\n\n\/* \u2500\u2500\u2500 MAP \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\nlet map = null, clusterGroup = null, nearestLayer = null;\nconst ICON_CACHE = {};\n\nfunction getIcon(type){\n  if(ICON_CACHE[type]) return ICON_CACHE[type];\n  const cfg = { blue:{c:'#2BBDE0',s:26}, red:{c:'#E53E3E',s:32}, gray:{c:'#A0A8C0',s:20} };\n  const {c,s} = cfg[type]||cfg.blue;\n  const h = Math.round(s*1.35);\n  ICON_CACHE[type] = L.divIcon({\n    html:`<svg xmlns=\"http:\/\/www.w3.org\/2000\/svg\" width=\"${s}\" height=\"${h}\" viewBox=\"0 0 28 38\"><path d=\"M14 0C6.27 0 0 6.27 0 14c0 9.33 14 24 14 24S28 23.33 28 14C28 6.27 21.73 0 14 0z\" fill=\"${c}\" stroke=\"white\" stroke-width=\"2\"\/><circle cx=\"14\" cy=\"14\" r=\"5\" fill=\"white\" opacity=\"0.9\"\/><\/svg>`,\n    className:'', iconSize:[s,h], iconAnchor:[s\/2,h], popupAnchor:[0,-h+2]\n  });\n  return ICON_CACHE[type];\n}\n\nfunction initMap(){\n  if(typeof L === 'undefined') return; \/\/ Leaflet not loaded yet \u2014 initMapWhenReady will retry\n  const el = document.getElementById('wm-map');\n  if(!el || map) return;\n\n  map = L.map('wm-map', {\n    zoomControl: true, attributionControl: false, preferCanvas: true\n  }).setView([39.5, -98.5], 4);\n\n  L.tileLayer('https:\/\/{s}.basemaps.cartocdn.com\/rastertiles\/voyager\/{z}\/{x}\/{y}{r}.png', {\n    subdomains:'abcd', maxZoom:19\n  }).addTo(map);\n\n  L.control.attribution({prefix:'\u00a9 OpenStreetMap \u00b7 CARTO'}).addTo(map);\n\n  clusterGroup = L.markerClusterGroup({\n    maxClusterRadius: 50,\n    disableClusteringAtZoom: 11,\n    showCoverageOnHover: false,\n    iconCreateFunction(cluster){\n      return L.divIcon({\n        html:`<div style=\"background:#1A3B7A;color:#fff;border-radius:50%;width:32px;height:32px;display:flex;align-items:center;justify-content:center;font-family:'Poppins',sans-serif;font-size:12px;font-weight:700;border:2px solid white;box-shadow:0 2px 8px rgba(11,31,74,.4);\">${cluster.getChildCount()}<\/div>`,\n        className:'', iconSize:[32,32], iconAnchor:[16,16]\n      });\n    }\n  });\n  map.addLayer(clusterGroup);\n  nearestLayer = L.layerGroup().addTo(map);\n\n  \/\/ Plot all locations immediately\n  plotAllMarkers(null);\n}\n\nfunction plotAllMarkers(nearestLoc){\n  if(!map || !clusterGroup) return;\n  clusterGroup.clearLayers();\n  if(nearestLayer) nearestLayer.clearLayers();\n\n  const batch = [];\n\n  \/\/ Open locations\n  LOCATIONS.forEach(l => {\n    if(!l.lat || !l.lng) return;\n    const isNearest = nearestLoc && l.lat === nearestLoc.lat && l.lng === nearestLoc.lng;\n    const addr = [l.a, `${l.c}, ${l.s} ${l.z}`].filter(Boolean).join(', ');\n    const popup = `<div style=\"font-family:'Poppins',sans-serif;min-width:180px;\">\n      <div style=\"font-size:13px;font-weight:700;color:#0B1F4A;margin-bottom:3px;\">WaveMAX ${l.n}<\/div>\n      <div style=\"font-size:11px;color:#424B72;line-height:1.5;margin-bottom:6px;\">${addr}<\/div>\n      ${l.p ? `<div style=\"font-size:11px;color:#424B72;margin-bottom:6px;\">${l.p}<\/div>` : ''}\n      ${l.wdf ? `<div style=\"font-size:11px;color:#2BBDE0;font-weight:700;margin-bottom:8px;\">WDF $${l.wdf}\/lb<\/div>` : ''}\n      <div style=\"display:flex;gap:6px;margin-top:8px;\">\n        <a href=\"https:\/\/www.wavemaxlaundry.com\/${l.sl}\/\" target=\"_blank\" style=\"flex:1;display:inline-block;padding:7px 10px;background:#2BBDE0;color:white;border-radius:50px;font-size:11px;font-weight:700;text-decoration:none;text-align:center;\">Visit<\/a>\n        <a href=\"https:\/\/www.google.com\/maps\/dir\/?api=1&destination=${encodeURIComponent(addr)}\" target=\"_blank\" style=\"flex:1;display:inline-block;padding:7px 10px;background:#0B1F4A;color:white;border-radius:50px;font-size:11px;font-weight:700;text-decoration:none;text-align:center;\">Directions<\/a>\n      <\/div>\n    <\/div>`;\n    const m = L.marker([l.lat, l.lng], {\n      icon: getIcon(isNearest ? 'red' : 'blue'),\n      zIndexOffset: isNearest ? 2000 : 0\n    }).bindPopup(popup, {maxWidth:240});\n    if(isNearest){\n      nearestLayer.addLayer(m);\n      setTimeout(() => { map.setView([l.lat,l.lng],13,{animate:true}); m.openPopup(); }, 300);\n    } else {\n      batch.push(m);\n    }\n  });\n\n  \/\/ Coming soon \u2014 gray pins\n  COMING_SOON.forEach(l => {\n    if(!l.lat || !l.lng) return;\n    const csAddr = [l.a, `${l.c}, ${l.s}`].filter(Boolean).join(', ');\n    const csPopup = `<div style=\"font-family:'Poppins',sans-serif;min-width:170px;\">\n      <div style=\"font-size:13px;font-weight:700;color:#0B1F4A;margin-bottom:3px;\">WaveMAX ${l.n}<\/div>\n      <div style=\"font-size:11px;color:#6B4EE6;font-weight:700;margin-bottom:6px;\">${l.type==='opening'?'Opening Soon':'Coming Soon'}<\/div>\n      ${l.a ? `<div style=\"font-size:11px;color:#424B72;line-height:1.5;margin-bottom:8px;\">${csAddr}<\/div>\n      <div style=\"display:flex;gap:6px;\">\n        <a href=\"https:\/\/www.google.com\/maps\/dir\/?api=1&destination=${encodeURIComponent(csAddr)}\" target=\"_blank\" style=\"flex:1;display:inline-block;padding:7px 10px;background:#0B1F4A;color:white;border-radius:50px;font-size:11px;font-weight:700;text-decoration:none;text-align:center;\">Directions<\/a>\n      <\/div>` : ''}\n    <\/div>`;\n    const m = L.marker([l.lat,l.lng], {icon:getIcon('gray')})\n      .bindPopup(csPopup, {maxWidth:220});\n    batch.push(m);\n  });\n\n  clusterGroup.addLayers(batch);\n}\n\nfunction updateMap(nearestLoc){\n  if(map) plotAllMarkers(nearestLoc || null);\n}\n\n\/\/ Divi-safe map init \u2014 polls until #wm-map has real pixel dimensions\n\/\/ Divi Code Modules paint AFTER window.load, so we can't rely on that event\nfunction initMapWhenReady(){\n  let attempts = 0;\n  const MAX = 60; \/\/ try for up to 6 seconds\n\n  function tryInit(){\n    const el = document.getElementById('wm-map');\n    if(!el){ if(++attempts < MAX) setTimeout(tryInit, 100); return; }\n\n    const w = el.offsetWidth;\n    const h = el.offsetHeight;\n\n    if(w > 50 && h > 50){\n      \/\/ Container has real dimensions \u2014 safe to init\n      initMap();\n      plotAllMarkers(null);\n    } else {\n      \/\/ Not painted yet \u2014 wait and retry\n      if(++attempts < MAX) setTimeout(tryInit, 100);\n    }\n  }\n\n  tryInit();\n}\n\nfunction showMapStatus(msg, autoHide = true){\n  const el = document.getElementById('mapStatus');\n  if(!el) return;\n  el.textContent = msg;\n  el.classList.add('visible');\n  if(autoHide) setTimeout(() => el.classList.remove('visible'), 3500);\n}\n\nfunction goToNearest(){\n  const btn = document.getElementById('nearestBtn');\n\n  \/\/ If we already have coordinates, open Google Maps directions immediately\n  if(uLat && uLng){\n    openNearestInMaps(uLat, uLng);\n    return;\n  }\n\n  \/\/ No location yet \u2014 request it now\n  btn.textContent = 'Locating\u2026';\n  btn.style.opacity = '0.75';\n  btn.style.pointerEvents = 'none';\n\n  if(navigator.geolocation){\n    navigator.geolocation.getCurrentPosition(\n      pos => {\n        uLat = pos.coords.latitude;\n        uLng = pos.coords.longitude;\n        resetNearestBtn();\n        openNearestInMaps(uLat, uLng);\n      },\n      async () => {\n        \/\/ GPS denied \u2014 try IP\n        try {\n          const r = await fetch('https:\/\/ipapi.co\/json\/', {signal: AbortSignal.timeout(4000)});\n          const d = await r.json();\n          if(!d.latitude) throw new Error();\n          uLat = d.latitude; uLng = d.longitude;\n          resetNearestBtn();\n          openNearestInMaps(uLat, uLng);\n        } catch(e){\n          resetNearestBtn();\n          showMapStatus('Could not detect location \u2014 enter your zip above', false);\n        }\n      },\n      { timeout: 6000 }\n    );\n  } else {\n    resetNearestBtn();\n    showMapStatus('Please enter your zip code above to find your nearest store', false);\n  }\n}\n\nfunction resetNearestBtn(){\n  const btn = document.getElementById('nearestBtn');\n  if(!btn) return;\n  btn.innerHTML = `<svg width=\"16\" height=\"16\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><circle cx=\"12\" cy=\"12\" r=\"3\"\/><path d=\"M12 2v3M12 19v3M2 12h3M19 12h3\"\/><path d=\"M5.64 5.64l2.12 2.12M16.24 16.24l2.12 2.12M5.64 18.36l2.12-2.12M16.24 7.76l2.12-2.12\"\/><\/svg> Find My Nearest Location`;\n  btn.style.opacity = '1';\n  btn.style.pointerEvents = 'all';\n}\n\nfunction openNearestInMaps(lat, lng){\n  const nearest = LOCATIONS\n    .filter(l => l.lat && l.lng)\n    .map(l => ({...l, d: haversine(lat, lng, l.lat, l.lng)}))\n    .sort((a,b) => a.d - b.d)[0];\n\n  if(!nearest) return;\n\n  nearestHighlighted = true;\n  uLat = lat; uLng = lng;\n  renderGrid();\n  setStatus(`Nearest: WaveMAX ${nearest.n} \u2014 ${nearest.d.toFixed(1)} mi away`);\n\n  \/\/ Highlight the nearest pin on the map\n  updateMap(nearest);\n\n  \/\/ Open Google Maps directions in new tab\n  const dest = encodeURIComponent(`${nearest.a}, ${nearest.c}, ${nearest.s} ${nearest.z}`);\n  window.open(`https:\/\/www.google.com\/maps\/dir\/${lat},${lng}\/${dest}`, '_blank');\n\n  showMapStatus(`Nearest: WaveMAX ${nearest.n} \u2014 ${nearest.d.toFixed(1)} mi`);\n}\n\nfunction zoomOnMap(lat, lng, name){\n  if(!map || !lat || !lng) return;\n\n  \/\/ Unspiderfy\/uncluster and zoom to this location\n  map.setView([lat, lng], 15, {animate: true});\n\n  \/\/ Find and open the popup for this marker\n  map.once('moveend', () => {\n    \/\/ Check nearestLayer first, then cluster\n    let found = false;\n    nearestLayer.eachLayer(m => {\n      const ll = m.getLatLng();\n      if(Math.abs(ll.lat - lat) < 0.0001 && Math.abs(ll.lng - lng) < 0.0001){\n        m.openPopup(); found = true;\n      }\n    });\n    if(!found){\n      clusterGroup.eachLayer(m => {\n        const ll = m.getLatLng();\n        if(Math.abs(ll.lat - lat) < 0.0001 && Math.abs(ll.lng - lng) < 0.0001){\n          \/\/ Zoom enough to uncluster, then open\n          clusterGroup.zoomToShowLayer(m, () => m.openPopup());\n          found = true;\n        }\n      });\n    }\n  });\n\n  \/\/ Scroll the map panel into view on mobile\n  const mapPanel = document.querySelector('.wm-map-panel');\n  if(mapPanel && window.innerWidth < 900){\n    mapPanel.scrollIntoView({behavior:'smooth', block:'start'});\n  }\n}\n\nfunction buildOpenCard(l, isNearest){\n  const ds = distStr(l.d);\n  const dc = distClass(l.d);\n  const pillClass = (l.d !== null && l.d < 100) ? 'wm-dist-pill' : 'wm-dist-pill far-pill';\n  const distHtml = ds ? `<span class=\"${pillClass}\">${ds}<\/span>` : '';\n  const nearestBorder = isNearest ? 'border-color:#E53E3E;border-width:2px;' : '';\n  const addrParts = [l.a, [l.c, l.s, l.z].filter(Boolean).join(', ')].filter(Boolean);\n  const addrHtml = addrParts.join('<br>');\n  const phoneHtml = l.p ? `\n    <div class=\"wm-info-item\">\n      <svg width=\"13\" height=\"13\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 2h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z\"\/><\/svg>\n      ${l.p}\n    <\/div>` : '';\n  let chips = '';\n  if(isNearest) chips += `<span class=\"chip\" style=\"background:#FFF0F0;color:#E53E3E;\"><svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"\/><circle cx=\"12\" cy=\"10\" r=\"3\"\/><\/svg>Nearest<\/span>`;\n  if(l.wdf){\n    chips += `<span class=\"chip chip-wdf\"><svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><rect x=\"3\" y=\"3\" width=\"18\" height=\"18\" rx=\"2\"\/><path d=\"M9 9h6M9 12h6M9 15h4\"\/><\/svg>WDF $${l.wdf}\/lb<\/span>`;\n    if(l.wdfMin) chips += `<span class=\"chip chip-wdf-min\">Min $${l.wdfMin}<\/span>`;\n  }\n  if(l.sdp){\n    chips += `<span class=\"chip chip-sameday\"><svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><circle cx=\"12\" cy=\"12\" r=\"10\"\/><polyline points=\"12 6 12 12 16 14\"\/><\/svg>Same-Day $${l.sdp}\/lb<\/span>`;\n  } else if(l.pickup){\n    chips += `<span class=\"chip chip-sameday\"><svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><circle cx=\"12\" cy=\"12\" r=\"10\"\/><polyline points=\"12 6 12 12 16 14\"\/><\/svg>Same-Day Pickup<\/span>`;\n  }\n  chips += `<span class=\"chip chip-hours\"><svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><circle cx=\"12\" cy=\"12\" r=\"10\"\/><polyline points=\"12 6 12 12 16 14\"\/><\/svg>7am\u201310pm Daily<\/span>`;\n  const mapsQ = encodeURIComponent((l.a?l.a+' ':'')+l.c+' '+l.s+' '+l.z);\n  const dataCoords = l.lat ? `data-lat=\"${l.lat}\" data-lng=\"${l.lng}\"` : '';\n  return `<div class=\"wm-card ${dc}\" ${dataCoords} style=\"cursor:pointer;${nearestBorder}\">\n    <div class=\"wm-card-accent\"${isNearest?' style=\"background:#E53E3E;\"':''}><\/div>\n    <div class=\"wm-card-body\">\n      <div class=\"wm-card-top\"><div class=\"wm-card-name\">WaveMAX ${l.n}<\/div>${distHtml}<\/div>\n      <div class=\"wm-card-address\">${addrHtml}<\/div>\n      <div class=\"wm-info-row\">${phoneHtml}<\/div>\n      <div class=\"wm-chips\">${chips}<\/div>\n    <\/div>\n    <div class=\"wm-card-footer\">\n      <a class=\"wm-btn wm-btn-primary\" href=\"${BASE}${l.sl}\/\" target=\"_blank\" onclick=\"event.stopPropagation()\">\n        <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6\"\/><polyline points=\"15 3 21 3 21 9\"\/><line x1=\"10\" y1=\"14\" x2=\"21\" y2=\"3\"\/><\/svg>\n        Visit\n      <\/a>\n      ${l.lat ? `<button class=\"wm-btn wm-btn-secondary\" onclick=\"event.stopPropagation();zoomOnMap(${l.lat},${l.lng},'${l.n.replace(\/'\/g,\"\\\\'\")}')\">\n        <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><circle cx=\"11\" cy=\"11\" r=\"8\"\/><path d=\"m21 21-4.35-4.35\"\/><line x1=\"11\" y1=\"8\" x2=\"11\" y2=\"14\"\/><line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\"\/><\/svg>\n        Zoom Map\n      <\/button>` : ''}\n      <a class=\"wm-btn wm-btn-secondary\" href=\"https:\/\/www.google.com\/maps\/search\/?api=1&query=${mapsQ}\" target=\"_blank\" onclick=\"event.stopPropagation()\">\n        <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"\/><circle cx=\"12\" cy=\"10\" r=\"3\"\/><\/svg>\n        Directions\n      <\/a>\n    <\/div>\n  <\/div>`;\n}\n\nfunction buildSoonCard(l){\n  const ds = distStr(l.d);\n  const pillClass = (l.d !== null && l.d < 100) ? 'wm-dist-pill' : 'wm-dist-pill far-pill';\n  const distHtml = ds ? `<span class=\"${pillClass}\">${ds}<\/span>` : '';\n  const isOpening = l.type === 'opening';\n  const chipClass = isOpening ? 'chip-soon' : 'chip-soon-city';\n  const chipLabel = isOpening ? 'Opening Soon' : 'Coming Soon';\n  const iconPath = isOpening\n    ? '<path d=\"M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83\"\/>'\n    : '<path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2z\"\/><path d=\"M12 6v6l4 2\"\/>';\n  const addrParts = [l.a, [l.c, l.s, l.z].filter(Boolean).join(', ')].filter(Boolean);\n  const addrHtml = addrParts.length ? addrParts.join('<br>') : `${l.c}, ${l.s}`;\n  const mapsQ = encodeURIComponent((l.a?l.a+' ':'')+l.c+' '+l.s);\n  const footerBtn = isOpening && l.a\n    ? `<a class=\"wm-btn wm-btn-secondary\" href=\"https:\/\/www.google.com\/maps\/search\/?api=1&query=${mapsQ}\" target=\"_blank\" onclick=\"event.stopPropagation()\">\n        <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"\/><circle cx=\"12\" cy=\"10\" r=\"3\"\/><\/svg>\n        View on Map\n      <\/a>`\n    : `<span class=\"wm-btn wm-btn-secondary\" style=\"opacity:0.45;cursor:default;\">Address TBA<\/span>`;\n  const zoomBtn = l.lat ? `<button class=\"wm-btn wm-btn-secondary\" onclick=\"event.stopPropagation();zoomOnMap(${l.lat},${l.lng},'${l.n.replace(\/'\/g,\"\\\\'\")}')\">\n    <svg width=\"12\" height=\"12\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\"><circle cx=\"11\" cy=\"11\" r=\"8\"\/><path d=\"m21 21-4.35-4.35\"\/><line x1=\"11\" y1=\"8\" x2=\"11\" y2=\"14\"\/><line x1=\"8\" y1=\"11\" x2=\"14\" y2=\"11\"\/><\/svg>\n    Zoom Map\n  <\/button>` : '';\n  const dataCoords = l.lat ? `data-lat=\"${l.lat}\" data-lng=\"${l.lng}\"` : '';\n  return `<div class=\"wm-card coming-soon\" ${dataCoords} style=\"cursor:pointer;\">\n    <div class=\"wm-card-accent\"><\/div>\n    <div class=\"wm-card-body\">\n      <div class=\"wm-card-top\"><div class=\"wm-card-name\">WaveMAX ${l.n}<\/div>${distHtml}<\/div>\n      <div class=\"wm-card-address\">${addrHtml}<\/div>\n      <div class=\"wm-chips\">\n        <span class=\"chip ${chipClass}\">\n          <svg width=\"10\" height=\"10\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">${iconPath}<\/svg>\n          ${chipLabel}\n        <\/span>\n      <\/div>\n    <\/div>\n    <div class=\"wm-card-footer\">${zoomBtn}${footerBtn}<\/div>\n  <\/div>`;\n}\n\nfunction renderGrid(){\n  const stateVal = document.getElementById('stateFilter').value;\n\n  let open = LOCATIONS.filter(l => {\n    if(stateVal && l.s !== stateVal) return false;\n    if(filterWdf && !l.wdf) return false;\n    if(filterPickup && !l.pickup) return false;\n    return true;\n  }).map(l => ({...l, d: (uLat!==null && l.lat) ? haversine(uLat,uLng,l.lat,l.lng) : null, _type:'open'}));\n\n  let soon = [];\n  if(!filterHideSoon && !filterWdf && !filterPickup){\n    soon = COMING_SOON.filter(l => !stateVal || l.s === stateVal)\n      .map(l => ({...l, d: (uLat!==null && l.lat) ? haversine(uLat,uLng,l.lat,l.lng) : null, _type:'soon'}));\n  }\n\n  const sortFn = uLat !== null\n    ? (a,b) => (a.d??99999)-(b.d??99999)\n    : (a,b) => a.n.localeCompare(b.n);\n  open.sort(sortFn);\n  soon.sort(sortFn);\n\n  const nearestLoc = (nearestHighlighted && open.find(l => l.lat && l.lng)) || null;\n\n  const totalShowing = open.length + soon.length;\n  const totalAll = LOCATIONS.length + COMING_SOON.length;\n  document.getElementById('resultsCount').innerHTML =\n    `<strong>${totalShowing}<\/strong> of ${totalAll} locations`;\n\n  const grid = document.getElementById('locationGrid');\n\n  if(!open.length && !soon.length){\n    grid.innerHTML = `<div class=\"wm-empty\" style=\"grid-column:1\/-1\">\n      <svg width=\"56\" height=\"56\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.2\"><path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"\/><circle cx=\"12\" cy=\"10\" r=\"3\"\/><\/svg>\n      <p>No locations found<\/p>\n      <span>Try clearing a filter or searching a different zip<\/span>\n    <\/div>`;\n    updateMap();\n    return;\n  }\n\n  let html = '';\n\n  \/\/ Show closest location banner as soon as IP location is known (before button click)\n  if(uLat && open.length){\n    const closest = open.find(l => l.lat && l.lng && l.d !== null);\n    if(closest){\n      const miles = closest.d.toFixed(1);\n      const addrLine = [closest.a, `${closest.c}, ${closest.s} ${closest.z}`].filter(Boolean).join(' \u00b7 ');\n      html += `<div class=\"wm-closest-banner\" style=\"grid-column:1\/-1;\">\n        <div class=\"wm-closest-icon\">\n          <svg width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\">\n            <path d=\"M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0 1 18 0z\"\/>\n            <circle cx=\"12\" cy=\"10\" r=\"3\"\/>\n          <\/svg>\n        <\/div>\n        <div class=\"wm-closest-body\">\n          <div class=\"wm-closest-label\">Closest Location to You<\/div>\n          <div class=\"wm-closest-name\">WaveMAX ${closest.n}<\/div>\n          <div class=\"wm-closest-addr\">${addrLine}<\/div>\n        <\/div>\n        <div class=\"wm-closest-dist\">\n          <div class=\"wm-closest-miles\">${miles}<\/div>\n          <div class=\"wm-closest-miles-label\">miles away<\/div>\n        <\/div>\n      <\/div>`;\n    }\n  }\n\n  if(open.length){\n    html += `<div class=\"wm-section-label\"><span>Open Now \u2014 ${open.length} location${open.length!==1?'s':''}<\/span><\/div>`;\n    html += open.map(l => buildOpenCard(l, l === nearestLoc)).join('');\n  }\n  if(soon.length){\n    const openingSoon = soon.filter(l=>l.type==='opening');\n    const comingSoon  = soon.filter(l=>l.type==='soon');\n    if(openingSoon.length){\n      html += `<div class=\"wm-section-label\"><span>Opening Soon \u2014 ${openingSoon.length} location${openingSoon.length!==1?'s':''}<\/span><\/div>`;\n      html += openingSoon.map(buildSoonCard).join('');\n    }\n    if(comingSoon.length){\n      html += `<div class=\"wm-section-label\"><span>Coming Soon \u2014 ${comingSoon.length} location${comingSoon.length!==1?'s':''}<\/span><\/div>`;\n      html += comingSoon.map(buildSoonCard).join('');\n    }\n  }\n\n  grid.innerHTML = html;\n  updateMap();\n}\n\n\/\/ Populate state dropdown from both arrays\nfunction populateStates(){\n  const sel = document.getElementById('stateFilter');\n  const all = [...LOCATIONS, ...COMING_SOON];\n  [...new Set(all.map(l => l.s))].sort().forEach(s => {\n    const o = document.createElement('option');\n    o.value = s; o.textContent = s;\n    sel.appendChild(o);\n  });\n}\n\npopulateStates();\nrenderGrid();\nautoDetect();\n\n\/\/ \u2500\u2500 Dynamic Leaflet loader (Divi-safe) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\/\/ Load CSS and JS programmatically so Divi's asset pipeline can't block them.\n\/\/ Once both are loaded, init the map with the polling approach.\n(function loadLeaflet(){\n  \/\/ Inject Leaflet CSS\n  function addCSS(href){\n    if(document.querySelector(`link[href=\"${href}\"]`)) return Promise.resolve();\n    return new Promise(res => {\n      const l = document.createElement('link');\n      l.rel = 'stylesheet'; l.href = href;\n      l.onload = res; l.onerror = res;\n      document.head.appendChild(l);\n    });\n  }\n\n  \/\/ Inject JS script\n  function addJS(src){\n    if(typeof L !== 'undefined' && src.includes('leaflet.js') && !src.includes('cluster')) return Promise.resolve();\n    if(window._wmx_loaded && window._wmx_loaded[src]) return Promise.resolve();\n    return new Promise(res => {\n      const s = document.createElement('script');\n      s.src = src;\n      s.onload = () => { if(!window._wmx_loaded) window._wmx_loaded={}; window._wmx_loaded[src]=true; res(); };\n      s.onerror = res;\n      document.head.appendChild(s);\n    });\n  }\n\n  Promise.all([\n    addCSS('https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.css'),\n    addCSS('https:\/\/unpkg.com\/leaflet.markercluster@1.5.3\/dist\/MarkerCluster.css'),\n    addCSS('https:\/\/unpkg.com\/leaflet.markercluster@1.5.3\/dist\/MarkerCluster.Default.css'),\n  ]).then(() =>\n    addJS('https:\/\/unpkg.com\/leaflet@1.9.4\/dist\/leaflet.js')\n  ).then(() =>\n    addJS('https:\/\/unpkg.com\/leaflet.markercluster@1.5.3\/dist\/leaflet.markercluster.js')\n  ).then(() => {\n    \/\/ Both loaded \u2014 start polling for the map container to have real dimensions\n    initMapWhenReady();\n  });\n})();\n<\/script>\n\n<\/body>\n<\/html><\/div><\/div>\n<\/div>\n<\/div>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"phone_number":"","address":"address","times":"","business_hours":"","google_maps_url":"maps_url","wdf_price":"","wdf_minimum":"","wdf_standard_price":"","wdf_24_hour_rush_price":"","wdf_includes_hangers":"","wdf_detergent_included":"","wdf_color_sorting":"","wdf_same_day_available":"","wdf_promo_bar_text":"","google_rating":"","facebook_url":"","instagram_url":"","google_review_url":"","promo_bar":"","owner_name":"","neighborhood":"","contact_email":"","app_url":"","ss_price_min":"","ss_price_max":"","ss_dryer_price":"","ss_dryer_minutes":"","num_washers":"","num_dryers":"","pickup_delivery_url":"","pickup_delivery_price":"","testimonial_1_name":"","testimonial_1_text":"","testimonial_1_rating":"","testimonial_2_name":"","testimonial_2_text":"","testimonial_2_rating":"","testimonial_3_name":"","testimonial_3_text":"","testimonial_3_rating":"","city":"","state":"","zip":"","last_wash_time":"","promo_bar_text":"","postal_code":"","location_video_url":"","google_place_id":"","pickup_delivery_available":false,"ghl_api_token":"","ghl_location_id":"","ghl_webhook_url":"","_et_pb_page_layout":"et_right_sidebar","local_video_url":"","news_video_html":"","footnotes":""},"class_list":["post-3013","page","type-page","status-publish","hentry"],"acf":[],"_links":{"self":[{"href":"https:\/\/wavemaxlaundry.com\/es\/wp-json\/wp\/v2\/pages\/3013","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wavemaxlaundry.com\/es\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/wavemaxlaundry.com\/es\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/wavemaxlaundry.com\/es\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wavemaxlaundry.com\/es\/wp-json\/wp\/v2\/comments?post=3013"}],"version-history":[{"count":5,"href":"https:\/\/wavemaxlaundry.com\/es\/wp-json\/wp\/v2\/pages\/3013\/revisions"}],"predecessor-version":[{"id":3020,"href":"https:\/\/wavemaxlaundry.com\/es\/wp-json\/wp\/v2\/pages\/3013\/revisions\/3020"}],"wp:attachment":[{"href":"https:\/\/wavemaxlaundry.com\/es\/wp-json\/wp\/v2\/media?parent=3013"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}