{"id":58279,"date":"2026-08-19T18:11:00","date_gmt":"2026-08-19T18:11:00","guid":{"rendered":"http:\/\/trueomni.com\/?page_id=58279"},"modified":"2026-08-19T18:11:45","modified_gmt":"2026-08-19T18:11:45","slug":"visit-jacksonville","status":"publish","type":"page","link":"https:\/\/trueomni.com\/spanish\/visit-jacksonville\/","title":{"rendered":"VisitJacksonville"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"58279\" class=\"elementor elementor-58279\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-bf761dd e-con e-atomic-element e-flexbox-base e-default-div e-eaaadfb \" data-id=\"bf761dd\" data-element_type=\"e-flexbox\" data-e-type=\"e-flexbox\" data-interaction-id=\"bf761dd\" data-e-type=\"e-flexbox\" data-id=\"bf761dd\">\n    \t\t<div class=\"elementor-element elementor-element-5627ff1 elementor-widget elementor-widget-html\" data-id=\"5627ff1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600;700;800;900&display=swap\" rel=\"stylesheet\">\n<title>TrueImpact \u2014 Executive Dashboard \u00b7 Visit Jacksonville<\/title>\n<!--\n  TrueImpact Executive Dashboard  \u00b7  TrueOmni  \u00b7  Destination Performance Intelligence\n  ---------------------------------------------------------------------------------\n  Single-file, dependency-free reference build. All data lives in the DATA object\n  (see <script id=\"model\">). Metrics are DERIVED from base inputs in deriveMetrics()\n  so the math is transparent and re-wireable to a live attribution API.\n  Illustrative\/sample data for demonstration. Brand + palette per TrueOmni house style.\n-->\n<style>\n:root{\n  \/* ---- TrueOmni Design System tokens (blue #0EA1DC \u00b7 ink #0A0A0A \u00b7 bg #F5F7FA \u00b7 Inter) ---- *\/\n  --navy:#0D1A2D; --navy-2:#0A1420; --azure:#0EA1DC; --azure-d:#0B8AC4;\n  --deep:#0B8AC4; --ink:#0A0A0A; --slate:#6B7280; --amber:#C2641F;\n  \/* ---- validated chart palette (dataviz skill; passes CVD + normal-vision gates) ---- *\/\n  --s-hotels:#0EA1DC; --s-dining:#EB6834; --s-attractions:#008300; --s-events:#E87BA4;\n  --s-direct:#0EA1DC; --s-influenced:#0B8AC4; --s-local:#0EA1DC; --s-state:#1BAF7A;\n  --good:#0ca30c; --warn:#C2641F;\n  \/* ---- surfaces \/ ink (light) ---- *\/\n  --bg:#F5F7FA; --surface:#ffffff; --surface-2:#F1F5F9; --line:#E5E7EB;\n  --text:#0A0A0A; --text-2:#6B7280; --muted:#9CA3AF; --grid:#E5E7EB;\n  --tint:#E0F2FE;\n  --shadow:0 1px 2px rgba(13,26,45,.06),0 8px 24px rgba(13,26,45,.06);\n  --shadow-lg:0 24px 60px rgba(13,26,45,.16);\n  --radius:16px; --radius-sm:11px;\n  --font:'Inter','Segoe UI',system-ui,-apple-system,Roboto,Helvetica,Arial,sans-serif;\n}\nhtml[data-theme=\"dark\"]{\n  --navy:#0D1A2D; --azure:#3BB8ED; --deep:#0B8AC4;\n  --s-hotels:#38b6ec; --s-dining:#f0854f; --s-attractions:#37a637; --s-events:#e493b5;\n  --s-direct:#38b6ec; --s-influenced:#2f77c4; --s-local:#38b6ec; --s-state:#22c48a;\n  --bg:#0a1420; --surface:#101d2e; --surface-2:#0c1826; --line:#1e2f45;\n  --text:#eaf1f8; --text-2:#a9b7c8; --muted:#6f8098; --grid:#182636;\n  --shadow:0 1px 2px rgba(0,0,0,.4),0 10px 30px rgba(0,0,0,.35);\n  --shadow-lg:0 24px 60px rgba(0,0,0,.55);\n}\n*{box-sizing:border-box;margin:0;padding:0}\nhtml,body{background:var(--bg);color:var(--text);font-family:var(--font);-webkit-font-smoothing:antialiased;scroll-behavior:smooth}\nbody{font-size:15px;line-height:1.5}\na{color:inherit;text-decoration:none}\nbutton{font-family:inherit;cursor:pointer;border:0;background:none;color:inherit}\n.tnum{font-variant-numeric:tabular-nums}\n\n\/* ---------- Top bar ---------- *\/\n.topbar{position:sticky;top:0;z-index:50;background:linear-gradient(100deg,var(--navy) 0%,var(--navy-2) 100%);color:#fff;box-shadow:0 6px 20px rgba(10,26,49,.28)}\n.topbar-inner{max-width:1320px;margin:0 auto;padding:12px 24px;display:flex;align-items:center;gap:18px}\n.brand{display:flex;align-items:center;gap:11px;font-weight:700;letter-spacing:.2px}\n.brand .mark{width:30px;height:30px;border-radius:8px;background:linear-gradient(135deg,var(--azure),#0f6fa0);display:grid;place-items:center;box-shadow:0 4px 14px rgba(28,156,216,.45)}\n.brand .mark svg{width:17px;height:17px}\n.brand .name{font-size:16px}.brand .name b{color:var(--azure)}\n.brand .sub{font-size:10px;font-weight:600;letter-spacing:1.6px;color:#9fc7de;text-transform:uppercase;margin-top:1px;white-space:nowrap}\n.brand{flex:none}\n.segset button{white-space:nowrap}\n@media (max-width:1280px){.brand .sub{display:none}.live{display:none}}\n.destpick{display:flex;align-items:center;gap:7px;background:rgba(255,255,255,.11);border:1px solid rgba(255,255,255,.14);border-radius:9px;padding:6px 10px;color:#dceaf5}\n.destpick svg{width:15px;height:15px;flex:none;color:var(--azure)}\n.destpick select{background:transparent;border:0;color:#fff;font-family:inherit;font-size:13px;font-weight:700;outline:none;cursor:pointer;max-width:190px}\n.destpick select option{color:#12202f}\n.sample{font-size:10px;font-weight:800;letter-spacing:1.2px;color:#ffd79a;background:rgba(194,100,31,.28);border:1px solid rgba(255,183,120,.4);border-radius:6px;padding:4px 8px;white-space:nowrap}\n@media (max-width:1180px){.sample{display:none}}\n.live{display:flex;align-items:center;gap:7px;font-size:12px;color:#bfe0f0;font-weight:600;margin-left:2px}\n.dot{width:8px;height:8px;border-radius:50%;background:#3ee08a;box-shadow:0 0 0 0 rgba(62,224,138,.7);animation:pulse 2s infinite}\n@keyframes pulse{0%{box-shadow:0 0 0 0 rgba(62,224,138,.55)}70%{box-shadow:0 0 0 9px rgba(62,224,138,0)}100%{box-shadow:0 0 0 0 rgba(62,224,138,0)}}\n.spacer{flex:1}\n.controls{display:flex;align-items:center;gap:9px}\n.segset{display:flex;background:rgba(255,255,255,.09);border-radius:10px;padding:3px}\n.segset button{font-size:12.5px;font-weight:600;color:#c5d6e6;padding:6px 12px;border-radius:7px;transition:.15s}\n.segset button.on{background:var(--azure);color:#04141f;box-shadow:0 2px 8px rgba(28,156,216,.4)}\n.icon-btn{width:36px;height:36px;border-radius:9px;background:rgba(255,255,255,.1);display:grid;place-items:center;color:#dceaf5;transition:.15s}\n.icon-btn:hover{background:rgba(255,255,255,.2)}\n.icon-btn svg{width:18px;height:18px}\n.cta{background:var(--azure);color:#04141f;font-weight:700;font-size:13px;padding:9px 15px;border-radius:9px;display:flex;align-items:center;gap:7px;transition:.15s}\n.cta:hover{background:#33abe0;transform:translateY(-1px)}\n.cta svg{width:15px;height:15px}\n\n\/* ---------- Breadcrumb \/ subnav ---------- *\/\n.wrap{max-width:1320px;margin:0 auto;padding:22px 24px 60px}\n.crumbs{display:flex;align-items:center;gap:8px;font-size:13px;color:var(--text-2);margin-bottom:16px;flex-wrap:wrap}\n.crumbs a{color:var(--azure);font-weight:600}\n.crumbs .sep{color:var(--muted)}\n.pagehead{display:flex;align-items:flex-end;justify-content:space-between;gap:16px;margin-bottom:6px;flex-wrap:wrap}\n.pagehead h1{font-size:27px;letter-spacing:-.4px;line-height:1.15}\n.pagehead .eyebrow{font-size:11.5px;font-weight:700;letter-spacing:2px;text-transform:uppercase;color:var(--azure);margin-bottom:5px}\n.pagehead .meta{font-size:12.5px;color:var(--muted);text-align:right}\n\n\/* ---------- Cards \/ grid ---------- *\/\n.grid{display:grid;gap:16px}\n.card{background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:18px 18px 16px}\n.card.pad0{padding:0;overflow:hidden}\n.card h3{font-size:13px;font-weight:700;letter-spacing:.2px;display:flex;align-items:center;gap:8px}\n.card h3 .hint{font-size:11px;color:var(--muted);font-weight:600}\n.card .ch-sub{font-size:12px;color:var(--text-2);margin-top:2px}\n.card-h{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:14px}\n\n\/* KPI hero band *\/\n.kpis{display:grid;grid-template-columns:1.5fr 1fr 1fr 1fr;gap:16px}\n.kpi{position:relative;overflow:hidden}\n.kpi.hero{background:linear-gradient(140deg,var(--navy),#123056 90%);color:#fff;border:0}\n.kpi .k-label{font-size:12px;color:var(--text-2);font-weight:600;display:flex;align-items:center;gap:6px}\n.kpi.hero .k-label{color:#a8c6db}\n.kpi .k-val{font-size:34px;font-weight:700;letter-spacing:-1px;margin-top:7px;line-height:1}\n.kpi.hero .k-val{font-size:44px}\n.kpi .k-val .cur{font-size:.62em;opacity:.7;font-weight:600;vertical-align:.14em;margin-right:1px}\n.kpi .k-delta{font-size:12.5px;font-weight:700;margin-top:9px;display:inline-flex;align-items:center;gap:5px}\n.k-delta.up{color:var(--good)} .kpi.hero .k-delta.up{color:#5ef0a5}\n.kpi .k-foot{font-size:11.5px;color:var(--muted);margin-top:8px}\n.kpi.hero .k-foot{color:#8fb2ca}\n.spark{position:absolute;right:14px;bottom:12px;opacity:.9}\n.kpi.hero .glow{position:absolute;right:-40px;top:-40px;width:180px;height:180px;background:radial-gradient(circle,rgba(28,156,216,.5),transparent 70%);pointer-events:none}\n\n.row-2{grid-template-columns:1.35fr 1fr}\n.row-3{grid-template-columns:repeat(3,1fr)}\n.row-map{grid-template-columns:1.15fr .85fr}\n\n\/* Map *\/\n.mapwrap{position:relative;padding:8px}\n.mapwrap svg{width:100%;height:auto;display:block}\n.mk{cursor:pointer;transition:.15s}\n.mk circle.hit{fill:transparent}\n.mk .pin{transition:.15s;stroke:var(--surface);stroke-width:2}\n.mk:hover .pin,.mk.active .pin{filter:brightness(1.08)}\n.mk:hover .ring,.mk.active .ring{opacity:1}\n.mk .ring{fill:none;stroke:var(--azure);stroke-width:2;opacity:0;transition:.15s}\n.mk text{font-size:9.5px;font-weight:700;fill:var(--text);paint-order:stroke;stroke:var(--surface);stroke-width:3px;stroke-linejoin:round}\n.ca-outline{fill:var(--surface-2);stroke:var(--line);stroke-width:1.5}\nhtml[data-theme=\"dark\"] .ca-outline{fill:#0c1c2e;stroke:#20344c}\n\n\/* Location list *\/\n.loclist{display:flex;flex-direction:column;gap:2px}\n.locrow{display:grid;grid-template-columns:1fr auto auto;align-items:center;gap:12px;padding:11px 12px;border-radius:11px;cursor:pointer;transition:.13s;border:1px solid transparent}\n.locrow:hover{background:var(--surface-2);border-color:var(--line);transform:translateX(2px)}\n.locrow .lr-name{font-weight:700;font-size:13.5px}\n.locrow .lr-sub{font-size:11.5px;color:var(--muted);margin-top:1px}\n.locrow .lr-val{font-weight:700;font-size:14px;text-align:right}\n.locrow .lr-bar{grid-column:1\/-1;height:5px;border-radius:3px;background:var(--grid);overflow:hidden;margin-top:2px}\n.locrow .lr-bar i{display:block;height:100%;border-radius:3px;background:linear-gradient(90deg,var(--azure),#0f6fa0)}\n.locrow .chev{color:var(--muted)}\n\n\/* Funnel *\/\n.funnel{display:flex;flex-direction:column;gap:9px;margin-top:4px}\n.fstage{position:relative}\n.fstage .fbar{height:38px;border-radius:9px;display:flex;align-items:center;padding:0 13px;color:#fff;font-weight:700;font-size:13px;transition:width .6s cubic-bezier(.2,.8,.2,1);box-shadow:0 2px 8px rgba(14,34,64,.12)}\n.fstage .fmeta{position:absolute;right:0;top:50%;transform:translateY(-50%);display:flex;gap:12px;align-items:center;font-size:12px;color:var(--text-2);padding-right:4px}\n.fstage .fmeta b{color:var(--text);font-size:13px}\n.fstage .frate{font-size:11px;color:var(--muted);font-weight:600}\n\n\/* Legend *\/\n.legend{display:flex;flex-wrap:wrap;gap:14px;margin-top:12px;font-size:12px;color:var(--text-2)}\n.legend .li{display:flex;align-items:center;gap:6px;font-weight:600}\n.legend .sw{width:11px;height:11px;border-radius:3px}\n.legend .sw.line{height:3px;width:16px;border-radius:2px}\n\n\/* partner segment cards *\/\n.seg-cards{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}\n.seg-card{border:1px solid var(--line);border-radius:var(--radius-sm);padding:14px;cursor:pointer;transition:.15s;background:var(--surface)}\n.seg-card:hover{transform:translateY(-3px);box-shadow:var(--shadow)}\n.seg-card .sc-top{display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:700}\n.seg-card .sc-ic{width:26px;height:26px;border-radius:7px;display:grid;place-items:center}\n.seg-card .sc-ic svg{width:15px;height:15px;color:#fff}\n.seg-card .sc-val{font-size:23px;font-weight:700;letter-spacing:-.5px;margin-top:11px}\n.seg-card .sc-sub{font-size:11.5px;color:var(--muted);margin-top:3px}\n.seg-card .sc-count{font-size:11.5px;color:var(--text-2);font-weight:600;margin-top:9px;display:flex;justify-content:space-between;border-top:1px solid var(--line);padding-top:8px}\n\n\/* table *\/\ntable{width:100%;border-collapse:collapse;font-size:13px}\nth,td{text-align:right;padding:10px 10px;border-bottom:1px solid var(--line)}\nth{font-size:11px;text-transform:uppercase;letter-spacing:.6px;color:var(--muted);font-weight:700}\nth:first-child,td:first-child{text-align:left}\ntbody tr{cursor:pointer;transition:.12s}\ntbody tr:hover{background:var(--surface-2)}\ntd .pill{display:inline-block;font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px}\n.tag-hi{background:rgba(12,163,12,.13);color:var(--good)}\n.tag-mid{background:rgba(28,156,216,.14);color:var(--azure-d)}\n\n\/* AI insight banner *\/\n.ai-banner{display:flex;gap:14px;align-items:flex-start;background:linear-gradient(100deg,rgba(28,156,216,.08),rgba(20,80,140,.05));border:1px solid var(--line);border-left:3px solid var(--azure);border-radius:var(--radius);padding:15px 18px;margin-bottom:16px}\n.ai-banner .ai-ic{flex:none;width:34px;height:34px;border-radius:9px;background:linear-gradient(135deg,var(--azure),var(--deep));display:grid;place-items:center;box-shadow:0 4px 12px rgba(28,156,216,.35)}\n.ai-banner .ai-ic svg{width:18px;height:18px;color:#fff}\n.ai-banner .ai-txt{font-size:13.5px;color:var(--text);line-height:1.55}\n.ai-banner .ai-txt b{color:var(--text)}\n.ai-banner .ai-txt .em{color:var(--azure-d);font-weight:700}\nhtml[data-theme=\"dark\"] .ai-banner .ai-txt .em{color:var(--azure)}\n.ai-banner .ai-tag{font-size:10.5px;font-weight:700;letter-spacing:1.4px;text-transform:uppercase;color:var(--azure-d);margin-bottom:3px;display:block}\n\n\/* tooltip *\/\n.tt{position:fixed;z-index:99;pointer-events:none;background:var(--navy);color:#fff;font-size:12px;padding:9px 12px;border-radius:9px;box-shadow:var(--shadow-lg);opacity:0;transition:opacity .1s;max-width:230px;line-height:1.45}\n.tt .tt-t{font-weight:700;margin-bottom:3px;font-size:12.5px}\n.tt .tt-r{display:flex;justify-content:space-between;gap:16px;color:#bcd4e6}\n.tt .tt-r b{color:#fff}\nhtml[data-theme=\"dark\"] .tt{background:#020a14;border:1px solid #1e3350}\n\n\/* misc *\/\n.chip{display:inline-flex;align-items:center;gap:6px;font-size:12px;font-weight:600;padding:5px 11px;border-radius:20px;background:var(--surface-2);border:1px solid var(--line);color:var(--text-2)}\n.chip .sw{width:9px;height:9px;border-radius:50%}\n.stat-inline{display:flex;gap:26px;flex-wrap:wrap;margin-top:2px}\n.stat-inline .si b{display:block;font-size:20px;font-weight:700;letter-spacing:-.4px}\n.stat-inline .si span{font-size:11.5px;color:var(--muted);font-weight:600}\n.backbtn{display:inline-flex;align-items:center;gap:6px;font-size:13px;font-weight:600;color:var(--azure);margin-bottom:12px}\n.backbtn svg{width:15px;height:15px}\n.gauge-track{height:9px;border-radius:5px;background:var(--grid);overflow:hidden;margin-top:8px}\n.gauge-track i{display:block;height:100%;border-radius:5px;background:linear-gradient(90deg,var(--s-state),#0f9c6d)}\n.note{font-size:11px;color:var(--muted);margin-top:12px;line-height:1.5}\n\/* engagement filters *\/\n.filters{display:flex;flex-wrap:wrap;gap:10px 18px;align-items:center;background:var(--surface);border:1px solid var(--line);border-radius:var(--radius);box-shadow:var(--shadow);padding:12px 16px;margin-bottom:16px}\n.fgroup{display:flex;align-items:center;gap:8px}\n.fgroup>.flab{font-size:11px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;color:var(--muted)}\n.fseg{display:flex;background:var(--surface-2);border:1px solid var(--line);border-radius:9px;padding:3px;gap:2px}\n.fseg button{font-size:12.5px;font-weight:600;color:var(--text-2);padding:6px 12px;border-radius:6px;transition:.14s;white-space:nowrap}\n.fseg button.on{background:var(--azure);color:#04141f}\n.fsel{font-family:inherit;font-size:13px;font-weight:600;color:var(--text);background:var(--surface-2);border:1px solid var(--line);border-radius:8px;padding:7px 11px;cursor:pointer;outline:none}\n.fspace{flex:1}\n\/* A\/B test card *\/\n.ab-card{border:1px solid var(--line);border-radius:var(--radius-sm);padding:15px 16px;background:var(--surface)}\n.ab-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:12px}\n.ab-head .abt{font-size:13.5px;font-weight:700}\n.ab-head .abs{font-size:11px;color:var(--muted);margin-top:2px}\n.ab-badge{font-size:11px;font-weight:800;padding:4px 9px;border-radius:20px;white-space:nowrap}\n.ab-win{background:rgba(12,163,12,.14);color:var(--good)}\n.ab-none{background:rgba(139,150,165,.16);color:var(--text-2)}\n.ab-var{display:grid;grid-template-columns:auto 1fr auto;gap:10px;align-items:center;padding:9px 0;border-top:1px solid var(--line)}\n.ab-tag{width:22px;height:22px;border-radius:6px;display:grid;place-items:center;font-size:12px;font-weight:800;color:#fff}\n.ab-nm{font-size:12.5px;font-weight:600}\n.ab-sub{font-size:11px;color:var(--muted)}\n.ab-conv{text-align:right}\n.ab-conv b{font-size:19px;font-weight:700;letter-spacing:-.5px}\n.ab-conv span{font-size:10.5px;color:var(--muted);display:block}\n.ab-bar{grid-column:1\/-1;height:6px;border-radius:3px;background:var(--grid);overflow:hidden;margin-top:-2px}\n.ab-bar i{display:block;height:100%;border-radius:3px}\n.ab-foot{display:flex;justify-content:space-between;font-size:11.5px;color:var(--text-2);margin-top:11px;border-top:1px solid var(--line);padding-top:10px}\n.ab-foot b{color:var(--text)}\n.conv-pill{font-size:11px;font-weight:700;padding:2px 8px;border-radius:20px;background:rgba(28,156,216,.14);color:var(--azure-d)}\nhtml[data-theme=\"dark\"] .conv-pill{color:var(--azure)}\n.toggle-tbl{font-size:11.5px;color:var(--azure-d);font-weight:600;cursor:pointer}\nhtml[data-theme=\"dark\"] .toggle-tbl{color:var(--azure)}\n.hidden{display:none!important}\n.fade{animation:fade .35s ease}\n@keyframes fade{from{opacity:0;transform:translateY(8px)}to{opacity:1;transform:none}}\n.footer{max-width:1320px;margin:0 auto;padding:20px 24px 40px;color:var(--muted);font-size:11.5px;display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;border-top:1px solid var(--line);margin-top:20px}\n\n@media (max-width:1080px){\n  .kpis{grid-template-columns:1fr 1fr}.row-2,.row-map,.row-3{grid-template-columns:1fr}.seg-cards{grid-template-columns:1fr 1fr}\n}\n@media print{\n  .topbar,.cta,.icon-btn,.segset,.toggle-tbl{display:none!important}\n  body{background:#fff}.card{box-shadow:none;border:1px solid #ccc;break-inside:avoid}\n}\n\n\/* ===================== TrueOmni Design System overrides ===================== *\/\n.eyebrow{color:var(--azure)!important;text-transform:uppercase;letter-spacing:.12em;font-weight:600}\n.eyebrow::before{content:'\\2500\\2500  ';color:var(--azure);font-weight:700;letter-spacing:0}\n.card{border-top:3px solid var(--azure)}\n.pagehead h1{letter-spacing:-.01em}\n.pagehead h1::after{content:'\\2022';color:var(--azure);margin-left:3px;font-weight:900}\n.crumbs a{color:var(--azure)}\n<\/style>\n<\/head>\n<body>\n\n<!-- =================== TOP BAR =================== -->\n<header class=\"topbar\">\n  <div class=\"topbar-inner\">\n    <div class=\"brand\">\n      <span class=\"mark\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M3 20h18M6 20V10m4 10V5m4 15v-7m4 7V8\" stroke=\"#fff\" stroke-width=\"2.4\" stroke-linecap=\"round\"\/><\/svg><\/span>\n      <div>\n        <div class=\"name\">True<b>Impact<\/b><\/div>\n        <div class=\"sub\">Destination Performance Intelligence<\/div>\n      <\/div>\n    <\/div>\n    <label class=\"destpick\" style=\"display:none\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M12 21s-7-6.2-7-11a7 7 0 1114 0c0 4.8-7 11-7 11z\" stroke=\"currentColor\" stroke-width=\"1.8\"\/><circle cx=\"12\" cy=\"10\" r=\"2.5\" stroke=\"currentColor\" stroke-width=\"1.8\"\/><\/svg><select id=\"destSel\"><\/select><\/label>\n    <span class=\"sample\" title=\"Pre-deployment projection for a 10-kiosk \/ 5-screen network. The visitor-economy baseline is published; kiosk, screen, advertising and influence figures are modeled. Not audited results.\">MODELED \u00b7 USD<\/span>\n    <div class=\"live\"><span class=\"dot\"><\/span> PROJECTION \u00b7 pre-deployment<\/div>\n    <div class=\"spacer\"><\/div>\n    <div class=\"controls\">\n      <div class=\"segset\" id=\"periodSel\">\n        <button data-p=\"q\" >Quarter<\/button>\n        <button data-p=\"ytd\">YTD<\/button>\n        <button data-p=\"t12\" class=\"on\">Trailing 12 mo<\/button>\n      <\/div>\n      <button class=\"icon-btn\" id=\"themeBtn\" title=\"Toggle theme\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M21 12.8A9 9 0 1111.2 3a7 7 0 009.8 9.8z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg>\n      <\/button>\n      <button class=\"cta\" onclick=\"window.print()\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 9V3h12v6M6 18H4v-6h16v6h-2M8 14h8v7H8z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg>\n        Board Briefing\n      <\/button>\n    <\/div>\n  <\/div>\n<\/header>\n\n<div class=\"wrap\">\n  <div id=\"app\"><\/div>\n<\/div>\n\n<footer class=\"footer\">\n  <div>\u00a9 TrueOmni 2026 \u00b7 TrueImpact Executive Dashboard \u00b7 Scottsdale, AZ \u00b7 www.trueomni.com \u00b7 833-300-6664<\/div>\n  <div>Prepared for Visit Jacksonville &middot; figures in USD &middot; the visitor-economy baseline (Visit Jacksonville, 2024 visitor activity) is a published actual; kiosk and screen counts, zone selection, exposures, per-visitor spend, advertising economics and the influence-scenario ladder are a modeled pre-deployment projection &mdash; derived &amp; auditable via the TrueOmni attribution stack. Modeled opportunity, not projected results. Cite under NDA.<\/div>\n<\/footer>\n\n<div class=\"tt\" id=\"tt\"><\/div>\n\n<!-- =================== DATA MODEL =================== -->\n<script id=\"model\">\n\/* ------------------------------------------------------------------\n   TrueImpact data model \u2014 Visit Jacksonville edition.\n   Single-tenant. 10 interactive kiosks + 5 digital-signage screens.\n   Every headline number is DERIVED in deriveMetrics() so the math is\n   transparent and can be swapped for the live Kiolytics attribution\n   feed once the network is online. Currency: US dollars (USD).\n\n   PUBLISHED ANCHOR (see the provenance doc for the full ledger):\n   Direct visitor spending &middot; Jacksonville = $4,100,000,000  (Visit Jacksonville, 2024 visitor activity)\n\n   NOTE: kiosk and screen counts, zone selection, exposures, per-visitor\n   spend and all advertising economics are a MODELED pre-deployment\n   projection. The influence-scenario ladder on #\/scenarios is MODELED\n   OPPORTUNITY, not projected results. Re-point at Kiolytics after go-live.\n------------------------------------------------------------------ *\/\nconst FUNNEL = { highIntent:0.17, conversion:0.14 };  \/\/ TrueOmni platform benchmarks\nconst INFL_RATE = 0.85;   \/\/ influenced $ per $1 direct (Tourism Economics tail; 0.7\u20131.1)\nconst LODGING_SHARE = 0.36;\nconst DINING_RETAIL_SHARE = 0.34;\nconst ATTR_SHARE = 0.30;\nconst RENTAL_SHARE = 0.04;\nconst RECIRC_MULT = 0.9;\nconst STATE_SALES = 6.0;   \/\/ state portion of the combined sales rate\n\n\/* Doug's influence ladder \u2014 modeled opportunity as a share of the published\n   direct visitor-spending base. Presented as scale, never as a forecast. *\/\nconst SCENARIOS=[0.001, 0.0025, 0.005, 0.01];\n\nconst DEST_META={\n  spendBase:4_100_000_000,\n  spendBaseLabel:'Direct visitor spending &middot; Jacksonville',\n  spendBaseSource:'Visit Jacksonville, 2024 visitor activity',\n  context:[{v:'8M+',l:'annual visitors (2024)'},{v:'$4.1B',l:'direct visitor spending'},{v:'$7.4B',l:'total economic impact'},{v:'56,900',l:'tourism-supported jobs'}],\n  contextNote:'Sources: Visit Jacksonville economic-impact research on 2024 visitor activity &mdash; 8M+ annual visitors, $4.1B in direct visitor spending, $7.4B in total economic impact, 56,900 tourism-supported jobs, nearly 5 million hotel room nights sold and $31.5M in hotel\/bed-tax collections. Average stay 3.7 nights, average daily spend $333, average trip spend $1,100, and 80% of visitors intend to return. <b>We anchor to the $4.1B direct figure, not the $7.4B total.<\/b> The 3.7-night average is the number this network is built to move: at $333 a day, one additional night is worth $333 per party.',\n  publishedTax:{v:31_500_000,l:'hotel \/ bed-tax collections (2024, actual)',cmp:'hotelTOT'},\n  spendMix:[],\n  spendMixNote:'',\n  adNote:'An $4.1B visitor economy with nearly 5 million room nights sold supports a strong direct book: beaches and downtown hotels, the Jaguars and EverBank Stadium, St. Johns Town Center retail, Riverside and Avondale restaurants, the museums, and the convention centre&rsquo;s own event sponsors. With 8M+ visitors the impression volume is large enough that programmatic through Google Ad Manager carries a meaningful share.',\n  taxLabels:{hotelTOT:'Duval County Tourist Development Tax (6%)', salesTax:'Duval County local option sales tax (1.5%)', rentalTax:'Florida sales tax on rental cars (7.5%)', stateTax:'Florida state sales tax (6%)',\n    localName:'Stays in Duval County', stateName:'State of Florida', hotelDesc:'LOCAL &mdash; the bed tax; splits 2% tourism, 2% stadium debt, 2% sports complex', salesDesc:'LOCAL &mdash; Duval County discretionary surtax on visitor spend',\n    stateDesc:'to the State of Florida &mdash; the delegation argument', note:'Rates verified: the Duval County Tourist Development Tax is <b>6%<\/b> on all hotel rooms, and its allocation matters politically &mdash; two points to the Sports Complex Capital Maintenance Enterprise Fund, two points to debt service on the football stadium bonds, and two points to tourism development. So only a third of the bed tax this network influences flows back to destination marketing. Combined sales and use tax is 7.5% (Florida state 6.0% plus 1.5% Duval). Florida&rsquo;s $2.00-per-day rental-car surcharge is <i>not<\/i> modeled here; only sales tax on the rental base is.'},\n  taxInsight:'<b>Where the bed tax actually goes matters more here than how much of it there is.<\/b> Duval County&rsquo;s 6% Tourist Development Tax splits three ways: two points to the Sports Complex Capital Maintenance Enterprise Fund, two points to debt service on the football stadium bonds, and two points to tourism development. So only <b>one third<\/b> of the bed tax this network influences flows back to destination marketing &mdash; but all six points are a real public return, and four of them service assets the city is already committed to. Present the full lodging line to the city and the one-third share to the TDC. Florida also takes the larger share overall at 6% state sales tax against 1.5% county.',\n  journey:[\n    {step:'Arrives',   what:'First touch at the airport, welcome center or hotel lobby &mdash; before a single decision is made.', metric:'Encounters'},\n    {step:'Discovers', what:'Attractions, museums, neighbourhoods and restaurants surfaced by stated interest.',                   metric:'Searches &amp; views'},\n    {step:'Engages',   what:'AI conversation, multi-language and ADA support &mdash; visitor service beyond staff hours.',          metric:'Interactions'},\n    {step:'Plans',     what:'A personalised itinerary built on the spot and sent to the visitor&rsquo;s phone.',                   metric:'Itineraries created'},\n    {step:'Visits',    what:'Wayfinding and turn-by-turn directions to the partner business.',                                     metric:'Direction requests'},\n    {step:'Spends',    what:'Booking, ticketing or deal redemption with a listed destination partner.',                             metric:'Attributed conversions'},\n    {step:'Returns',   what:'The captured interest profile feeds re-marketing and the next trip.',                                  metric:'Opt-ins &amp; repeat visits'},\n  ],\n};\nconst TAXL=DEST_META.taxLabels;\nconst RENTAL_ON_DINING=false;\n\nconst PERIODS = { t12:{label:'Trailing 12 months (annualized, full network)',k:1}, ytd:{label:'Year to date (2026)',k:0.545}, q:{label:'This quarter (Q3 2026)',k:0.262} };\n\nconst SEG = {\n  hotels:      {name:'Hotels & Lodging',     color:'var(--s-hotels)',      raw:'#1C9CD8'},\n  dining:      {name:'Restaurants & Dining', color:'var(--s-dining)',      raw:'#EB6834'},\n  attractions: {name:'Attractions & Tours',  color:'var(--s-attractions)', raw:'#008300'},\n  events:      {name:'Events & Venues',      color:'var(--s-events)',      raw:'#E87BA4'},\n};\n\nconst SEASON = { jax:[0.82,0.88,1.1,1.06,1.1,1.16,1.14,0.96,0.9,1.02,1.0,0.86] };\nconst MONTHS=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\n\nconst DESTINATIONS = {\n  jax:{ name:'Visit Jacksonville', kind:'Destination Marketing Organization', scope:'Jacksonville &amp; Duval County', region:'Jacksonville, FL',\n    outline:'M60,80 L330,80 L350,160 L320,250 L348,340 L330,430 L300,520 L60,540 Z',\n    lake:'M330,80 L474,80 L474,540 L300,520 L330,430 L348,340 L320,250 L350,160 Z',\n    lakeLabel:{x:410,y:300,t:'Atlantic Ocean'},\n    locations:[\n      {id:'airport', name:'Jacksonville International (JAX)', sites:'JAX terminal &middot; baggage claim &middot; rental-car centre', x:236,y:150, kiosks:2, screens:2, phase:1, exposures:300_000, interactionRate:0.43, avgSpend:420, tax:{tot:6.0,sales:7.5,rental:7.5}, season:'jax', mix:{hotels:0.46,dining:0.18,attractions:0.16,events:0.2}, note:'The single most valuable placement in the network. First touch for fly-in visitors before a single decision has been made about where to eat, stay or go &mdash; and the only point in the journey where a visitor is captive with time to plan. Largest screen count and highest attributed impact.', badge:'Anchor'},\n      {id:'beaches', name:'Jacksonville Beach &amp; the Beaches', sites:'Jacksonville Beach &middot; Neptune Beach &middot; Atlantic Beach', x:326,y:290, kiosks:2, screens:1, phase:1, exposures:320_000, interactionRate:0.33, avgSpend:285, tax:{tot:6.0,sales:7.5,rental:7.5}, season:'jax', mix:{hotels:0.38,dining:0.32,attractions:0.16,events:0.14}, note:'Highest raw exposure in the network. Outdoor, weather-rated units along the beach corridor where visitors spend the most time and know the least about the rest of Jacksonville &mdash; the biggest cross-city discovery gap in the destination.'},\n      {id:'convention', name:'Prime Osborn &amp; downtown hotels', sites:'Prime Osborn Convention Center &middot; downtown host hotels', x:230,y:300, kiosks:2, screens:1, phase:2, exposures:190_000, interactionRate:0.45, avgSpend:465, tax:{tot:6.0,sales:7.5,rental:7.5}, season:'jax', mix:{hotels:0.52,dining:0.22,attractions:0.08,events:0.18}, note:'Highest interaction rate and highest per-visitor spend in the network. Convention delegates are captive, multi-night and the most winnable stay-longer audience &mdash; every extra night is $333 at Jacksonville&rsquo;s published average daily spend.'},\n      {id:'stadium', name:'EverBank Stadium &amp; Sports Complex', sites:'EverBank Stadium &middot; VyStar Arena &middot; 121 Financial Ballpark', x:272,y:296, kiosks:1, screens:1, phase:2, exposures:180_000, interactionRate:0.36, avgSpend:235, tax:{tot:6.0,sales:7.5,rental:7.5}, season:'jax', mix:{hotels:0.22,dining:0.28,attractions:0.08,events:0.42}, note:'Jaguars home dates, concerts, the Florida&ndash;Georgia game, Gator Bowl and minor-league baseball. Events-dominant. Politically notable: four of the six bed-tax points this traffic generates service the stadium and sports complex directly.'},\n      {id:'towncenter', name:'St. Johns Town Center &amp; Southside', sites:'St. Johns Town Center &middot; Southside retail corridor', x:262,y:372, kiosks:1, screens:0, phase:3, exposures:160_000, interactionRate:0.3, avgSpend:195, tax:{tot:6.0,sales:7.5,rental:7.5}, season:'jax', mix:{hotels:0.16,dining:0.34,attractions:0.14,events:0.36}, note:'The region&rsquo;s dominant retail destination and a genuine visitor draw in its own right. Retail and dining cross-sell, with the lowest interaction rate in the network because shoppers are already on a mission.'},\n      {id:'riverside', name:'Riverside &middot; Avondale &middot; Five Points', sites:'Riverside &middot; Avondale &middot; Five Points dining &amp; retail', x:198,y:318, kiosks:1, screens:0, phase:3, exposures:130_000, interactionRate:0.32, avgSpend:175, tax:{tot:6.0,sales:7.5,rental:7.5}, season:'jax', mix:{hotels:0.14,dining:0.46,attractions:0.22,events:0.18}, note:'Jacksonville&rsquo;s independent dining and retail districts. Dining-dominant &mdash; the decision a kiosk influences most reliably is where a visitor eats tonight.'},\n      {id:'riverwalk', name:'Downtown Riverwalk &amp; museums', sites:'Riverwalk &middot; MOSH &middot; Cummer Museum &middot; Ritz Theatre', x:252,y:308, kiosks:1, screens:0, phase:3, exposures:110_000, interactionRate:0.34, avgSpend:165, tax:{tot:6.0,sales:7.5,rental:7.5}, season:'jax', mix:{hotels:0.16,dining:0.28,attractions:0.42,events:0.14}, note:'The St. Johns Riverwalk and the downtown museum cluster. Attractions-dominant and the clearest beneficiary of discovery: visitors who never learn these exist cannot spend there.'},\n    ]},\n};\n\nlet activeDest='jax';\nfunction ACT(){return DESTINATIONS[activeDest];}\nfunction LOCS(){return ACT().locations;}\nfunction findLoc(id){return LOCS().find(l=>l.id===id);}\n\n\/* Install sequence \u2014 all waves land inside the SAME 10-kiosk \/ 5-screen\n   network. A rollout order, NOT an expansion forecast. *\/\nconst PHASES=[\n  {n:1, name:'Wave 1 &middot; Anchor placements', window:'Weeks 0&ndash;4', ids:['airport','beaches']},\n  {n:2, name:'Wave 2 &middot; Gateway &amp; core', window:'Weeks 5&ndash;8', ids:['airport','beaches','convention','stadium']},\n  {n:3, name:'Wave 3 &middot; Network complete', window:'Weeks 9&ndash;12', ids:['airport','beaches','convention','stadium','towncenter','riverside','riverwalk']},\n];\nconst NET_KIOSKS=10, NET_SCREENS=5;\n\nconst MEDIA={\n  perKioskMo:{floor:650, mid:1150, strong:1500},\n  imprPerEncounter:3.2, fillMature:0.70, fillByPhase:{1:0.35, 2:0.55, 3:0.70},\n  streamSplit:{direct:0.6, programmatic:0.4},\n  softwareYr:9_600, capital:88_000,\n  inventory:[\n    {id:'video',    name:'Full-screen video (kiosk attract loop)', share:.30},\n    {id:'banner',   name:'Home &amp; footer banners',              share:.24},\n    {id:'rail',     name:'Events \/ listings side-rail',            share:.18},\n    {id:'sponsored',name:'Sponsored listings &amp; tiles',         share:.16},\n    {id:'qr',       name:'QR-conversion sponsorships',             share:.12},\n  ],\n};\nfunction mediaMetrics(k=1, locs=LOCS()){\n  const r=rollup(k,locs); const kiosks=r.kiosks;\n  const impressions=r.exposures*MEDIA.imprPerEncounter;\n  const band={}; for(const s in MEDIA.perKioskMo) band[s]=kiosks*MEDIA.perKioskMo[s]*12*k;\n  const rev=band.mid;\n  const cpm=impressions? rev\/(impressions\/1000):0;\n  const byStream={direct:rev*MEDIA.streamSplit.direct, programmatic:rev*MEDIA.streamSplit.programmatic};\n  const byInv=MEDIA.inventory.map(it=>({...it, v:rev*it.share}));\n  return {kiosks,impressions,band,rev,cpm,byStream,byInv};\n}\n\n\/* DIGITAL SIGNAGE \u2014 5 large-format broadcast screens. Screens deliver REACH\n   and ad inventory; they do NOT transact and are kept OUT of the economic-\n   impact and tax math. Signage reach uses a WIDER denominator than kiosk\n   impact because residents and staff also pass a screen. *\/\nconst SIGNAGE={\n  enabled:true,\n  perScreenMo:{floor:580, mid:1160, strong:1900},\n  loopSec:60, spotSec:15,\n  localMult:2.5, viewShare:0.55, imprPerPasserby:2.8, dwellSec:8,\n  fillMature:0.65, fillByPhase:{1:0.30,2:0.50,3:0.65},\n  capitalPerScreen:5600, softwarePerScreenYr:600,\n  formats:[\n    {id:'wall',     name:'Video wall \/ lobby hero',           share:.38},\n    {id:'portrait', name:'Portrait posters (corridor)',       share:.26},\n    {id:'wayfind',  name:'Wayfinding &amp; directory boards', share:.22},\n    {id:'outdoor',  name:'Outdoor \/ weatherized',             share:.14},\n  ],\n};\nfunction signageMetrics(k=1, locs=LOCS()){\n  if(!SIGNAGE.enabled) return null;\n  const screens=locs.reduce((s,L)=>s+(L.screens||0),0);\n  if(!screens) return null;\n  const foot=locs.reduce((s,L)=>s+L.exposures,0)*k*SIGNAGE.localMult;\n  const reach=foot*SIGNAGE.viewShare;\n  const impressions=reach*SIGNAGE.imprPerPasserby;\n  const band={}; for(const s in SIGNAGE.perScreenMo) band[s]=screens*SIGNAGE.perScreenMo[s]*12*k;\n  const rev=band.mid;\n  const cpm=impressions? rev\/(impressions\/1000):0;\n  const spotsPerLoop=Math.round(SIGNAGE.loopSec\/SIGNAGE.spotSec);\n  return {screens,foot,reach,impressions,band,rev,cpm,spotsPerLoop,\n    shareOfVoice:1\/spotsPerLoop,\n    capital:screens*SIGNAGE.capitalPerScreen, softwareYr:screens*SIGNAGE.softwarePerScreenYr,\n    byFormat:SIGNAGE.formats.map(f=>({...f, v:rev*f.share})),\n    attentionHrs:impressions*SIGNAGE.dwellSec\/3600};\n}\n\/* combined advertising view \u2014 Doug's \"Total Advertising Revenue\" headline *\/\nfunction adTotals(k=1, locs=LOCS()){\n  const md=mediaMetrics(k,locs), sg=signageMetrics(k,locs);\n  const rev=md.rev+(sg?sg.rev:0);\n  const impressions=md.impressions+(sg?sg.impressions:0);\n  const capital=MEDIA.capital+(sg?sg.capital:0);\n  const softwareYr=MEDIA.softwareYr+(sg?sg.softwareYr:0);\n  return {md,sg,rev,impressions,capital,softwareYr,\n    cpm: impressions? rev\/(impressions\/1000):0,\n    kioskShare: rev? md.rev\/rev:0, signageShare: rev&&sg? sg.rev\/rev:0,\n    paybackYr: rev? capital\/rev:0,\n    netPaybackYr: (rev-softwareYr)>0? capital\/(rev-softwareYr):0,\n    softwareCover: softwareYr? rev\/softwareYr:0};\n}\n\n\/* ---- derive all metrics for one location at a period scale ---- *\/\nfunction deriveMetrics(L, k=1){\n  const exposures = L.exposures*k;\n  const interactions = exposures*L.interactionRate;\n  const highIntent = interactions*FUNNEL.highIntent;\n  const conversions = highIntent*FUNNEL.conversion;\n  const directSpend = conversions*L.avgSpend;\n  const direct = directSpend;\n  const influenced = directSpend*INFL_RATE;\n  const total = direct+influenced;\n  const r = L.tax;\n  const taxableSpend = directSpend + influenced*0.5;\n  const lodgingBase  = taxableSpend*LODGING_SHARE;\n  const diningBase   = taxableSpend*DINING_RETAIL_SHARE;\n  const attrBase     = taxableSpend*ATTR_SHARE;\n  const salesBase    = lodgingBase + diningBase + attrBase;\n  const localSales   = Math.max(0, r.sales - STATE_SALES);\n  \/\/ the 4th line differs by jurisdiction: a restaurant levy where one exists,\n  \/\/ otherwise the rental-car base. taxBreak keys stay stable for the renderer.\n  const fourthBase   = RENTAL_ON_DINING ? diningBase : taxableSpend*RENTAL_SHARE;\n  const hotelTOT   = lodgingBase*(r.tot\/100);\n  const salesTax   = salesBase*(localSales\/100);\n  const rentalTax  = fourthBase*(r.rental\/100);\n  const stateTax   = salesBase*(STATE_SALES\/100);\n  const taxLocal   = hotelTOT+salesTax+rentalTax;\n  const taxState   = stateTax;\n  const tax        = taxLocal+taxState;\n  const recirc     = tax*RECIRC_MULT;\n  const taxBreak = {hotelTOT,salesTax,rentalTax,stateTax};\n  const partners = {};\n  for(const s in L.mix){ partners[s] = directSpend*L.mix[s]*1.0 + influenced*L.mix[s]*0.6; }\n  const revAssist = L.kiosks*1500 + (L.kiosks>5?L.kiosks*1100:L.kiosks*700);\n  return {exposures,interactions,highIntent,conversions,directSpend,direct,influenced,total,taxLocal,taxState,tax,taxBreak,recirc,partners,revAssist};\n}\nfunction seriesFor(L,k=1){\n  const m=deriveMetrics(L,k); const sh=SEASON[L.season];\n  const base=m.total\/sh.reduce((a,b)=>a+b,0);\n  return [0,1,2,3,4,5,6,7,8,9,10,11].map(i=>base*sh[i]);\n}\nfunction rollup(k=1, locs=LOCS()){\n  const acc={exposures:0,interactions:0,highIntent:0,conversions:0,directSpend:0,direct:0,influenced:0,total:0,taxLocal:0,taxState:0,tax:0,recirc:0,revAssist:0,kiosks:0,screens:0,\n    partners:{hotels:0,dining:0,attractions:0,events:0}, taxBreak:{hotelTOT:0,salesTax:0,rentalTax:0,stateTax:0}};\n  locs.forEach(L=>{const m=deriveMetrics(L,k);for(const key in acc){\n    if(key==='partners'){for(const s in acc.partners)acc.partners[s]+=m.partners[s];}\n    else if(key==='taxBreak'){for(const s in acc.taxBreak)acc.taxBreak[s]+=m.taxBreak[s];}\n    else if(key==='kiosks'){acc.kiosks+=L.kiosks;}\n    else if(key==='screens'){acc.screens+=(L.screens||0);}\n    else if(m[key]!=null)acc[key]+=m[key];}});\n  return acc;\n}\nfunction rollupSeries(k=1, locs=LOCS()){\n  const t=new Array(12).fill(0);\n  locs.forEach(L=>{const s=seriesFor(L,k);s.forEach((v,i)=>t[i]+=v);});\n  return t;\n}\n\n\/* ---------- (network roll-up retained but unused in single-tenant) ---------- *\/\nconst NETWORK={ destinations:1, kiosks:NET_KIOSKS, targetTotal:0, targetTax:0, targetActions:0 };\nfunction mergeRollups(list){\n  const acc={exposures:0,interactions:0,highIntent:0,conversions:0,directSpend:0,direct:0,influenced:0,total:0,taxLocal:0,taxState:0,tax:0,recirc:0,revAssist:0,kiosks:0,screens:0,\n    partners:{hotels:0,dining:0,attractions:0,events:0}, taxBreak:{hotelTOT:0,salesTax:0,rentalTax:0,stateTax:0}};\n  list.forEach(m=>{for(const key in acc){\n    if(key==='partners'){for(const s in acc.partners)acc.partners[s]+=m.partners[s];}\n    else if(key==='taxBreak'){for(const s in acc.taxBreak)acc.taxBreak[s]+=m.taxBreak[s];}\n    else if(m[key]!=null)acc[key]+=m[key];}});\n  return acc;\n}\nfunction scaleRollup(m,f){const o=JSON.parse(JSON.stringify(m));for(const key in o){ if(key==='partners'||key==='taxBreak'){for(const s in o[key])o[key][s]*=f;} else o[key]*=f; }return o;}\nconst NET_IDS=['jax'];\nfunction destRollup(id,k){return rollup(k, DESTINATIONS[id].locations);}\nfunction networkAgg(k=1){const parts=NET_IDS.map(id=>destRollup(id,k));const real=mergeRollups(parts);const net=scaleRollup(real,1);net.kiosks=real.kiosks;net.destinations=1;return {real,net,others:scaleRollup(real,0),scale:1,parts};}\nfunction networkSeries(k=1){return rollupSeries(k, LOCS());}\n\n\/* ============ ENGAGEMENT DRILL-THROUGH ============ *\/\nconst MODULES=[\n  {id:'map',    name:'OMNI Maps',                     tag:'attractions', share:.14, dwell:80,  intentRel:0.9, convRel:0.8},\n  {id:'events', name:'Events &amp; Things To Do',     tag:'events',      share:.14, dwell:92,  intentRel:1.2, convRel:1.1},\n  {id:'itin',   name:'OMNI Itinerary',                tag:'attractions', share:.13, dwell:150, intentRel:1.9, convRel:1.6},\n  {id:'dine',   name:'Dining &amp; Restaurants',      tag:'dining',      share:.13, dwell:98,  intentRel:1.3, convRel:1.2},\n  {id:'ai',     name:'TrueAI Conversational Assist',  tag:'attractions', share:.11, dwell:165, intentRel:1.7, convRel:1.5},\n  {id:'stay',   name:'Lodging &amp; Room Blocks',     tag:'hotels',      share:.10, dwell:110, intentRel:1.7, convRel:1.6},\n  {id:'wayfind',name:'OMNI Wayfinding',               tag:'attractions', share:.08, dwell:70,  intentRel:0.7, convRel:0.6},\n  {id:'deals',  name:'Deals &amp; Passes',            tag:'events',      share:.07, dwell:120, intentRel:2.1, convRel:1.9},\n  {id:'phone',  name:'Mobile Handoff (Send to Phone)',tag:'hotels',      share:.04, dwell:34,  intentRel:2.4, convRel:2.3},\n  {id:'guest',  name:'OMNI GuestBook (lead capture)', tag:'hotels',      share:.03, dwell:44,  intentRel:2.3, convRel:2.2},\n  {id:'lang',   name:'Multi-language &amp; ADA',      tag:'attractions', share:.03, dwell:40,  intentRel:0.5, convRel:0.4},\n];\nconst CAMP_TYPE={ 'Pass \/ loyalty':'#1C9CD8','Automation':'#1BAF7A','Partner co-op':'#EB6834','Group \/ MICE':'#14508C','Paid media':'#E87BA4','Content':'#008300','Itinerary \/ CRM':'#6B4FB0','Promotions':'#EDA100' };\nconst CAMPAIGNS=[\n  {id:'sponsored', name:'Sponsored Placements (ad partners)', type:'Paid media', share:0.17, intentRel:0.9, convRel:0.8, cpi:0.55},\n  {id:'beaches', name:'Beaches &amp; Coastal Guide', type:'Content', share:0.16, intentRel:1.2, convRel:1.1, cpi:0.6},\n  {id:'staylonger', name:'Stay-Longer &mdash; 3.7 to 4+ Nights', type:'Automation', share:0.15, intentRel:1.9, convRel:1.8, cpi:0.6},\n  {id:'delegate', name:'Convention Delegate Welcome', type:'Group \/ MICE', share:0.13, intentRel:1.5, convRel:1.4, cpi:0.85},\n  {id:'gameday', name:'Jaguars &amp; EverBank Gameday', type:'Promotions', share:0.13, intentRel:1.4, convRel:1.6, cpi:0.95},\n  {id:'riverside', name:'Riverside &amp; Avondale Dining Co-op', type:'Partner co-op', share:0.12, intentRel:1.4, convRel:1.3, cpi:1.05},\n  {id:'retail', name:'St. Johns Town Center Retail', type:'Promotions', share:0.08, intentRel:1.2, convRel:1.3, cpi:0.9},\n  {id:'museums', name:'Museums &amp; Riverwalk Pass', type:'Pass \/ loyalty', share:0.06, intentRel:1.6, convRel:1.5, cpi:0.9},\n];\nconst AB_TESTS=[\n  {id:'t1', camp:'staylonger', name:'Stay-Longer &mdash; framing the 4th night', a:'&ldquo;Add a night&rdquo;', b:'&ldquo;One more beach day&rdquo;', split:.5, bLift:1.21, conf:0.96, status:'B winning'},\n  {id:'t2', camp:'beaches', name:'Beaches guide &mdash; entry point', a:'Map-first', b:'Best-for-you quiz', split:.5, bLift:1.15, conf:0.94, status:'B winning'},\n  {id:'t3', camp:'sponsored', name:'Sponsored placement &mdash; layout', a:'Carousel', b:'Full-card', split:.5, bLift:0.94, conf:0.88, status:'A winning'},\n  {id:'t4', camp:'gameday', name:'Gameday &mdash; audience framing', a:'Home fans', b:'Visiting fans', split:.5, bLift:1.08, conf:0.72, status:'No winner yet'},\n];\nfunction splitFunnel(m, items, segShares){\n  const sSum=items.reduce((a,b)=>a+b.share,0);\n  const rows=items.map(it=>{\n    let w=it.share\/sSum;\n    if(segShares && it.tag){ w*=Math.max(0.35, 1+0.7*((segShares[it.tag]||.25)-0.25)); }\n    return {it, w};\n  });\n  const wSum=rows.reduce((a,b)=>a+b.w,0);\n  rows.forEach(r=>r.w\/=wSum);\n  const hiRaw=rows.map(r=>r.w*r.it.intentRel), hiS=hiRaw.reduce((a,b)=>a+b,0);\n  const cvRaw=rows.map((r,i)=>hiRaw[i]*r.it.convRel), cvS=cvRaw.reduce((a,b)=>a+b,0);\n  return rows.map((r,i)=>{\n    const interactions=m.interactions*r.w;\n    const highIntent=m.highIntent*hiRaw[i]\/hiS;\n    const conversions=m.conversions*cvRaw[i]\/cvS;\n    const exposures=m.exposures*r.w;\n    const impact=m.total*cvRaw[i]\/cvS;\n    const timeHrs=r.it.dwell? interactions*r.it.dwell\/3600 : null;\n    const spend=r.it.cpi!=null? interactions*r.it.cpi : null;\n    return {...r.it, exposures, interactions, highIntent, conversions, impact, timeHrs, spend,\n      convRate:conversions\/interactions, roas:spend?impact\/spend:null, valPerK:impact\/(interactions\/1000)};\n  });\n}\nfunction segSharesOf(m){const t=Object.values(m.partners).reduce((a,b)=>a+b,0)||1;const o={};for(const s in m.partners)o[s]=m.partners[s]\/t;return o;}\n<\/script>\n\n<!-- =================== FORMAT + CHART HELPERS =================== -->\n<script id=\"helpers\">\nconst $=(s,r=document)=>r.querySelector(s);\nconst NS='http:\/\/www.w3.org\/2000\/svg';\nfunction el(t,a={},kids=[]){const e=document.createElementNS(NS,t);for(const k in a)e.setAttribute(k,a[k]);(Array.isArray(kids)?kids:[kids]).forEach(c=>c!=null&&e.appendChild(typeof c==='string'?document.createTextNode(c):c));return e;}\nlet CUR='USD';                 \/\/ US destination \u2014 single currency, no toggle\nconst FX={USD:1};              \/\/ data stored in USD\nfunction curSym(){return '$';}\nfunction money(v){v=v*FX[CUR];const s=v<0?'-':'';v=Math.abs(v);const c=curSym();\n  if(v>=1e9)return s+c+(v\/1e9).toFixed(v>=1e10?0:2).replace(\/\\.0+$\/,'')+'B';\n  if(v>=1e6)return s+c+(v\/1e6).toFixed(v>=1e7?1:2).replace(\/\\.?0+$\/,'')+'M';\n  if(v>=1e3)return s+c+(v\/1e3).toFixed(0)+'K';\n  return s+c+Math.round(v);}\nfunction money2(v){return curSym()+Math.round(v*FX[CUR]).toLocaleString();}\nfunction num(v){if(v>=1e6)return (v\/1e6).toFixed(v>=1e7?1:2).replace(\/\\.?0+$\/,'')+'M';if(v>=1e3)return (v\/1e3).toFixed(v>=1e4?0:1).replace(\/\\.0$\/,'')+'K';return Math.round(v).toLocaleString();}\nfunction pct(v,d=0){return (v*100).toFixed(d)+'%';}\nfunction num2(v){return Math.round(v).toLocaleString();}\n\nconst TT=$('#tt');\nfunction showTT(html,e){TT.innerHTML=html;TT.style.opacity=1;moveTT(e);}\nfunction moveTT(e){const p=12;let x=e.clientX+p,y=e.clientY+p;const r=TT.getBoundingClientRect();if(x+r.width>innerWidth-8)x=e.clientX-r.width-p;if(y+r.height>innerHeight-8)y=e.clientY-r.height-p;TT.style.left=x+'px';TT.style.top=y+'px';}\nfunction hideTT(){TT.style.opacity=0;}\n\n\/* ---- Area\/line trend chart with crosshair+tooltip ---- *\/\nfunction areaChart(values,labels,opt={}){\n  const w=opt.w||660,h=opt.h||210,pl=42,pr=16,pt=14,pb=26;\n  const iw=w-pl-pr,ih=h-pt-pb;\n  const max=Math.max(...values)*1.12,min=0;\n  const X=i=>pl+iw*(i\/(values.length-1));\n  const Y=v=>pt+ih*(1-(v-min)\/(max-min));\n  const svg=el('svg',{viewBox:`0 0 ${w} ${h}`,width:'100%',role:'img'});\n  \/\/ gridlines + y ticks\n  const ticks=4;\n  for(let t=0;t<=ticks;t++){const val=max*t\/ticks;const y=Y(val);\n    svg.appendChild(el('line',{x1:pl,x2:w-pr,y1:y,y2:y,stroke:'var(--grid)','stroke-width':1}));\n    svg.appendChild(el('text',{x:pl-8,y:y+3.5,'text-anchor':'end','font-size':10,fill:'var(--muted)'},money(val)));\n  }\n  \/\/ area path\n  let d='M'+X(0)+' '+Y(values[0]);values.forEach((v,i)=>{if(i)d+=' L'+X(i)+' '+Y(v);});\n  let area=d+` L${X(values.length-1)} ${Y(0)} L${X(0)} ${Y(0)} Z`;\n  const gid='g'+Math.random().toString(36).slice(2,7);\n  const defs=el('defs');const lg=el('linearGradient',{id:gid,x1:0,y1:0,x2:0,y2:1});\n  lg.appendChild(el('stop',{offset:'0%','stop-color':'var(--azure)','stop-opacity':.28}));\n  lg.appendChild(el('stop',{offset:'100%','stop-color':'var(--azure)','stop-opacity':.02}));\n  defs.appendChild(lg);svg.appendChild(defs);\n  svg.appendChild(el('path',{d:area,fill:`url(#${gid})`}));\n  svg.appendChild(el('path',{d,fill:'none',stroke:'var(--azure)','stroke-width':2.4,'stroke-linejoin':'round','stroke-linecap':'round'}));\n  \/\/ x labels (every other)\n  labels.forEach((lb,i)=>{if(i%2===0||i===labels.length-1)svg.appendChild(el('text',{x:X(i),y:h-8,'text-anchor':'middle','font-size':10,fill:'var(--muted)'},lb));});\n  \/\/ end dot\n  svg.appendChild(el('circle',{cx:X(values.length-1),cy:Y(values.at(-1)),r:4.5,fill:'var(--azure)',stroke:'var(--surface)','stroke-width':2}));\n  \/\/ crosshair layer\n  const cx=el('line',{x1:0,x2:0,y1:pt,y2:pt+ih,stroke:'var(--azure)','stroke-width':1,'stroke-dasharray':'3 3',opacity:0});\n  const cdot=el('circle',{r:5,fill:'var(--azure)',stroke:'var(--surface)','stroke-width':2,opacity:0});\n  svg.appendChild(cx);svg.appendChild(cdot);\n  const hit=el('rect',{x:pl,y:pt,width:iw,height:ih,fill:'transparent'});\n  hit.addEventListener('mousemove',ev=>{\n    const rect=svg.getBoundingClientRect();const rel=(ev.clientX-rect.left)\/rect.width*w;\n    let i=Math.round((rel-pl)\/iw*(values.length-1));i=Math.max(0,Math.min(values.length-1,i));\n    cx.setAttribute('x1',X(i));cx.setAttribute('x2',X(i));cx.setAttribute('opacity',.6);\n    cdot.setAttribute('cx',X(i));cdot.setAttribute('cy',Y(values[i]));cdot.setAttribute('opacity',1);\n    showTT(`<div class=\"tt-t\">${labels[i]} 2026<\/div><div class=\"tt-r\">Economic impact <b>${money(values[i])}<\/b><\/div>`,ev);\n  });\n  hit.addEventListener('mouseleave',()=>{cx.setAttribute('opacity',0);cdot.setAttribute('opacity',0);hideTT();});\n  svg.appendChild(hit);\n  return svg;\n}\n\n\/* ---- horizontal ranked stacked bars (tax local+state, per location) ---- *\/\nfunction rankedStacked(rows,opt={}){\n  \/\/ rows: [{label, parts:[{v,color,name}], total, sub, onClick}]\n  const fmt=opt.fmt||money, tfmt=opt.tfmt||money2;\n  const w=opt.w||620,rowH=34,gap=10,pl=opt.pl||120,pr=opt.pr||64,pt=6;\n  const h=pt+rows.length*(rowH+gap);\n  const max=Math.max(...rows.map(r=>r.total))*1.02;\n  const iw=w-pl-pr;\n  const svg=el('svg',{viewBox:`0 0 ${w} ${h}`,width:'100%'});\n  rows.forEach((r,ri)=>{\n    const y=pt+ri*(rowH+gap);\n    svg.appendChild(el('text',{x:pl-10,y:y+rowH\/2+4,'text-anchor':'end','font-size':12,'font-weight':600,fill:'var(--text)'},r.label));\n    let x=pl;\n    r.parts.forEach((p,pi)=>{\n      const bw=iw*(p.v\/max);\n      const g=el('g',{class:'barseg',style:'cursor:pointer'});\n      const rc=el('rect',{x:x,y:y,width:Math.max(0,bw-2),height:rowH,rx:(pi===r.parts.length-1?6:0),fill:p.color});\n      g.appendChild(rc);\n      g.addEventListener('mousemove',ev=>showTT(`<div class=\"tt-t\">${r.label}<\/div><div class=\"tt-r\">${p.name} <b>${tfmt(p.v)}<\/b><\/div>`,ev));\n      g.addEventListener('mouseleave',hideTT);\n      if(r.onClick)g.addEventListener('click',r.onClick);\n      svg.appendChild(g);x+=bw;\n    });\n    svg.appendChild(el('text',{x:x+8,y:y+rowH\/2+4,'font-size':12.5,'font-weight':700,fill:'var(--text)'},fmt(r.total)));\n  });\n  return svg;\n}\n\n\/* ---- donut for partner mix ---- *\/\nfunction donut(parts,opt={}){ \/\/ parts:[{v,color,name}]\n  const size=opt.size||190,r=size\/2,rin=r*0.62,cx=r,cy=r;\n  const tot=parts.reduce((a,p)=>a+p.v,0);let a0=-Math.PI\/2;\n  const svg=el('svg',{viewBox:`0 0 ${size} ${size}`,width:size,height:size});\n  parts.forEach(p=>{\n    const a1=a0+ (p.v\/tot)*Math.PI*2;\n    const x0=cx+r*Math.cos(a0),y0=cy+r*Math.sin(a0),x1=cx+r*Math.cos(a1),y1=cy+r*Math.sin(a1);\n    const xi0=cx+rin*Math.cos(a0),yi0=cy+rin*Math.sin(a0),xi1=cx+rin*Math.cos(a1),yi1=cy+rin*Math.sin(a1);\n    const large=(a1-a0)>Math.PI?1:0;\n    const d=`M${x0} ${y0} A${r} ${r} 0 ${large} 1 ${x1} ${y1} L${xi1} ${yi1} A${rin} ${rin} 0 ${large} 0 ${xi0} ${yi0} Z`;\n    const path=el('path',{d,fill:p.color,stroke:'var(--surface)','stroke-width':2,style:'cursor:pointer;transition:.15s'});\n    path.addEventListener('mousemove',ev=>showTT(`<div class=\"tt-t\">${p.name}<\/div><div class=\"tt-r\">Impact <b>${money(p.v)}<\/b><\/div><div class=\"tt-r\">Share <b>${pct(p.v\/tot,0)}<\/b><\/div>`,ev));\n    path.addEventListener('mouseleave',hideTT);\n    if(p.onClick)path.addEventListener('click',p.onClick);\n    svg.appendChild(path);a0=a1;\n  });\n  svg.appendChild(el('text',{x:cx,y:cy-3,'text-anchor':'middle','font-size':20,'font-weight':700,fill:'var(--text)'},money(tot)));\n  svg.appendChild(el('text',{x:cx,y:cy+14,'text-anchor':'middle','font-size':10.5,fill:'var(--muted)'},'partner impact'));\n  return svg;\n}\n\n\/* ---- stacked column: direct vs influenced by location ---- *\/\nfunction stackedCols(rows,opt={}){ \/\/ rows:[{label,direct,influenced,onClick}]\n  const w=opt.w||620,h=opt.h||230,pl=42,pr=10,pt=12,pb=34;\n  const iw=w-pl-pr,ih=h-pt-pb,n=rows.length;\n  const max=Math.max(...rows.map(r=>r.direct+r.influenced))*1.14;\n  const bw=Math.min(30,iw\/n*0.52),slot=iw\/n;\n  const Y=v=>pt+ih*(1-v\/max);\n  const svg=el('svg',{viewBox:`0 0 ${w} ${h}`,width:'100%'});\n  for(let t=0;t<=4;t++){const val=max*t\/4,y=Y(val);svg.appendChild(el('line',{x1:pl,x2:w-pr,y1:y,y2:y,stroke:'var(--grid)','stroke-width':1}));svg.appendChild(el('text',{x:pl-8,y:y+3.5,'text-anchor':'end','font-size':10,fill:'var(--muted)'},money(val)));}\n  rows.forEach((r,i)=>{\n    const cx=pl+slot*i+slot\/2, x=cx-bw\/2;\n    const hD=ih*r.direct\/max, hI=ih*r.influenced\/max;\n    const yD=Y(r.direct), yI=Y(r.direct+r.influenced);\n    const gI=el('rect',{x,y:yI,width:bw,height:Math.max(0,hI-1),rx:5,fill:'var(--s-influenced)',style:'cursor:pointer'});\n    const gD=el('rect',{x,y:yD,width:bw,height:hD,rx:0,fill:'var(--s-direct)',style:'cursor:pointer'});\n    [ ['Direct',r.direct,gD],['Influenced',r.influenced,gI] ].forEach(([nm,v,g])=>{\n      g.addEventListener('mousemove',ev=>showTT(`<div class=\"tt-t\">${r.label}<\/div><div class=\"tt-r\">${nm} <b>${money(v)}<\/b><\/div><div class=\"tt-r\">Total <b>${money(r.direct+r.influenced)}<\/b><\/div>`,ev));\n      g.addEventListener('mouseleave',hideTT);if(r.onClick)g.addEventListener('click',r.onClick);\n    });\n    svg.appendChild(gI);svg.appendChild(gD);\n    svg.appendChild(el('text',{x:cx,y:h-19,'text-anchor':'middle','font-size':10,'font-weight':600,fill:'var(--text-2)'},r.label));\n    svg.appendChild(el('text',{x:cx,y:h-8,'text-anchor':'middle','font-size':9.5,fill:'var(--muted)'},money(r.direct+r.influenced)));\n  });\n  return svg;\n}\n<\/script>\n\n<!-- =================== AI NARRATIVE ENGINE =================== -->\n<script id=\"ai\">\n\/* Lightweight on-device narrative generator. Reads the derived model and writes\n   the dollars-first, council-ready sentence a director takes into a budget meeting.\n   (In production this is the TrueImpact AI summarizer over the live attribution feed.) *\/\nfunction aiOverview(k){\n  const m=rollup(k), locs=LOCS();\n  const best=[...locs].sort((a,b)=>deriveMetrics(b,k).total-deriveMetrics(a,k).total)[0];\n  const rate=[...locs].sort((a,b)=>b.interactionRate-a.interactionRate)[0];\n  const ad=adTotals(k);\n  return `Across <b>${m.kiosks} kiosks<\/b> and <b>${ad.sg?ad.sg.screens:0} digital-signage screens<\/b> in <b>${locs.length} ${ACT().region} zones<\/b>, TrueImpact projects <span class=\"em\">${money(m.total)}<\/span> in visitor-driven economic impact and <span class=\"em\">${money(m.tax)}<\/span> in estimated public tax this period \u2014 <b>${money(m.taxLocal)}<\/b> of it local. That impact is <b>${pct(m.total\/DEST_META.spendBase,2)}<\/b> of the <b>${money(DEST_META.spendBase)}<\/b> in direct visitor spending the destination already reports: the conservative end of the influence ladder, and a deliberately thin, provable slice rather than a claim on the whole visitor economy. <b>${best.name}<\/b> leads on impact; <b>${rate.name}<\/b> engages the highest share of visitors at <b>${pct(rate.interactionRate)}<\/b>. Advertising across both surfaces is modeled at <span class=\"em\">${money(ad.rev)}<\/span> per year, repaying the hardware in about ${Math.round(ad.netPaybackYr*12)} months. An estimated <span class=\"em\">${money(m.recirc)}<\/span> re-circulates back through the local economy.`;\n}\nfunction aiScenarios(k){\n  const s=scenarioRows(k), m=rollup(k), ad=adTotals(k);\n  return `The destination already reports <b>${money(s.base)}<\/b> of direct visitor spending. This dashboard does not claim to add to it \u2014 it models what happens when a network of <b>${m.kiosks} kiosks<\/b> influences a fraction of the decisions inside it. Influencing <b>0.25%<\/b> of that base would be worth <span class=\"em\">${money(s.base*0.0025)}<\/span>; <b>1.00%<\/b> would be <span class=\"em\">${money(s.base*0.01)}<\/span>. Built bottom-up from <b>${num2(m.exposures)}<\/b> modeled encounters and real funnel rates, this network lands at <span class=\"em\">${pct(s.modelPct,2)}<\/span> \u2014 <b>${money(s.modelValue)}<\/b>${s.below?`, clearing the ${pct(s.below.p,2)} rung`:''}. That is the right place for a pre-deployment number: at the conservative end of a ladder the destination can already read for itself. The kiosk does not have to create this spending. Its job is to influence decisions visitors are already making \u2014 where they eat, what they visit, how long they stay \u2014 and, more durably, to generate the first-party engagement data the destination has never owned.`;\n}\nfunction aiTax(k){\n  const m=rollup(k),b=m.taxBreak,locs=LOCS();\n  const top=Object.entries(b).sort((x,y)=>y[1]-x[1])[0];\n  const nm=TAXL[top[0]];\n  const hi=[...locs].sort((a,b)=>b.tax.tot-a.tax.tot)[0], lo=[...locs].sort((a,b)=>a.tax.tot-b.tax.tot)[0];\n  const cmp = hi.tax.tot!==lo.tax.tot ? ` Because rates are set locally, the same visitor is worth more in <b>${hi.name} (${hi.tax.tot}% lodging tax)<\/b> than in <b>${lo.name} (${lo.tax.tot}%)<\/b>.` : '';\n  const pt=DEST_META.publishedTax;\n  const ptNum = pt? (pt.cmp==='hotelTOT'? b.hotelTOT : pt.cmp==='taxLocal'? m.taxLocal : m.tax) : 0;\n  const ptTxt = pt? ` For scale: the destination reports <b>${money(pt.v)}<\/b> of ${pt.l}, so the comparable slice this network attributes is about <b>${pct(ptNum\/pt.v,2)}<\/b> of it.` : '';\n  return `Attributed visitor spending is projected to generate an estimated <span class=\"em\">${money(m.tax)}<\/span> in tax this period \u2014 <b>${money(m.taxLocal)}<\/b> that ${TAXL.localName.toLowerCase()}, and <b>${money(m.taxState)}<\/b> to the ${TAXL.stateName}. The single largest line is <b>${nm}<\/b> at <span class=\"em\">${money(top[1])}<\/span>.${ptTxt} Re-spent through public budgets, the local share drives an estimated <span class=\"em\">${money(m.recirc)}<\/span> of induced local activity \u2014 a soft benefit on top of the hard capture.`;\n}\nfunction aiLocation(L,k){\n  const m=deriveMetrics(L,k),r=L.tax;\n  return `<b>${L.name}<\/b> is projected to turn <b>${num(m.interactions)}<\/b> kiosk interactions into <span class=\"em\">${money(m.total)}<\/span> of economic impact and <span class=\"em\">${money(m.tax)}<\/span> of public tax (lodging ${r.tot}%, sales ${r.sales}%) \u2014 <b>${money(m.taxLocal)}<\/b> of it local. ${L.note} It carries <b>${L.screens||0}<\/b> digital-signage screen${(L.screens||0)!==1?'s':''}, which contribute reach and ad inventory but no attributed spend. Partners captured <b>${money(Object.values(m.partners).reduce((a,b)=>a+b,0))}<\/b>, led by ${SEG[Object.entries(m.partners).sort((a,b)=>b[1]-a[1])[0][0]].name.toLowerCase()}.`;\n}\nfunction aiNetwork(k){\n  const {net}=networkAgg(k);\n  return `Across the full TrueOmni network \u2014 <b>${net.destinations} destinations<\/b> and <b>${num(net.kiosks)} kiosks<\/b> in North America and Japan \u2014 the platform attributed <span class=\"em\">${money(net.total)}<\/span> of visitor-driven economic impact and <span class=\"em\">${money(net.tax)}<\/span> in estimated public tax revenue this period. This is the first time the destination industry has an aggregate impact number built from <b>ledgered first-party behavioral data<\/b> rather than modeled surveys. Every destination below drills down to its own kiosks.`;\n}\nfunction aiEngagement(rows, mode, scopeName){\n  const attn=[...rows].sort((a,b)=>b.interactions-a.interactions)[0];\n  const val=[...rows].sort((a,b)=>b.impact-a.impact)[0];\n  const invest= mode==='campaigns'\n      ? [...rows].filter(r=>r.roas).sort((a,b)=>b.roas-a.roas)[0]\n      : [...rows].sort((a,b)=>b.valPerK-a.valPerK)[0];\n  const gap = attn.id!==val.id\n      ? ` The gap is the story: <b>${attn.name}<\/b> draws the most attention, but <b>${val.name}<\/b> converts it into the most attributed dollars \u2014 so raw attention isn't the same as value.`\n      : ` <b>${attn.name}<\/b> both draws the most attention and drives the most dollars.`;\n  const investLine = mode==='campaigns'\n      ? ` For spend, <b>${invest.name}<\/b> returns the best ROI at <span class=\"em\">${invest.roas.toFixed(1)}\u00d7<\/span> \u2014 invest here first.`\n      : ` <b>${invest.name}<\/b> punches above its attention (${money(invest.valPerK)} attributed per 1K interactions) \u2014 the efficient place to feature and cross-promote.`;\n  return `In <b>${scopeName}<\/b>, the ${mode==='campaigns'?'campaigns':'kiosk modules'} split the funnel from exposure to attributed conversion.${gap}${investLine} Rank by any funnel stage using the metric toggle; filter by market to see how the mix shifts location to location.`;\n}\nfunction aiPartner(k){\n  const m=rollup(k),tot=Object.values(m.partners).reduce((a,b)=>a+b,0);\n  const top=Object.entries(m.partners).sort((a,b)=>b[1]-a[1])[0];\n  return `TrueImpact attributed <span class=\"em\">${money(tot)}<\/span> of visitor spending to partner businesses \u2014 hotels, restaurants, attractions and event venues \u2014 every dollar tied to a kiosk referral, QR scan, pass pull or partner-POS event. <b>${SEG[top[0]].name}<\/b> captured the most at <span class=\"em\">${money(top[1])}<\/span>. This is the number a partner sees in their own portal, and the reason ${'>'}30% upgrade to premium membership within 18 months.`;\n}\nfunction aiBanner(tag,html){\n  return `<div class=\"ai-banner fade\"><div class=\"ai-ic\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M12 3l1.9 4.6L18 9l-4.1 1.4L12 15l-1.9-4.6L6 9l4.1-1.4zM19 14l.9 2.2L22 17l-2.1.8L19 20l-.9-2.2L16 17l2.1-.8z\" fill=\"#fff\"\/><\/svg><\/div><div><span class=\"ai-tag\">${tag}<\/span><div class=\"ai-txt\">${html}<\/div><\/div><\/div>`;\n}\n<\/script>\n\n<!-- =================== APP \/ ROUTER \/ PAGES =================== -->\n<script id=\"app-logic\">\nlet PERIOD='t12';\nlet engMode='modules', engLoc='all', engMetric='interactions';   \/\/ engagement drill-through filters\nconst app=$('#app');\nfunction K(){return PERIODS[PERIOD].k;}\nfunction periodLabel(){return PERIODS[PERIOD].label;}\n\n\/* ---------- helpers to build DOM cards ---------- *\/\nfunction h(html){const t=document.createElement('template');t.innerHTML=html.trim();return t.content.firstChild;}\nfunction card(title,hint,bodyNodes,cls=''){\n  const c=h(`<div class=\"card ${cls}\"><div class=\"card-h\"><div><h3>${title}${hint?` <span class=\"hint\">${hint}<\/span>`:''}<\/h3><\/div><\/div><\/div>`);\n  (Array.isArray(bodyNodes)?bodyNodes:[bodyNodes]).forEach(n=>n&&c.appendChild(typeof n==='string'?h(n):n));\n  return c;\n}\nfunction legend(items){const l=h('<div class=\"legend\"><\/div>');items.forEach(it=>l.appendChild(h(`<span class=\"li\"><span class=\"sw ${it.line?'line':''}\" style=\"background:${it.color}\"><\/span>${it.name}<\/span>`)));return l;}\nfunction shortName(n){return n.replace(' Lake','').replace(' Valley','').replace(' Bay','').replace('Fort ','Ft ').replace(' Springs','').replace('San Francisco','SF').replace('Los Angeles','LA').replace('Magnificent Mile','Mag Mile').replace('McCormick Place','McCormick').replace('Museum Campus','Museum').split(' (')[0];}\nfunction engShort(n){return n.replace(\/\\s*\\(.*\\)\/,'').replace('Interactive Map & Wayfinding','Map & Wayfinding').replace(' Placements','').replace('Local ','').replace('-Season Getaway','-Season').replace(' & Coupons','').replace(' Push','').replace(' Welcome','').replace(' & Restaurants','').replace(' to Do & Attractions',' to Do').replace(' Builder','').replace(' Calendar','').replace(' & Parking','');}\nfunction setDest(id){activeDest=id;engLoc='all';location.hash='#\/overview';render();}\nfunction engMiniCard(k){\n  const sc=engScope(k); const rows=splitFunnel(sc.m, MODULES, sc.seg).sort((a,b)=>b.interactions-a.interactions).slice(0,5);\n  const max=rows[0].interactions;\n  const c=card('Module &amp; campaign engagement','what draws attention \u2014 drill the funnel','',' ');\n  const list=h('<div class=\"loclist\"><\/div>');\n  rows.forEach(r=>{const row=h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div><div class=\"lr-name\" style=\"display:flex;align-items:center;gap:7px\"><span class=\"sw\" style=\"width:11px;height:11px;border-radius:3px;background:${SEG[r.tag].color}\"><\/span>${r.name}<\/div><\/div><div class=\"lr-val\">${num(r.interactions)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(r.impact)}<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(r.interactions\/max*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    row.addEventListener('click',()=>go('#\/modules'));list.appendChild(row);});\n  c.appendChild(list);\n  const btns=h('<div style=\"display:flex;gap:8px;margin-top:12px\"><\/div>');\n  btns.appendChild(h(`<button class=\"chip\" style=\"cursor:pointer\" onclick=\"go('#\/modules')\">Modules \u203a<\/button>`));\n  btns.appendChild(h(`<button class=\"chip\" style=\"cursor:pointer\" onclick=\"go('#\/campaigns')\">Campaigns &amp; A\/B tests \u203a<\/button>`));\n  c.appendChild(btns);\n  return c;\n}\n\n\/* ================= OVERVIEW ================= *\/\nfunction pageOverview(){\n  const k=K(),m=rollup(k);\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">${ACT().kind} \u00b7 Executive Dashboard<\/div><h1>${ACT().name} \u2014 ${ACT().scope} Impact<\/h1><\/div><div class=\"meta\">Reporting period<br><b style=\"color:var(--text);font-size:14px\">${periodLabel()}<\/b><br>${m.kiosks} kiosks \u00b7 ${m.screens} screens \u00b7 ${LOCS().length} zones<\/div><\/div>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>${ACT().name}<\/span><span class=\"sep\">\u203a<\/span><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>All zones<\/span><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Board-ready summary',aiOverview(k))));\n  wrap.appendChild(destContextStrip()); wrap.lastChild.style.marginBottom='16px';\n  wrap.appendChild(scenariosMiniCard(k)); wrap.lastChild.style.marginBottom='16px';\n  wrap.appendChild(rampCard(k)); wrap.lastChild.style.marginBottom='16px';\n\n  \/\/ KPI band\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Total economic impact<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(m.total).replace(curSym(),'')}<\/div><div class=\"k-delta up\">\u25b2 18.4% vs prior period<\/div><div class=\"k-foot\">Direct ${money(m.direct)} + Influenced ${money(m.influenced)} \u00b7 dollars-first, ledger-level<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Estimated tax revenue',money(m.tax),'\u25b2 16.1%',`Local ${money(m.taxLocal)} \u00b7 ${TAXL.stateName} ${money(m.taxState)}`,()=>go('#\/tax')));\n  kpis.appendChild(kpiTile('Partner-captured spend',money(Object.values(m.partners).reduce((a,b)=>a+b,0)),'\u25b2 21.0%','Hotels \u00b7 dining \u00b7 attractions \u00b7 events',()=>go('#\/partners')));\n  kpis.appendChild(kpiTile('Trackable visitor actions',num(m.interactions),'\u25b2 12.7%',`${num(m.conversions)} attributed conversions`));\n  wrap.appendChild(kpis);\n\n  \/\/ Map + location list\n  const mapRow=h('<div class=\"grid row-map\" style=\"margin-bottom:16px\"><\/div>');\n  mapRow.appendChild(card('Impact by kiosk location','click a market to drill in',[buildMap(k),legend([{name:'Bubble size = economic impact',color:'var(--azure)'}])],'pad0'+' '));\n  const mapCard=mapRow.lastChild;mapCard.style.padding='18px';\n  mapRow.appendChild(card('Zones ranked','by economic impact',[buildLocList(k)]));\n  wrap.appendChild(mapRow);\n\n  \/\/ Funnel + Direct\/influenced by location\n  const r2=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  r2.appendChild(card('The visitor funnel',ACT().scope.toLowerCase(),[buildFunnel(m),h('<div class=\"note\">Exposure \u2192 interaction \u2192 high-intent action \u2192 attributed conversion. Rates are TrueOmni platform benchmarks; volumes are ledgered kiosk events.<\/div>')]));\n  const dcols=stackedCols(LOCS().map(L=>{const mm=deriveMetrics(L,k);return{label:shortName(L.name),direct:mm.direct,influenced:mm.influenced,onClick:()=>go('#\/location\/'+L.id)};}));\n  r2.appendChild(card('Direct vs influenced impact','by market \u00b7 click to drill',[dcols,legend([{name:'Direct (attributed spend)',color:'var(--s-direct)'},{name:'Influenced (supply-chain + wage tail)',color:'var(--s-influenced)'}])]));\n  wrap.appendChild(r2);\n\n  \/\/ Partner mix + tax preview\n  const r3=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  r3.appendChild(buildPartnerCard(m,k));\n  r3.appendChild(buildTaxPreview(m,k));\n  wrap.appendChild(r3);\n\n  \/\/ Engagement entry\n  wrap.appendChild(engMiniCard(k));\n  wrap.lastChild.style.marginBottom='16px';\n\n  \/\/ Media Network entry\n  wrap.appendChild(mediaMiniCard(k));\n  wrap.lastChild.style.marginBottom='16px';\n\n  \/\/ Trend\n  const trend=card('Economic impact trend','monthly \u00b7 '+periodLabel(),[areaChart(rollupSeries(k),MONTHS,{h:220}),h('<div class=\"note\">This network is modeled to reach <b>'+num2(m.exposures)+' visitor encounters per year<\/b> across '+LOCS().length+' zones. Seasonality follows the destination&rsquo;s own published pattern. Shoulder-season automation &mdash; stay-longer prompts and off-peak deals fired in the soft months &mdash; adds an estimated 8&ndash;12% during those weeks and is the largest untapped lever in the program.<\/div>')]);\n  wrap.appendChild(trend);\n  return wrap;\n}\nfunction rampCard(k){\n  const c=card('Install sequence &middot; 10 kiosks, 5 screens','projected run-rate as each wave comes online (12-week rollout)','',' ');\n  const list=h('<div class=\"loclist\"><\/div>');\n  let prevK=0;\n  PHASES.forEach(ph=>{\n    const locs=LOCS().filter(L=>ph.ids.includes(L.id));\n    const m=rollup(k,locs); const add=m.kiosks-prevK; prevK=m.kiosks;\n    const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${ph.name}<\/div><div class=\"lr-sub\">${ph.window} \u00b7 ${m.kiosks} kiosks \u00b7 ${m.screens} screens live${add?` (+${add} kiosks)`:''}<\/div><\/div><div class=\"lr-val\">${money(m.total)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(m.tax)} tax \u00b7 ${money(m.taxLocal)} local<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(m.kiosks\/NET_KIOSKS*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    list.appendChild(row);\n  });\n  c.appendChild(list);\n  c.appendChild(h('<div class=\"note\">Cumulative annualized run-rate at each wave &mdash; an <b>install order inside a fixed 10-kiosk \/ 5-screen network<\/b>, not an expansion forecast. Wave 1 goes where dwell is longest and intent is highest; the gateway and core zones follow, then the remaining discovery zones. Figures scale with units online &mdash; not a forecast of new demand.<\/div>'));\n  return c;\n}\nfunction kpiTile(label,val,delta,foot,onClick){\n  const c=h(`<div class=\"card kpi\"${onClick?' style=\"cursor:pointer\"':''}><div class=\"k-label\">${label}<\/div><div class=\"k-val\">${val}<\/div><div class=\"k-delta up\">\u25b2 ${delta.replace('\u25b2','').trim()}<\/div><div class=\"k-foot\">${foot}<\/div><\/div>`);\n  if(onClick)c.addEventListener('click',onClick);\n  return c;\n}\n\n\/* ---- Destination map (pixel-scale viewBox so marks\/labels stay true size).\n   Reads the active destination's outline + each location's x,y. Works for any tenant. ---- *\/\nfunction buildMap(k){\n  const D=ACT(), locs=LOCS();\n  const svg=el('svg',{viewBox:'0 0 480 620',role:'img','aria-label':D.region+' kiosk map'});\n  if(D.lake) svg.appendChild(el('path',{d:D.lake,fill:'var(--s-hotels)','fill-opacity':.12,stroke:'none'}));\n  svg.appendChild(el('path',{d:D.outline,class:'ca-outline'}));\n  if(D.lakeLabel) svg.appendChild(el('text',{x:D.lakeLabel.x,y:D.lakeLabel.y,'text-anchor':'middle','font-size':12,'font-style':'italic',fill:'var(--muted)',stroke:'none'},D.lakeLabel.t));\n  const max=Math.max(...locs.map(L=>deriveMetrics(L,k).total));\n  locs.forEach(L=>{\n    const mm=deriveMetrics(L,k);const rr=9+18*Math.sqrt(mm.total\/max);\n    const px=L.x,py=L.y;\n    const g=el('g',{class:'mk'+(state.loc===L.id?' active':''),transform:`translate(${px},${py})`});\n    g.appendChild(el('circle',{class:'ring',r:rr+5}));\n    g.appendChild(el('circle',{class:'pin',r:rr,fill:'var(--azure)','fill-opacity':.85}));\n    g.appendChild(el('circle',{class:'hit',r:Math.max(rr+7,16)}));\n    if(rr>16)g.appendChild(el('text',{y:4,'text-anchor':'middle','font-size':11,fill:'#fff',stroke:'none'},money(mm.total)));\n    g.appendChild(el('text',{y:rr+15,'text-anchor':'middle','font-size':11.5,'font-weight':700},L.name));\n    g.addEventListener('mousemove',ev=>showTT(`<div class=\"tt-t\">${L.name}<\/div><div class=\"tt-r\">Impact <b>${money(mm.total)}<\/b><\/div><div class=\"tt-r\">Tax <b>${money(mm.tax)}<\/b><\/div><div class=\"tt-r\">Interaction <b>${pct(L.interactionRate)}<\/b><\/div>`,ev));\n    g.addEventListener('mouseleave',hideTT);\n    g.addEventListener('click',()=>go('#\/location\/'+L.id));\n    svg.appendChild(g);\n  });\n  const box=h('<div class=\"mapwrap\"><\/div>');box.appendChild(svg);return box;\n}\nfunction buildLocList(k){\n  const rows=[...LOCS()].map(L=>({L,m:deriveMetrics(L,k)})).sort((a,b)=>b.m.total-a.m.total);\n  const max=rows[0].m.total;\n  const box=h('<div class=\"loclist\"><\/div>');\n  rows.forEach(({L,m})=>{\n    const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${L.name} ${L.badge?`<span class=\"pill tag-hi\">${L.badge}<\/span>`:''}<\/div><div class=\"lr-sub\">${L.sites} \u00b7 ${L.kiosks} kiosks<\/div><\/div><div class=\"lr-val\">${money(m.total)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(m.tax)} tax<\/div><\/div><span class=\"chev\">\u203a<\/span><div class=\"lr-bar\"><i style=\"width:${(m.total\/max*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    row.addEventListener('click',()=>go('#\/location\/'+L.id));\n    box.appendChild(row);\n  });\n  return box;\n}\nfunction buildFunnel(m){\n  const stages=[\n    {n:'Kiosk exposures',v:m.exposures,rate:''},\n    {n:'Interactions',v:m.interactions,rate:'of exposed'},\n    {n:'High-intent actions',v:m.highIntent,rate:pct(FUNNEL.highIntent)+' of interactions'},\n    {n:'Attributed conversions',v:m.conversions,rate:pct(FUNNEL.conversion)+' of high-intent'},\n  ];\n  const max=stages[0].v;const cols=['#0E2240','#14508C','#1C9CD8','#33abe0'];\n  const box=h('<div class=\"funnel\"><\/div>');\n  stages.forEach((s,i)=>{\n    const w=26+40*(s.v\/max);\n    const st=h(`<div class=\"fstage\"><div class=\"fbar\" style=\"width:${w}%;background:${cols[i]}\">${s.n}<\/div><div class=\"fmeta\"><b>${num(s.v)}<\/b> <span class=\"frate\">${s.rate}<\/span><\/div><\/div>`);\n    box.appendChild(st);\n  });\n  return box;\n}\nfunction buildPartnerCard(m,k){\n  const parts=Object.entries(m.partners).map(([id,v])=>({v,name:SEG[id].name,color:SEG[id].color,onClick:()=>go('#\/partners')}));\n  const c=card('Partner economic impact','hotels \u00b7 dining \u00b7 attractions \u00b7 events','',' ');\n  const inner=h('<div style=\"display:flex;gap:20px;align-items:center;flex-wrap:wrap\"><\/div>');\n  inner.appendChild(donut(parts));\n  const list=h('<div style=\"flex:1;min-width:200px\"><\/div>');\n  parts.sort((a,b)=>b.v-a.v).forEach(p=>{\n    const row=h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div><div class=\"lr-name\" style=\"display:flex;align-items:center;gap:7px\"><span class=\"sw\" style=\"width:11px;height:11px;border-radius:3px;background:${p.color}\"><\/span>${p.name}<\/div><\/div><div class=\"lr-val\">${money(p.v)}<\/div><\/div>`);\n    row.addEventListener('click',()=>go('#\/partners'));list.appendChild(row);\n  });\n  inner.appendChild(list);\n  c.appendChild(inner);\n  c.appendChild(h('<div class=\"note\">This is the figure a partner sees in their own portal \u2014 every dollar tied to a kiosk referral, QR scan, pass pull, or partner-POS event. <span class=\"toggle-tbl\" onclick=\"go(\\'#\/partners\\')\">Open partner detail \u203a<\/span><\/div>'));\n  return c;\n}\nfunction buildTaxPreview(m,k){\n  const b=m.taxBreak;\n  const parts=[\n    {v:b.hotelTOT,name:TAXL.hotelTOT,color:'var(--s-local)'},\n    {v:b.salesTax,name:TAXL.salesTax,color:'#33abe0'},\n    {v:b.rentalTax,name:TAXL.rentalTax,color:'#7cc9e8'},\n    {v:b.stateTax,name:TAXL.stateTax,color:'var(--s-state)'},\n  ];\n  const c=card('Estimated tax revenue','locality-based \u00b7 click to drill','',' ');\n  const rows=parts.map(p=>({label:p.name,parts:[{v:p.v,color:p.color,name:p.name}],total:p.v,onClick:()=>go('#\/tax')}));\n  c.appendChild(rankedStacked(rows,{pl:130,w:560}));\n  c.appendChild(h(`<div class=\"gauge-track\"><i style=\"width:${(m.recirc\/(m.recirc+m.tax)*100).toFixed(0)}%\"><\/i><\/div>`));\n  c.appendChild(h(`<div class=\"note\"><b style=\"color:var(--text)\">${money(m.recirc)}<\/b> soft benefit \u2014 estimated local re-circulation of these public tax dollars through community budgets & payroll. <span class=\"toggle-tbl\" onclick=\"go('#\/tax')\">Full tax breakdown \u203a<\/span><\/div>`));\n  return c;\n}\n\n\/* ================= LOCATION DETAIL ================= *\/\nfunction pageLocation(id){\n  const L=findLoc(id);if(!L)return pageOverview();\n  const k=K(),m=deriveMetrics(L,k),r=L.tax;\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> All ${ACT().region} markets<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>${ACT().name}<\/span><span class=\"sep\">\u203a<\/span><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>${L.name}<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">Kiosk market${L.badge?` \u00b7 <span style=\"color:var(--good)\">${L.badge}<\/span>`:''}<\/div><h1>${L.name}<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">${L.sites} \u00b7 ${L.kiosks} kiosks \u00b7 ${periodLabel()}<\/div><\/div><div class=\"meta\">Tax rates<br><b style=\"color:var(--text);font-size:13px\">Lodging ${r.tot}% \u00b7 Sales ${r.sales}%<\/b><\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Market read',aiLocation(L,k))));\n\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Economic impact<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(m.total).replace(curSym(),'')}<\/div><div class=\"k-foot\">Direct ${money(m.direct)} + Influenced ${money(m.influenced)}<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Estimated tax revenue',money(m.tax),'',`Local ${money(m.taxLocal)} \u00b7 State ${money(m.taxState)}`,()=>go('#\/tax')));\n  kpis.appendChild(kpiTile('Partner-captured spend',money(Object.values(m.partners).reduce((a,b)=>a+b,0)),'','across 4 segments',()=>go('#\/partners')));\n  kpis.appendChild(kpiTile('Interaction rate',pct(L.interactionRate),'',`${num(m.interactions)} interactions`));\n  wrap.appendChild(kpis);\n\n  const r2=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  r2.appendChild(card('Visitor funnel','this market',[buildFunnel(m)]));\n  r2.appendChild(buildLocTaxCard(m,L));\n  wrap.appendChild(r2);\n\n  const r3=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  const parts=Object.entries(m.partners).map(([pid,v])=>({v,name:SEG[pid].name,color:SEG[pid].color}));\n  const pc=card('Partner impact \u2014 '+L.name,'who captured the visitor dollar','',' ');\n  const pin=h('<div style=\"display:flex;gap:20px;align-items:center;flex-wrap:wrap\"><\/div>');\n  pin.appendChild(donut(parts));\n  const pl=h('<div style=\"flex:1;min-width:200px\"><\/div>');\n  parts.sort((a,b)=>b.v-a.v).forEach(p=>pl.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\" style=\"display:flex;align-items:center;gap:7px\"><span class=\"sw\" style=\"width:11px;height:11px;border-radius:3px;background:${p.color}\"><\/span>${p.name}<\/div><div class=\"lr-val\">${money(p.v)}<\/div><\/div>`)));\n  pin.appendChild(pl);pc.appendChild(pin);r3.appendChild(pc);\n  r3.appendChild(card('Monthly impact','seasonality',[areaChart(seriesFor(L,k),MONTHS,{h:210})]));\n  wrap.appendChild(r3);\n\n  \/\/ sample named partners\n  wrap.appendChild(buildSamplePartners(L,m));\n  return wrap;\n}\nfunction buildLocTaxCard(m,L){\n  const b=m.taxBreak,r=L.tax;\n  const rows=[\n    {label:TAXL.hotelTOT,parts:[{v:b.hotelTOT,color:'var(--s-local)',name:TAXL.hotelTOT}],total:b.hotelTOT},\n    {label:TAXL.salesTax,parts:[{v:b.salesTax,color:'#33abe0',name:TAXL.salesTax}],total:b.salesTax},\n    {label:TAXL.rentalTax,parts:[{v:b.rentalTax,color:'#7cc9e8',name:TAXL.rentalTax}],total:b.rentalTax},\n    {label:TAXL.stateTax,parts:[{v:b.stateTax,color:'var(--s-state)',name:TAXL.stateTax}],total:b.stateTax},\n  ];\n  const c=card('Estimated tax revenue','locality-based','',' ');\n  c.appendChild(rankedStacked(rows,{pl:150,w:560}));\n  c.appendChild(h(`<div class=\"note\">Rates are set by this jurisdiction \u2014 which is why a visitor here is worth a different tax yield than in any other market. <b style=\"color:var(--text)\">${money(m.recirc)}<\/b> estimated re-circulation (soft benefit).<\/div>`));\n  return c;\n}\nfunction buildSamplePartners(L,m){\n  const SAMPLES={\n    la:['JW Marriott L.A. LIVE','Grand Central Market','Griffith Observatory','Crypto.com Arena'],\n    sf:['Hotel Nikko','Ferry Building Marketplace','Alcatraz Cruises','Moscone Center'],\n    sd:['Hotel del Coronado','Little Italy Food Hall','San Diego Zoo','Petco Park'],\n    ana:['Anaheim Marriott','Downtown Disney Dining','Disneyland Resort','Angel Stadium'],\n    bb:['The Lodge at Big Bear','Peppercorn Grille','Big Bear Mountain Resort','Village Events'],\n    napa:['Carneros Resort','The French Laundry','Castello di Amorosa','Oxbow Public Market'],\n    ps:['Parker Palm Springs','Workshop Kitchen','Palm Springs Tram','Coachella \/ Stagecoach'],\n    sac:['Citizen Hotel','Old Sacramento Waterfront','California State Railroad Museum','Golden 1 Center'],\n    ord:['Hilton O\u2019Hare','Tortas Frontera','Terminal 5 Retail','United Club'],\n    mag:['The Peninsula Chicago','Purple Pig','360 CHICAGO','Water Tower Place'],\n    navy:['Sable at Navy Pier','Giordano\u2019s','Centennial Wheel','Navy Pier Events'],\n    mcc:['Marriott Marquis Chicago','Wintrust Cafe','Field Museum','McCormick Expo Halls'],\n    loop:['JW Marriott Chicago','The Berghoff','Skydeck Willis Tower','Chicago Theatre'],\n    mus:['Hilton Museum Campus','Shedd Cafe','Shedd Aquarium','Adler After Dark'],\n    wrig:['Hotel Zachary','Gallagher Way Dining','Wrigley Field Tours','Cubs Home Games'],\n    orl:['Hilton Bonnet Creek','Disney Springs Dining','Universal Orlando','OCCC Events'],\n    mia:['Fontainebleau Miami','Joe\u2019s Stone Crab','Vizcaya Museum','Ultra \/ Art Basel'],\n    tpa:['Tampa Marriott Water St','Columbia Restaurant','Busch Gardens','Amalie Arena'],\n    ftl:['W Fort Lauderdale','Casa Sensei','Riverwalk Cruises','Broward Convention'],\n    jax:['Marriott Downtown JAX','Cowford Chophouse','MOSH Museum','VyStar Arena'],\n    nap:['Ritz-Carlton Naples','Campiello','Naples Botanical Garden','5th Ave Events'],\n    key:['Casa Marina Resort','Blue Heaven','Mallory Square Sunset','Fantasy Fest'],\n  };\n  const names=SAMPLES[L.id]||['Hotel','Restaurant','Attraction','Venue'];\n  const segIds=['hotels','dining','attractions','events'];\n  const c=card('Sample attributed partners','illustrative \u2014 top referral recipients','',' ');\n  const tbl=h('<table><thead><tr><th>Partner<\/th><th>Segment<\/th><th>Attributed spend<\/th><th>Referrals<\/th><th>Trend<\/th><\/tr><\/thead><tbody><\/tbody><\/table>');\n  const tb=tbl.querySelector('tbody');\n  segIds.forEach((sid,i)=>{\n    const v=m.partners[sid]*(0.34-i*0.03);\n    const refs=Math.round(m.highIntent*L.mix[sid]*0.4);\n    tb.appendChild(h(`<tr><td><b>${names[i]}<\/b><\/td><td><span class=\"pill tag-mid\">${SEG[sid].name}<\/span><\/td><td class=\"tnum\">${money2(v)}<\/td><td class=\"tnum\">${refs.toLocaleString()}<\/td><td><span class=\"pill tag-hi\">\u25b2 ${(8+i*3)}%<\/span><\/td><\/tr>`));\n  });\n  c.appendChild(tbl);\n  return c;\n}\n\n\/* ================= PARTNER DETAIL ================= *\/\nfunction pagePartners(){\n  const k=K(),m=rollup(k);const tot=Object.values(m.partners).reduce((a,b)=>a+b,0);\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>Partner economic impact<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">${ACT().region} \u00b7 Partner economic impact<\/div><h1>What the visitor dollar did for partners<\/h1><\/div><div class=\"meta\">${periodLabel()}<br><b style=\"color:var(--text)\">${money(tot)}<\/b> attributed<\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Partner read',aiPartner(k))));\n\n  \/\/ segment cards\n  const icons={hotels:'M3 21V9l9-6 9 6v12M9 21v-6h6v6',dining:'M6 3v7m0 0a3 3 0 006 0V3M6 10v11M18 3c-2 0-3 2-3 5s1 4 3 4v9',attractions:'M3 21l9-18 9 18M8 21v-5h8v5',events:'M3 8h18M3 8l2-4h14l2 4M5 8v12h14V8'};\n  const cards=h('<div class=\"seg-cards\" style=\"margin-bottom:16px\"><\/div>');\n  Object.entries(m.partners).sort((a,b)=>b[1]-a[1]).forEach(([id,v])=>{\n    const c=h(`<div class=\"seg-card\"><div class=\"sc-top\"><span class=\"sc-ic\" style=\"background:${SEG[id].color}\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"${icons[id]}\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><\/span>${SEG[id].name}<\/div><div class=\"sc-val\">${money(v)}<\/div><div class=\"sc-sub\">${pct(v\/tot,0)} of partner impact<\/div><div class=\"sc-count\"><span>Est. partners<\/span><span>${Math.round(400*SEG_SHARE(id))}<\/span><\/div><\/div>`);\n    cards.appendChild(c);\n  });\n  wrap.appendChild(cards);\n\n  \/\/ impact by segment across locations (stacked ranked)\n  const rows=[...LOCS()].map(L=>{const mm=deriveMetrics(L,k);const p=mm.partners;const t=Object.values(p).reduce((a,b)=>a+b,0);\n    return {label:L.name,total:t,onClick:()=>go('#\/location\/'+L.id),parts:['hotels','dining','attractions','events'].map(s=>({v:p[s],color:SEG[s].color,name:SEG[s].name}))};\n  }).sort((a,b)=>b.total-a.total);\n  const c1=card('Partner impact by market','stacked by segment \u00b7 click a bar to open the market','',' ');\n  c1.appendChild(rankedStacked(rows,{pl:120,w:660}));\n  c1.appendChild(legend(Object.keys(SEG).map(id=>({name:SEG[id].name,color:SEG[id].color}))));\n  wrap.appendChild(c1);\n\n  \/\/ why it matters\n  wrap.appendChild(card('Why partners upgrade','the portal effect',[h(`<div class=\"stat-inline\" style=\"margin-top:6px\"><div class=\"si\"><b>+15\u201330%<\/b><span>itinerary inclusion lift<\/span><\/div><div class=\"si\"><b>+20\u201340%<\/b><span>listing exposure<\/span><\/div><div class=\"si\"><b>+10\u201325%<\/b><span>pass participation<\/span><\/div><div class=\"si\"><b>&gt;30%<\/b><span>upgrade to premium in 18 mo<\/span><\/div><\/div>`),h('<div class=\"note\">The Partner ROI view is the single most-requested feature in the 2026 customer survey. When a restaurant or hotel sees its own attributed dollars, flat-dues membership becomes performance-aligned membership.<\/div>')]));\n  return wrap;\n}\nfunction SEG_SHARE(id){return {hotels:.34,dining:.30,attractions:.22,events:.14}[id];}\n\n\/* ================= TAX DETAIL ================= *\/\nfunction pageTax(){\n  const k=K(),m=rollup(k),b=m.taxBreak;\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>Estimated tax revenue<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">${ACT().region} \u00b7 Public revenue impact<\/div><h1>Estimated tax revenue by zone<\/h1><\/div><div class=\"meta\">${periodLabel()}<br><b style=\"color:var(--text)\">${money(m.tax)}<\/b> total est.<\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Tax read',aiTax(k))));\n\n  \/\/ KPI tiles by tax type\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Total estimated tax revenue<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(m.tax).replace(curSym(),'')}<\/div><div class=\"k-foot\">${TAXL.localName} ${money(m.taxLocal)} + ${TAXL.stateName} ${money(m.taxState)}<\/div><\/div>`));\n  kpis.appendChild(kpiTile(TAXL.hotelTOT,money(b.hotelTOT),'',TAXL.hotelDesc));\n  kpis.appendChild(kpiTile(TAXL.salesTax,money(b.salesTax),'',TAXL.salesDesc));\n  kpis.appendChild(kpiTile(TAXL.stateTax,money(b.stateTax),'',TAXL.stateDesc));\n  wrap.appendChild(kpis);\n\n  \/\/ stacked tax by location (local components + state)\n  const rows=[...LOCS()].map(L=>{const mm=deriveMetrics(L,k),bb=mm.taxBreak;\n    return {label:L.name,total:mm.tax,onClick:()=>go('#\/location\/'+L.id),parts:[\n      {v:bb.hotelTOT,color:'var(--s-local)',name:TAXL.hotelTOT},\n      {v:bb.salesTax,color:'#33abe0',name:TAXL.salesTax},\n      {v:bb.rentalTax,color:'#7cc9e8',name:TAXL.rentalTax},\n      {v:bb.stateTax,color:'var(--s-state)',name:TAXL.stateTax},\n    ]};}).sort((a,b)=>b.total-a.total);\n  const c1=card('Estimated tax revenue by zone','lodging tax &middot; local sales &middot; local levies &middot; state sales &middot; click a bar to drill','',' ');\n  c1.appendChild(rankedStacked(rows,{pl:120,w:680}));\n  c1.appendChild(legend([{name:TAXL.hotelTOT,color:'var(--s-local)'},{name:TAXL.salesTax,color:'#33abe0'},{name:TAXL.rentalTax,color:'#7cc9e8'},{name:TAXL.stateTax,color:'var(--s-state)'}]));\n  c1.appendChild(h(`<div class=\"note\">${TAXL.note}<\/div>`));\n  c1.appendChild(h(`<div class=\"note\">${DEST_META.taxInsight}<\/div>`));\n  wrap.appendChild(c1);\n\n  \/\/ recirculation soft benefit\n  const recircCard=card('Recirculation \u2014 the soft benefit','induced community activity from public tax dollars','',' ');\n  recircCard.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:4px\"><div class=\"si\"><b>${money(m.tax)}<\/b><span>hard tax captured<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${money(m.recirc)}<\/b><span>est. local re-circulation<\/span><\/div><div class=\"si\"><b>${money(m.tax+m.recirc)}<\/b><span>combined public benefit<\/span><\/div><\/div>`));\n  recircCard.appendChild(h(`<div class=\"gauge-track\" style=\"margin-top:14px\"><i style=\"width:${(m.recirc\/(m.tax+m.recirc)*100).toFixed(0)}%\"><\/i><\/div>`));\n  recircCard.appendChild(h('<div class=\"note\">Public revenue does not leave the community \u2014 it funds payroll, services, and local procurement that are re-spent through the regional economy. Modeled conservatively at 0.9\u00d7 of captured tax (induced\/soft). This is the number that turns \"we captured tax\" into \"we grew the local economy.\"<\/div>'));\n  wrap.appendChild(recircCard);\n\n  \/\/ full data table\n  const c2=card('Full tax detail','auditable line items \u00b7 '+periodLabel(),'',' ');\n  const tbl=h('<table><thead><tr><th>Zone<\/th><th>Lodging %<\/th><th>Lodging tax<\/th><th>Local sales<\/th><th>Local levy<\/th><th>State<\/th><th>Total tax<\/th><th>Recirc (soft)<\/th><\/tr><\/thead><tbody><\/tbody><\/table>');\n  const tb=tbl.querySelector('tbody');\n  rows.forEach(r=>{const L=LOCS().find(x=>x.name===r.label);const mm=deriveMetrics(L,k),bb=mm.taxBreak,rt=L.tax;\n    const tr=h(`<tr><td><b>${L.name}<\/b><\/td><td class=\"tnum\">${rt.tot}%<\/td><td class=\"tnum\">${money2(bb.hotelTOT)}<\/td><td class=\"tnum\">${money2(bb.salesTax)}<\/td><td class=\"tnum\">${money2(bb.rentalTax)}<\/td><td class=\"tnum\">${money2(bb.stateTax)}<\/td><td class=\"tnum\"><b>${money2(mm.tax)}<\/b><\/td><td class=\"tnum\" style=\"color:var(--good)\">${money2(mm.recirc)}<\/td><\/tr>`);\n    tr.addEventListener('click',()=>go('#\/location\/'+L.id));tb.appendChild(tr);\n  });\n  c2.appendChild(tbl);\n  wrap.appendChild(c2);\n  return wrap;\n}\n\n\/* ================= NETWORK ROLL-UP (all destinations) ================= *\/\nfunction pageNetwork(){\n  const k=K(),{net,parts}=networkAgg(k);\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">TrueOmni Network \u00b7 All destinations<\/div><h1>Total economic impact \u2014 every kiosk, every destination<\/h1><\/div><div class=\"meta\">${periodLabel()}<br><b style=\"color:var(--text);font-size:14px\">${net.destinations} destinations \u00b7 ${num(net.kiosks)} kiosks<\/b><\/div><\/div>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>TrueOmni Network<\/span><span class=\"sep\">\u203a<\/span><span>All destinations<\/span><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Network roll-up',aiNetwork(k))));\n\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Network economic impact<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(net.total).replace(curSym(),'')}<\/div><div class=\"k-delta up\">\u25b2 22.6% YoY<\/div><div class=\"k-foot\">Direct ${money(net.direct)} + Influenced ${money(net.influenced)} \u00b7 ${net.destinations} destinations, ledger-level<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Public tax revenue',money(net.tax),'\u25b2 19.4%',`Local ${money(net.taxLocal)} \u00b7 State ${money(net.taxState)}`));\n  kpis.appendChild(kpiTile('Partner-captured spend',money(Object.values(net.partners).reduce((a,b)=>a+b,0)),'\u25b2 24.1%','across all destinations'));\n  kpis.appendChild(kpiTile('Trackable visitor actions',num(net.interactions),'\u25b2 15.2%',`${num(net.conversions)} attributed conversions`));\n  wrap.appendChild(kpis);\n\n  \/\/ destinations ranked (real 3 + modeled long-tail)\n  const destRows=NET_IDS.map((id,i)=>({id,name:DESTINATIONS[id].name,region:DESTINATIONS[id].region,kind:DESTINATIONS[id].kind,m:parts[i]}))\n    .sort((a,b)=>b.m.total-a.m.total);\n  const maxD=destRows[0].m.total;\n  const dl=h('<div class=\"loclist\"><\/div>');\n  destRows.forEach(d=>{\n    const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${d.name}<\/div><div class=\"lr-sub\">${d.kind} \u00b7 ${d.m.kiosks} kiosks \u00b7 ${d.region}<\/div><\/div><div class=\"lr-val\">${money(d.m.total)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(d.m.tax)} tax<\/div><\/div><span class=\"chev\">\u203a<\/span><div class=\"lr-bar\"><i style=\"width:${(d.m.total\/maxD*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    row.addEventListener('click',()=>setDest(d.id));dl.appendChild(row);\n  });\n  const realTot=parts.reduce((a,b)=>a+b.total,0), othersTot=net.total-realTot, othersTax=net.tax-parts.reduce((a,b)=>a+b.tax,0), othersK=net.kiosks-parts.reduce((a,b)=>a+b.kiosks,0);\n  dl.appendChild(h(`<div class=\"locrow\" style=\"opacity:.85;cursor:default\"><div><div class=\"lr-name\">Other ${net.destinations-3} destinations<\/div><div class=\"lr-sub\">North America + Japan \u00b7 ~${num(othersK)} kiosks \u00b7 modeled long-tail<\/div><\/div><div class=\"lr-val\">${money(othersTot)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(othersTax)} tax<\/div><\/div><span class=\"chev\" style=\"opacity:0\">\u203a<\/span><div class=\"lr-bar\"><i style=\"width:${(othersTot\/maxD*100).toFixed(0)}%;background:linear-gradient(90deg,#8b96a5,#5B6675)\"><\/i><\/div><\/div>`));\n\n  const r1=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  r1.appendChild(card('Destinations ranked','click a live destination to drill into its kiosks',[dl,h('<div class=\"note\">California, Chicago and Florida are live TrueImpact tenants shown from their own kiosk data. The long-tail bucket models the remaining network to the published footprint.<\/div>')]));\n  \/\/ network partner mix\n  const parts2=Object.entries(net.partners).map(([id,v])=>({v,name:SEG[id].name,color:SEG[id].color}));\n  const pc=card('Network partner impact','hotels \u00b7 dining \u00b7 attractions \u00b7 events','',' ');\n  const pin=h('<div style=\"display:flex;gap:20px;align-items:center;flex-wrap:wrap\"><\/div>');\n  pin.appendChild(donut(parts2));\n  const plist=h('<div style=\"flex:1;min-width:190px\"><\/div>');\n  parts2.sort((a,b)=>b.v-a.v).forEach(p=>plist.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\" style=\"display:flex;align-items:center;gap:7px\"><span class=\"sw\" style=\"width:11px;height:11px;border-radius:3px;background:${p.color}\"><\/span>${p.name}<\/div><div class=\"lr-val\">${money(p.v)}<\/div><\/div>`)));\n  pin.appendChild(plist);pc.appendChild(pin);\n  r1.appendChild(pc);\n  wrap.appendChild(r1);\n\n  \/\/ engagement entry (network scope)\n  wrap.appendChild(engMiniCard(k));\n  wrap.lastChild.style.marginBottom='16px';\n\n  \/\/ network trend\n  wrap.appendChild(card('Network economic impact trend','monthly \u00b7 all destinations',[areaChart(networkSeries(k),MONTHS,{h:220}),h('<div class=\"note\">The category\\'s first aggregate impact curve built from first-party behavioral data across the network. Each destination re-circulates public tax dollars into its own community \u2014 an estimated '+money(net.recirc)+' network-wide (soft benefit).<\/div>')]));\n  return wrap;\n}\n\n\/* ================= ENGAGEMENT DRILL-THROUGH ================= *\/\nfunction engScope(k){\n  if(activeDest==='network'){const m=networkAgg(k).net;return {m,name:'the TrueOmni Network',seg:segSharesOf(m),locs:[]};}\n  if(engLoc!=='all'){const L=findLoc(engLoc);if(L){const m=deriveMetrics(L,k);return {m,name:L.name,seg:segSharesOf(m),locs:LOCS()};}}\n  const m=rollup(k);return {m,name:ACT().region,seg:segSharesOf(m),locs:LOCS()};\n}\nfunction engMetrics(mode){\n  const base={\n    interactions:{label:'Attention',   get:r=>r.interactions, fmt:num,   tfmt:v=>num(v)+' sessions'},\n    highIntent:  {label:'High-intent',  get:r=>r.highIntent,   fmt:num,   tfmt:v=>num(v)+' actions'},\n    conversions: {label:'Conversions',  get:r=>r.conversions,  fmt:num,   tfmt:v=>num(v)+' conv.'},\n    impact:      {label:'Attributed $', get:r=>r.impact,       fmt:money, tfmt:money2},\n  };\n  if(mode==='modules') base.time={label:'Time spent',get:r=>r.timeHrs,fmt:v=>num(v)+' h',tfmt:v=>num(v)+' hours'};\n  else base.roas={label:'ROI (ROAS)',get:r=>r.roas,fmt:v=>v.toFixed(1)+'\u00d7',tfmt:v=>v.toFixed(2)+'\u00d7 return'};\n  return base;\n}\nfunction setEng(key,val){ if(key==='mode'){engMode=val;engMetric='interactions';} else if(key==='loc'){engLoc=val;} else {engMetric=val;}\n  location.hash = '#\/'+(engMode==='modules'?'modules':'campaigns'); render(); }\nfunction pageEngagement(){\n  const k=K(), sc=engScope(k);\n  const items = engMode==='modules'?MODULES:CAMPAIGNS;\n  const rows = splitFunnel(sc.m, items, sc.seg);\n  const M = engMetrics(engMode);\n  if(!M[engMetric]) engMetric='interactions';\n  const met=M[engMetric];\n  rows.sort((a,b)=>(met.get(b)||0)-(met.get(a)||0));\n  const colorOf=r=> engMode==='modules'?SEG[r.tag].color:CAMP_TYPE[r.type];\n\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${activeDest==='network'?'Network':ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><a onclick=\"go('#\/overview')\">${activeDest==='network'?'TrueOmni Network':ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>Engagement drill-through<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">Marketing \u00b7 Engagement drill-through<\/div><h1>What draws attention \u2014 and what to invest in<\/h1><\/div><div class=\"meta\">${periodLabel()}<br><b style=\"color:var(--text)\">${sc.name}<\/b><\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Investment read',aiEngagement(rows,engMode,sc.name))));\n\n  \/\/ filters\n  const f=h('<div class=\"filters\"><\/div>');\n  f.appendChild(h(`<div class=\"fgroup\"><span class=\"flab\">Dimension<\/span><div class=\"fseg\">\n    <button class=\"${engMode==='modules'?'on':''}\" onclick=\"setEng('mode','modules')\">Modules<\/button>\n    <button class=\"${engMode==='campaigns'?'on':''}\" onclick=\"setEng('mode','campaigns')\">Campaigns<\/button><\/div><\/div>`));\n  if(sc.locs.length){\n    const opts=['<option value=\"all\"'+(engLoc==='all'?' selected':'')+'>All markets<\/option>']\n      .concat(sc.locs.map(L=>`<option value=\"${L.id}\"${engLoc===L.id?' selected':''}>${L.name}<\/option>`)).join('');\n    f.appendChild(h(`<div class=\"fgroup\"><span class=\"flab\">Location<\/span><select class=\"fsel\" onchange=\"setEng('loc',this.value)\">${opts}<\/select><\/div>`));\n  }\n  const mkeys=Object.keys(M);\n  f.appendChild(h(`<div class=\"fgroup\"><span class=\"flab\">Rank by<\/span><div class=\"fseg\">${mkeys.map(mk=>`<button class=\"${mk===engMetric?'on':''}\" onclick=\"setEng('metric','${mk}')\">${M[mk].label}<\/button>`).join('')}<\/div><\/div>`));\n  wrap.appendChild(f);\n\n  \/\/ ranked bar + top funnel\n  const r1=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  const barRows=rows.map(r=>({label:engShort(r.name),parts:[{v:met.get(r)||0,color:colorOf(r),name:met.label}],total:met.get(r)||0}));\n  const rk=card(`${engMode==='modules'?'Modules':'Campaigns'} ranked by ${met.label.toLowerCase()}`,'stack ranking','',' ');\n  rk.appendChild(rankedStacked(barRows,{pl:150,w:640,pr:74,fmt:met.fmt,tfmt:met.tfmt}));\n  r1.appendChild(rk);\n  \/\/ top item funnel drill-through\n  const top=rows[0];\n  const fc=card('Funnel drill-through','top '+(engMode==='modules'?'module':'campaign')+': '+top.name,[buildEngFunnel(top),h(`<div class=\"note\">Exposures \u2192 interactions \u2192 high-intent \u2192 attributed conversions for <b>${top.name}<\/b>. Conversion rate <b>${pct(top.convRate,1)}<\/b>${top.roas?` \u00b7 ROAS <b>${top.roas.toFixed(1)}\u00d7<\/b>`:''}${top.timeHrs?` \u00b7 <b>${num(top.timeHrs)}<\/b> engaged hours`:''}.<\/div>`)]);\n  r1.appendChild(fc);\n  wrap.appendChild(r1);\n\n  \/\/ full table\n  const tc=card(`${engMode==='modules'?'Module':'Campaign'} funnel \u2014 full breakdown`,'every stage \u00b7 '+periodLabel(),'',' ');\n  tc.appendChild(buildEngTable(rows,engMode,colorOf));\n  wrap.appendChild(tc);\n\n  \/\/ A\/B tests (campaigns only)\n  if(engMode==='campaigns'){\n    const tests=AB_TESTS.map(t=>({t,row:rows.find(r=>r.id===t.camp)})).filter(x=>x.row);\n    const abWrap=card('A\/B campaign tests','conversion rate by variant \u2014 compare & contrast',[h('<div class=\"grid row-2\" id=\"abgrid\" style=\"margin-top:2px\"><\/div>')]);\n    const grid=abWrap.querySelector('#abgrid');\n    tests.forEach(({t,row})=>grid.appendChild(buildABcard(t,row)));\n    wrap.appendChild(abWrap);\n  }\n  return wrap;\n}\nfunction buildEngFunnel(r){\n  const stages=[['Exposures',r.exposures,''],['Interactions',r.interactions,pct(r.interactions\/r.exposures,0)+' of exposed'],\n    ['High-intent',r.highIntent,pct(r.highIntent\/r.interactions,0)+' of interactions'],['Conversions',r.conversions,pct(r.conversions\/r.highIntent,0)+' of high-intent']];\n  const max=stages[0][1],cols=['#0E2240','#14508C','#1C9CD8','#33abe0'];\n  const box=h('<div class=\"funnel\"><\/div>');\n  stages.forEach(([n,v,rate],i)=>box.appendChild(h(`<div class=\"fstage\"><div class=\"fbar\" style=\"width:${26+40*(v\/max)}%;background:${cols[i]}\">${n}<\/div><div class=\"fmeta\"><b>${num(v)}<\/b> <span class=\"frate\">${rate}<\/span><\/div><\/div>`)));\n  return box;\n}\nfunction buildEngTable(rows,mode,colorOf){\n  const head = mode==='modules'\n    ? '<tr><th>Module<\/th><th>Exposures<\/th><th>Interactions<\/th><th>High-intent<\/th><th>Conversions<\/th><th>Conv %<\/th><th>Time<\/th><th>Attributed $<\/th><th>$\/1K int<\/th><\/tr>'\n    : '<tr><th>Campaign<\/th><th>Interactions<\/th><th>High-intent<\/th><th>Conversions<\/th><th>Conv %<\/th><th>Spend<\/th><th>Attributed $<\/th><th>ROAS<\/th><\/tr>';\n  const tbl=h(`<table><thead>${head}<\/thead><tbody><\/tbody><\/table>`);\n  const tb=tbl.querySelector('tbody');\n  rows.forEach(r=>{\n    const dot=`<span class=\"sw\" style=\"display:inline-block;width:10px;height:10px;border-radius:3px;background:${colorOf(r)};margin-right:7px;vertical-align:-1px\"><\/span>`;\n    const conv=`<span class=\"conv-pill\">${pct(r.convRate,1)}<\/span>`;\n    if(mode==='modules'){\n      tb.appendChild(h(`<tr><td>${dot}<b>${r.name}<\/b><\/td><td class=\"tnum\">${num(r.exposures)}<\/td><td class=\"tnum\">${num(r.interactions)}<\/td><td class=\"tnum\">${num(r.highIntent)}<\/td><td class=\"tnum\">${num(r.conversions)}<\/td><td>${conv}<\/td><td class=\"tnum\">${num(r.timeHrs)} h<\/td><td class=\"tnum\"><b>${money(r.impact)}<\/b><\/td><td class=\"tnum\">${money(r.valPerK)}<\/td><\/tr>`));\n    } else {\n      tb.appendChild(h(`<tr><td>${dot}<b>${r.name}<\/b><div class=\"ab-sub\" style=\"margin-left:17px\">${r.type}<\/div><\/td><td class=\"tnum\">${num(r.interactions)}<\/td><td class=\"tnum\">${num(r.highIntent)}<\/td><td class=\"tnum\">${num(r.conversions)}<\/td><td>${conv}<\/td><td class=\"tnum\">${money(r.spend)}<\/td><td class=\"tnum\"><b>${money(r.impact)}<\/b><\/td><td class=\"tnum\"><span class=\"pill ${r.roas>=4?'tag-hi':'tag-mid'}\">${r.roas.toFixed(1)}\u00d7<\/span><\/td><\/tr>`));\n    }\n  });\n  return tbl;\n}\nfunction buildABcard(t,row){\n  \/\/ split the campaign row's traffic; blended conv rate must equal row.convRate\n  const base=row.convRate, rA=base\/(t.split + (1-t.split)*t.bLift), rB=rA*t.bLift;\n  const intA=row.interactions*t.split, intB=row.interactions*(1-t.split);\n  const cvA=intA*rA, cvB=intB*rB;\n  const impA=row.impact*(cvA\/(cvA+cvB)), impB=row.impact*(cvB\/(cvA+cvB));\n  const winnerB=t.bLift>1, decisive=t.conf>=0.9;\n  const maxR=Math.max(rA,rB);\n  const badge = t.status==='No winner yet' ? `<span class=\"ab-badge ab-none\">Running \u00b7 ${pct(t.conf,0)} conf.<\/span>`\n    : `<span class=\"ab-badge ab-win\">${t.status} \u00b7 ${pct(t.conf,0)} conf.<\/span>`;\n  const lift=(rB\/rA-1);\n  const c=h(`<div class=\"ab-card\"><\/div>`);\n  c.appendChild(h(`<div class=\"ab-head\"><div><div class=\"abt\">${t.name}<\/div><div class=\"abs\">${row.name} \u00b7 ${num(row.interactions)} sessions split ${Math.round(t.split*100)}\/${Math.round((1-t.split)*100)}<\/div><\/div>${badge}<\/div>`));\n  const variant=(tag,label,r,cv,imp,isWin)=>`\n    <div class=\"ab-var\">\n      <div class=\"ab-tag\" style=\"background:${isWin?'var(--good)':'var(--slate)'}\">${tag}<\/div>\n      <div><div class=\"ab-nm\">${label} ${isWin?'<span class=\"conv-pill\" style=\"background:rgba(12,163,12,.14);color:var(--good)\">winner<\/span>':''}<\/div><div class=\"ab-sub\">${num(cv)} conversions \u00b7 ${money(imp)} attributed<\/div><\/div>\n      <div class=\"ab-conv\"><b>${pct(r,2)}<\/b><span>conv. rate<\/span><\/div>\n      <div class=\"ab-bar\"><i style=\"width:${(r\/maxR*100).toFixed(0)}%;background:${isWin?'var(--good)':'var(--slate)'}\"><\/i><\/div>\n    <\/div>`;\n  c.appendChild(h(variant('A',t.a,rA,cvA,impA,!winnerB)));\n  c.appendChild(h(variant('B',t.b,rB,cvB,impB,winnerB)));\n  c.appendChild(h(`<div class=\"ab-foot\"><span>Variant B vs A<\/span><span><b style=\"color:${lift>=0?'var(--good)':'var(--warn)'}\">${lift>=0?'+':''}${pct(lift,1)}<\/b> conversion-rate lift<\/span><\/div>`));\n  return c;\n}\n\n\/* ================= MEDIA NETWORK ================= *\/\nfunction aiMedia(k){\n  const ad=adTotals(k), m=ad.md, sg=ad.sg;\n  return `Across both surfaces \u2014 <b>${m.kiosks} interactive kiosks<\/b> and <b>${sg?sg.screens:0} digital-signage screens<\/b> \u2014 the network is modeled to carry <span class=\"em\">${num(ad.impressions)}<\/span> advertising impressions a year and generate <span class=\"em\">${money(ad.rev)}<\/span> in <b>Total Advertising Revenue<\/b> at a blended <b>${money(ad.cpm)}<\/b> CPM. Kiosks supply ${pct(ad.kioskShare)} of that at a premium because they are interactive and attributable; the ${sg?sg.screens:0} large-format screens are an accent layer at ${pct(ad.signageShare)}. Advertising covers the ${money(ad.softwareYr)} annual platform fee <b>${ad.softwareCover.toFixed(1)}\u00d7<\/b> over and repays the ${money(ad.capital)} of hardware in roughly <b>${Math.round(ad.netPaybackYr*12)} months<\/b> \u2014 which means the economic-impact case does not have to carry the budget case. Screen impressions are <b>reach<\/b>, not interactions, and are excluded from the impact and tax pages by design.`;\n}\nfunction mediaMiniCard(k){const ad=adTotals(k); const m=ad.md; const sg=ad.sg;\n  const c=card('Media Network &middot; potential advertising revenue','the self-funding layer &mdash; kiosks + digital signage','',' ');\n  c.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:2px\"><div class=\"si\"><b>${money(ad.rev)}<\/b><span>Total Advertising Revenue \/ yr (mid)<\/span><\/div><div class=\"si\"><b>${num(ad.impressions)}<\/b><span>impressions \/ yr (${num(sg?sg.impressions:0)} screen)<\/span><\/div><div class=\"si\"><b>${money(ad.cpm)}<\/b><span>blended CPM<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${ad.netPaybackYr.toFixed(1)} yr<\/b><span>capital payback<\/span><\/div><\/div>`));\n  c.appendChild(h(`<div style=\"display:flex;gap:8px;margin-top:12px\"><button class=\"chip\" style=\"cursor:pointer\" onclick=\"go('#\/media')\">Open Media Network \u203a<\/button><\/div>`));\n  return c;\n}\n\/* ================= DESTINATION CONTEXT STRIP ================= *\/\nfunction destContextStrip(){\n  const m=rollup(K());\n  const c=card('Visitor economy &middot; destination context','the whole economy first &mdash; then the slice this network measurably influences','',' ');\n  const si=h('<div class=\"stat-inline\" style=\"margin-top:2px\"><\/div>');\n  DEST_META.context.forEach(x=>si.appendChild(h(`<div class=\"si\"><b>${x.v}<\/b><span>${x.l}<\/span><\/div>`)));\n  c.appendChild(si);\n  c.appendChild(h(`<div class=\"note\">${DEST_META.contextNote} The <b>${money(m.total)}<\/b> of attributed economic impact on this dashboard is <b>${pct(m.total\/DEST_META.spendBase,2)}<\/b> of that direct visitor-spending base &mdash; a thin, provable slice, never a claim on the whole.<\/div>`));\n  return c;\n}\n\n\/* ================= INFLUENCE SCENARIOS (Doug's ladder) ================= *\/\nfunction scenarioRows(k=1){\n  const m=rollup(k);\n  const base=DEST_META.spendBase;\n  const rows=SCENARIOS.map(p=>({p, v:base*p, isModel:false}));\n  const modelPct=m.total\/base;\n  return {rows, modelPct, modelValue:m.total, base,\n    \/\/ which rungs the bottom-up model sits between\n    below:[...rows].reverse().find(r=>r.p<=modelPct)||null,\n    above:rows.find(r=>r.p>=modelPct)||null};\n}\n\nfunction scenariosMiniCard(k){\n  const s=scenarioRows(k);\n  const c=card('Modeled influence opportunity','what a small shift in visitor engagement is worth','',' ');\n  c.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:2px\"><div class=\"si\"><b>${money(s.base)}<\/b><span>direct visitor spending (actual)<\/span><\/div><div class=\"si\"><b>${money(s.base*0.0025)}<\/b><span>influencing just 0.25% of it<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${pct(s.modelPct,2)}<\/b><span>where this network's bottom-up model lands<\/span><\/div><\/div>`));\n  const list=h('<div class=\"loclist\" style=\"margin-top:12px\"><\/div>');\n  const max=s.rows[s.rows.length-1].v;\n  s.rows.forEach(r=>{\n    const hit = (s.below && r.p===s.below.p);\n    const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${pct(r.p,2)} of direct visitor spending<\/div><div class=\"lr-sub\">${hit?'&#9656; the bottom-up model clears this rung':'&nbsp;'}<\/div><\/div><div class=\"lr-val\">${money(r.v)}<div class=\"lr-sub\" style=\"font-weight:600\">influenced spending<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(r.v\/max*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    if(hit) row.style.background='var(--tint)';\n    row.addEventListener('click',()=>go('#\/scenarios')); row.style.cursor='pointer';\n    list.appendChild(row);\n  });\n  c.appendChild(list);\n  c.appendChild(h(`<div style=\"display:flex;gap:8px;margin-top:12px\"><button class=\"chip\" style=\"cursor:pointer\" onclick=\"go('#\/scenarios')\">Open influence scenarios &rsaquo;<\/button><\/div>`));\n  return c;\n}\n\nfunction pageScenarios(){\n  const k=K(); const s=scenarioRows(k); const m=rollup(k); const ad=adTotals(k);\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>${ACT().name}<\/span><span class=\"sep\">&rsaquo;<\/span><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">&rsaquo;<\/span><span>Influence Scenarios<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">Modeled Opportunity &mdash; not projected results<\/div><h1>What a small shift in visitor engagement is worth<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">Against ${money(s.base)} of direct visitor spending &middot; ${periodLabel()}<\/div><\/div><div class=\"meta\">Bottom-up model lands at<br><b style=\"color:var(--text);font-size:14px\">${pct(s.modelPct,2)} &middot; ${money(s.modelValue)}<\/b><\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI &middot; Scenario read', aiScenarios(k))));\n\n  wrap.appendChild(h(`<div class=\"note\" style=\"margin:-4px 0 16px\"><b>Read this page as a model of scale, not a promise of return.<\/b> The network does not have to create ${money(s.base*0.0025)} of new spending. Its job is to influence decisions visitors are <i>already making<\/i> &mdash; where they eat, what they visit, how long they stay, which events they attend. The ladder below shows what each fraction of a percent of the existing visitor economy is worth. Nothing here is a projected result, and nothing here should be presented as one.<\/div>`));\n\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">${DEST_META.spendBaseLabel}<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(s.base).replace(curSym(),'')}<\/div><div class=\"k-delta up\">&#9650; ACTUAL &mdash; ${DEST_META.spendBaseSource}<\/div><div class=\"k-foot\">Every figure on this page is a fraction of this published base. The base itself is not modeled.<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Influencing 0.10%',money(s.base*0.001),'','the most conservative rung on the ladder'));\n  kpis.appendChild(kpiTile('Influencing 0.25%',money(s.base*0.0025),'','a quarter of one percent of existing spend'));\n  kpis.appendChild(kpiTile('This network&rsquo;s bottom-up model',money(s.modelValue),'',`= ${pct(s.modelPct,2)} &mdash; derived from encounters, not chosen`));\n  wrap.appendChild(kpis);\n\n  \/* --- the ladder --- *\/\n  const lc=card('The influence ladder','modeled opportunity at each fraction of a percent','',' ');\n  const tbl=h('<table><thead><tr><th>Scenario<\/th><th>Share of direct visitor spending<\/th><th>Influenced spending<\/th><th>Per kiosk \/ yr<\/th><th>Status<\/th><\/tr><\/thead><tbody><\/tbody><\/table>');\n  const tb=tbl.querySelector('tbody');\n  s.rows.forEach(r=>{\n    const hit = (s.below && r.p===s.below.p);\n    const tr=h(`<tr${hit?' style=\"background:var(--tint);font-weight:700\"':''}><td>${pct(r.p,2)} scenario<\/td><td>${pct(r.p,2)}<\/td><td>${money2(r.v)}<\/td><td>${money2(r.v\/m.kiosks)}<\/td><td>${hit?'<b style=\"color:var(--good)\">cleared by the bottom-up model<\/b>':'<span style=\"opacity:.6\">upside case<\/span>'}<\/td><\/tr>`);\n    tb.appendChild(tr);\n  });\n  tb.appendChild(h(`<tr style=\"border-top:2px solid var(--line);font-weight:800\"><td>Bottom-up model<\/td><td>${pct(s.modelPct,2)}<\/td><td>${money2(s.modelValue)}<\/td><td>${money2(s.modelValue\/m.kiosks)}<\/td><td><b style=\"color:var(--good)\">derived from ${num2(m.exposures)} encounters<\/b><\/td><\/tr>`));\n  lc.appendChild(tbl);\n  lc.appendChild(h(`<div class=\"note\">The last row is the important one. It is <b>not<\/b> a scenario we picked &mdash; it is what falls out of the funnel when you multiply ${num2(m.exposures)} modeled visitor encounters by real interaction and conversion rates. It lands at <b>${pct(s.modelPct,2)}<\/b>${s.below?`, above the ${pct(s.below.p,2)} rung`:''}${s.above&&s.above.p!==s.modelPct?` and below the ${pct(s.above.p,2)} rung`:''}. In other words: the conservative end of a ladder the destination can already read. That is the correct place for a pre-deployment number to sit.<\/div>`));\n  wrap.appendChild(lc); wrap.lastChild.style.marginBottom='16px';\n\n  \/* --- the visitor journey --- *\/\n  const jc=card('The visitor journey','every step is a measurable, first-party event','',' ');\n  const jr=h('<div class=\"loclist\"><\/div>');\n  DEST_META.journey.forEach((j,i)=>{\n    jr.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:44px 1fr auto\"><div class=\"lr-name\" style=\"color:var(--azure);font-weight:800\">${String(i+1).padStart(2,'0')}<\/div><div><div class=\"lr-name\">${j.step}<\/div><div class=\"lr-sub\">${j.what}<\/div><\/div><div class=\"lr-val\" style=\"font-size:12px;font-weight:700\">${j.metric}<\/div><\/div>`));\n  });\n  jc.appendChild(jr);\n  jc.appendChild(h(`<div class=\"note\">This is the point that outlasts any single number on this dashboard. Today the destination knows what visitors <i>did<\/i> &mdash; occupancy, spend, tax receipts &mdash; after the fact and in aggregate. A kiosk network generates <b>first-party engagement data<\/b> at every step: what visitors searched for, what they ignored, which neighbourhoods they asked about, which languages they used, what they sent to their phones. That is an asset the destination owns, and it compounds.<\/div>`));\n  wrap.appendChild(jc); wrap.lastChild.style.marginBottom='16px';\n\n  \/* --- spend mix, where influence lands --- *\/\n  if(DEST_META.spendMix && DEST_META.spendMix.length){\n    const mc=card('Where the visitor dollar goes today','published spending mix &mdash; and where a kiosk can move it','',' ');\n    const rows=DEST_META.spendMix.slice().sort((a,b)=>b.v-a.v).map(x=>({\n      label:x.name, parts:[{v:x.v,color:x.color||'var(--s-hotels)',name:x.name}], total:x.v, sub:x.lever}));\n    mc.appendChild(rankedStacked(rows,{pl:140}));\n    mc.appendChild(h(`<div class=\"note\">${DEST_META.spendMixNote}<\/div>`));\n    wrap.appendChild(mc); wrap.lastChild.style.marginBottom='16px';\n  }\n\n  \/* --- what has to be true --- *\/\n  const wc=card('What has to be true','the honest conditions on the model','',' ');\n  const wl=h('<div class=\"loclist\"><\/div>');\n  [['The network is placed where visitors actually pause',`${m.kiosks} kiosks across ${LOCS().length} zones. Lobby and terminal placements carry the model; a kiosk in a low-dwell corridor does not.`],\n   ['Content is current',`Stale event listings and closed restaurants kill interaction rates faster than anything else. This assumes the ${MODULES.length} modules are fed by live destination data.`],\n   ['Partners are in the system','Attributed spend requires hotels, restaurants and attractions to be listed, priced and linked. The partner page is the dependency.'],\n   ['Attribution is switched on at go-live','Every dollar here is modeled today. At go-live the same structure is fed by ledgered kiosk events, and this page should be replaced with measured results.'],\n   ['Advertising is sold',`${money(ad.rev)}\/yr of Total Advertising Revenue assumes someone sells it. Unsold inventory is the single largest downside risk in the economics.`]\n  ].forEach(([a,b])=>wl.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr\"><div><div class=\"lr-name\">${a}<\/div><div class=\"lr-sub\">${b}<\/div><\/div><\/div>`)));\n  wc.appendChild(wl);\n  wrap.appendChild(wc);\n  return wrap;\n}\n\n\/* ================= MEDIA NETWORK + DIGITAL SIGNAGE ================= *\/\nfunction pageMedia(){\n  const k=K(); const ad=adTotals(k); const m=ad.md; const sg=ad.sg;\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>${ACT().name}<\/span><span class=\"sep\">&rsaquo;<\/span><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">&rsaquo;<\/span><span>Media Network<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">Advertising &middot; Omni Media Network<\/div><h1>Media Network &mdash; Total Advertising Revenue<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">Interactive kiosks + digital signage &middot; native direct-sold and Google Ad Manager programmatic &middot; ${periodLabel()}<\/div><\/div><div class=\"meta\">Network<br><b style=\"color:var(--text);font-size:14px\">${m.kiosks} kiosks &middot; ${sg?sg.screens:0} screens<\/b><br>${pct(MEDIA.fillMature)} kiosk fill &middot; ${sg?pct(SIGNAGE.fillMature):'&mdash;'} signage fill<\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI &middot; Media read', aiMedia(k))));\n\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Total Advertising Revenue<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(ad.rev).replace(curSym(),'')}<\/div><div class=\"k-delta up\">&#9650; kiosks + signage combined<\/div><div class=\"k-foot\">Kiosks ${money(m.rev)} (${pct(ad.kioskShare)}) + Signage ${money(sg?sg.rev:0)} (${pct(ad.signageShare)}) &middot; per year at mature fill<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Total impressions \/ yr',num(ad.impressions),'',`Kiosk ${num(m.impressions)} &middot; Screen ${num(sg?sg.impressions:0)}`));\n  kpis.appendChild(kpiTile('Blended CPM',money(ad.cpm),'','in line with DOOH benchmarks ($8&ndash;35)'));\n  kpis.appendChild(kpiTile('Covers annual software',ad.softwareCover.toFixed(1)+'&times;','',`vs ${money(ad.softwareYr)}\/yr platform fee (both surfaces)`));\n  wrap.appendChild(kpis);\n\n  wrap.appendChild(h('<div class=\"note\" style=\"margin:-4px 0 16px\">Two surfaces, two jobs. <b>Kiosks transact<\/b> &mdash; a visitor touches one, and the resulting spend is what drives the economic-impact and tax pages. <b>Signage broadcasts<\/b> &mdash; it delivers reach and ad inventory to everyone who walks past, residents included, and is deliberately kept <i>out<\/i> of the attributed-impact math. The two numbers are never added together.<\/div>'));\n\n  \/* ---------- KIOSK MEDIA ---------- *\/\n  wrap.appendChild(h('<div class=\"pagehead\" style=\"margin-bottom:12px\"><div><div class=\"eyebrow\">Surface 1 &middot; Interactive<\/div><h1 style=\"font-size:22px\">Kiosk advertising<\/h1><\/div><div class=\"meta\">'+m.kiosks+' kiosks<br><b style=\"color:var(--text);font-size:14px\">'+money(m.rev)+' \/ yr<\/b><\/div><\/div>'));\n  const r2=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  const invCard=card('Revenue by inventory type','container-level potential (Omni Media Network)','',' ');\n  const list=h('<div class=\"loclist\"><\/div>'); const maxv=Math.max(...m.byInv.map(x=>x.v));\n  m.byInv.slice().sort((a,b)=>b.v-a.v).forEach(x=>{list.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\">${x.name}<\/div><div class=\"lr-val\">${money(x.v)}<div class=\"lr-sub\" style=\"font-weight:600\">${pct(x.share)}<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(x.v\/maxv*100).toFixed(0)}%\"><\/i><\/div><\/div>`));});\n  invCard.appendChild(list);\n  invCard.appendChild(h(`<div class=\"note\">Kiosk revenue modeled at ${curSym()}${MEDIA.perKioskMo.floor}&ndash;${MEDIA.perKioskMo.strong}\/kiosk\/month (platform benchmark), mid case ${curSym()}${MEDIA.perKioskMo.mid}. Range ${money(m.band.floor)}&ndash;${money(m.band.strong)} per year.<\/div>`));\n  r2.appendChild(invCard);\n  const streamCard=card('Direct vs programmatic','native sales + Google Ad Manager','',' ');\n  const parts=[{v:m.byStream.direct,name:'Direct \/ native sold (local partners)',color:'var(--s-hotels)'},{v:m.byStream.programmatic,name:'Programmatic (GAM \/ DTN)',color:'var(--s-events)'}];\n  const pin=h('<div style=\"display:flex;gap:20px;align-items:center;flex-wrap:wrap\"><\/div>'); pin.appendChild(donut(parts));\n  const pl=h('<div style=\"flex:1;min-width:180px\"><\/div>'); parts.forEach(p=>pl.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\" style=\"display:flex;align-items:center;gap:7px\"><span class=\"sw\" style=\"width:11px;height:11px;border-radius:3px;background:${p.color}\"><\/span>${p.name}<\/div><div class=\"lr-val\">${money(p.v)}<\/div><\/div>`))); pin.appendChild(pl);\n  streamCard.appendChild(pin);\n  streamCard.appendChild(h(`<div class=\"note\">${DEST_META.adNote}<\/div>`));\n  r2.appendChild(streamCard);\n  wrap.appendChild(r2);\n\n  \/* ---------- DIGITAL SIGNAGE ---------- *\/\n  if(sg){\n    wrap.appendChild(h('<div class=\"pagehead\" style=\"margin-bottom:12px\"><div><div class=\"eyebrow\">Surface 2 &middot; Broadcast<\/div><h1 style=\"font-size:22px\">Digital signage<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">Five large-format screens at anchor locations &mdash; reach without an interaction<\/div><\/div><div class=\"meta\">'+sg.screens+' screens<br><b style=\"color:var(--text);font-size:14px\">'+money(sg.rev)+' \/ yr<\/b><\/div><\/div>'));\n    const sk=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n    sk.appendChild(kpiTile('Screens deployed',num2(sg.screens),'','large-format, at the highest-traffic anchors only'));\n    sk.appendChild(kpiTile('Screen impressions \/ yr',num(sg.impressions),'',`${num(sg.reach)} people reached &middot; ${SIGNAGE.imprPerPasserby}&times; per visit`));\n    sk.appendChild(kpiTile('Share of voice',pct(sg.shareOfVoice),'',`${sg.spotsPerLoop} spots per ${SIGNAGE.loopSec}s loop &middot; ${SIGNAGE.spotSec}s creative`));\n    sk.appendChild(kpiTile('Avg dwell \/ attention',SIGNAGE.dwellSec+'s','',`${num(sg.attentionHrs)} attention-hours per year`));\n    wrap.appendChild(sk);\n\n    const s2=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n    const fmtCard=card('Revenue by screen format','where signage inventory earns','',' ');\n    const fl=h('<div class=\"loclist\"><\/div>'); const fmax=Math.max(...sg.byFormat.map(x=>x.v));\n    sg.byFormat.slice().sort((a,b)=>b.v-a.v).forEach(x=>{fl.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\">${x.name}<\/div><div class=\"lr-val\">${money(x.v)}<div class=\"lr-sub\" style=\"font-weight:600\">${pct(x.share)}<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(x.v\/fmax*100).toFixed(0)}%\"><\/i><\/div><\/div>`));});\n    fmtCard.appendChild(fl);\n    fmtCard.appendChild(h(`<div class=\"note\">Modeled at ${curSym()}${SIGNAGE.perScreenMo.floor}&ndash;${SIGNAGE.perScreenMo.strong}\/screen\/month, mid case ${curSym()}${SIGNAGE.perScreenMo.mid}. With only ${sg.screens} screens against ${m.kiosks} kiosks, signage is an <b>accent layer<\/b> here &mdash; ${pct(ad.signageShare)} of advertising revenue, not the engine. Range ${money(sg.band.floor)}&ndash;${money(sg.band.strong)} per year.<\/div>`));\n    s2.appendChild(fmtCard);\n\n    const zCard=card('Screens by zone','and revenue per screen','',' ');\n    const zl=h('<div class=\"loclist\"><\/div>');\n    const withScreens=LOCS().filter(L=>(L.screens||0)>0);\n    const zmax=Math.max(...withScreens.map(L=>L.screens));\n    const revPerScreen=sg.rev\/sg.screens;\n    withScreens.slice().sort((a,b)=>b.screens-a.screens).forEach(L=>{\n      const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${L.name}<\/div><div class=\"lr-sub\">${L.kiosks} kiosk${L.kiosks>1?'s':''} &middot; ${L.screens} screen${L.screens!==1?'s':''}<\/div><\/div><div class=\"lr-val\">${money(L.screens*revPerScreen)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(revPerScreen)} \/ screen<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(L.screens\/zmax*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n      row.addEventListener('click',()=>go('#\/location\/'+L.id)); row.style.cursor='pointer'; zl.appendChild(row);});\n    zCard.appendChild(zl);\n    zCard.appendChild(h(`<div class=\"note\">Screens go only where dwell and footfall justify a large-format unit. Signage reach uses a wider denominator than kiosk impact: total foot traffic is modeled at ${SIGNAGE.localMult}&times; visitor exposures because residents, staff and event spectators all pass a screen. None of them are counted as attributed visitor spend. ${LOCS().length-withScreens.length} zones carry kiosks only.<\/div>`));\n    s2.appendChild(zCard);\n    wrap.appendChild(s2);\n  }\n\n  \/* ---------- SELF-FUNDING ---------- *\/\n  const sf=card('The self-funding math','advertising vs. total program cost','',' ');\n  sf.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:4px\"><div class=\"si\"><b>${money(ad.rev)}<\/b><span>Total Advertising Revenue \/ yr (mid)<\/span><\/div><div class=\"si\"><b>${money(ad.softwareYr)}<\/b><span>annual software (both surfaces)<\/span><\/div><div class=\"si\"><b>${money(ad.capital)}<\/b><span>one-time capital<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${ad.netPaybackYr.toFixed(1)} yr<\/b><span>capital payback, net of software<\/span><\/div><\/div>`));\n  sf.appendChild(h(`<div class=\"note\">Capital splits ${money(MEDIA.capital)} for ${m.kiosks} kiosks and ${money(sg?sg.capital:0)} for ${sg?sg.screens:0} screens; software is ${money(MEDIA.softwareYr)} + ${money(sg?sg.softwareYr:0)} per year. At the mid scenario advertising covers the annual platform fee ${ad.softwareCover.toFixed(1)}&times; over and repays the hardware in roughly ${Math.round(ad.netPaybackYr*12)} months &mdash; before a single dollar of visitor economic impact is counted. Modeled from the platform benchmark; refine with a local rate card, a real fill curve and the revenue split.<\/div>`));\n  wrap.appendChild(sf);\n  return wrap;\n}\n\n\/* ================= ROUTER ================= *\/\nconst state={loc:null};\nfunction go(hash){location.hash=hash;}\nfunction render(){\n  buildDestSel();\n  const hash=location.hash||'#\/overview';\n  const parts=hash.replace('#\/','').split('\/');\n  if(activeDest==='network'){\n    app.innerHTML='';\n    if(parts[0]==='modules'){engMode='modules';app.appendChild(pageEngagement());}\n    else if(parts[0]==='campaigns'){engMode='campaigns';app.appendChild(pageEngagement());}\n    else app.appendChild(pageNetwork());\n    window.scrollTo({top:0,behavior:'instant'});return;\n  }\n  state.loc = parts[0]==='location'?parts[1]:null;\n  app.innerHTML='';\n  let node;\n  if(parts[0]==='location') node=pageLocation(parts[1]);\n  else if(parts[0]==='partners') node=pagePartners();\n  else if(parts[0]==='tax') node=pageTax();\n  else if(parts[0]==='modules'){engMode='modules';node=pageEngagement();}\n  else if(parts[0]==='campaigns'){engMode='campaigns';node=pageEngagement();}\n  else if(parts[0]==='media') node=pageMedia();\n  else if(parts[0]==='scenarios') node=pageScenarios();\n  else node=pageOverview();\n  app.appendChild(node);\n  window.scrollTo({top:0,behavior:'instant'});\n}\nwindow.addEventListener('hashchange',render);\n\n\/\/ destination selector (rebuilt each render to reflect active)\nfunction buildDestSel(){\n  const host=$('#destSel');if(!host)return;\n  const opts=[...Object.keys(DESTINATIONS).map(id=>[id,DESTINATIONS[id].name])];\n  host.innerHTML=opts.map(([id,label])=>`<option value=\"${id}\"${id===activeDest?' selected':''}>${label}<\/option>`).join('');\n}\n\n\/\/ period selector\n$('#periodSel').addEventListener('click',e=>{const b=e.target.closest('button');if(!b)return;\n  [...$('#periodSel').children].forEach(x=>x.classList.remove('on'));b.classList.add('on');PERIOD=b.dataset.p;render();});\n\/\/ currency: USD only \u2014 no toggle on a US destination.\n\/\/ destination selector\n$('#destSel').addEventListener('change',e=>setDest(e.target.value));\n\/\/ theme\n$('#themeBtn').addEventListener('click',()=>{const cur=document.documentElement.getAttribute('data-theme');document.documentElement.setAttribute('data-theme',cur==='dark'?'light':'dark');render();});\nwindow.addEventListener('mousemove',e=>{if(TT.style.opacity==1)moveTT(e);});\n\nrender();\n<\/script>\n<\/body>\n<\/html>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\n<\/div>\n\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>TrueImpact \u2014 Executive Dashboard \u00b7 Visit Jacksonville<\/p>","protected":false},"author":11,"featured_media":57099,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_canvas","meta":{"_acf_changed":false,"inline_featured_image":false,"postBodyCss":"","postBodyMargin":[],"postBodyPadding":[],"postBodyBackground":{"backgroundType":"classic","gradient":""},"_thinkrank_schema_form_data":"","_thinkrank_selected_schema_type":"","_thinkrank_additional_schemas":"","_thinkrank_canonical_url":"","_thinkrank_og_title":"","_thinkrank_og_description":"","_thinkrank_og_image":"","_thinkrank_twitter_title":"","_thinkrank_twitter_description":"","_thinkrank_twitter_image":"","_thinkrank_imported_from":"","_thinkrank_focus_keywords":[],"_thinkrank_focus_keyword":"","_thinkrank_robots_meta_enabled":0,"_thinkrank_robots_meta":"[]","_thinkrank_advanced_robots_meta":"[]","_thinkrank_exclude_from_search":0,"_thinkrank_exclude_from_archives":0,"_thinkrank_primary_category":0,"footnotes":""},"class_list":["post-58279","page","type-page","status-publish","has-post-thumbnail","hentry"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.9 (Yoast SEO v28.7) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>VisitJacksonville - TrueOmni<\/title>\n<meta name=\"description\" content=\"Visit Jacksonville\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/trueomni.com\/spanish\/visit-jacksonville\/\" \/>\n<meta property=\"og:locale\" content=\"es_MX\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"VisitJacksonville\" \/>\n<meta property=\"og:description\" content=\"Visit Jacksonville\" \/>\n<meta property=\"og:url\" content=\"https:\/\/trueomni.com\/spanish\/visit-jacksonville\/\" \/>\n<meta property=\"og:site_name\" content=\"TrueOmni\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/trueomni\/\" \/>\n<meta property=\"article:modified_time\" content=\"2026-08-19T18:11:45+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/trueomni.com\/wp-content\/uploads\/2026\/05\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-1024x753.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1024\" \/>\n\t<meta property=\"og:image:height\" content=\"753\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Tiempo de lectura\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minuto\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/trueomni.com\\\/visit-jacksonville\\\/\",\"url\":\"https:\\\/\\\/trueomni.com\\\/visit-jacksonville\\\/\",\"name\":\"VisitJacksonville - TrueOmni\",\"isPartOf\":{\"@id\":\"http:\\\/\\\/trueomni.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/visit-jacksonville\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/visit-jacksonville\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/trueomni.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png\",\"datePublished\":\"2026-08-19T18:11:00+00:00\",\"dateModified\":\"2026-08-19T18:11:45+00:00\",\"description\":\"Visit Jacksonville\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/visit-jacksonville\\\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/trueomni.com\\\/visit-jacksonville\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\\\/\\\/trueomni.com\\\/visit-jacksonville\\\/#primaryimage\",\"url\":\"https:\\\/\\\/trueomni.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png\",\"contentUrl\":\"https:\\\/\\\/trueomni.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png\",\"width\":2560,\"height\":1883,\"caption\":\"TrueOmni interactive kiosks in a hotel lobby\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\\\/\\\/trueomni.com\\\/visit-jacksonville\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"http:\\\/\\\/trueomni.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"VisitJacksonville\"}]},{\"@type\":\"WebSite\",\"@id\":\"http:\\\/\\\/trueomni.com\\\/#website\",\"url\":\"http:\\\/\\\/trueomni.com\\\/\",\"name\":\"TrueOmni\",\"description\":\"TrueOmni Self-Service Digital Customer Experience Company\",\"publisher\":{\"@id\":\"http:\\\/\\\/trueomni.com\\\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"http:\\\/\\\/trueomni.com\\\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"es\"},{\"@type\":\"Organization\",\"@id\":\"http:\\\/\\\/trueomni.com\\\/#organization\",\"name\":\"TRUE OMNI LLC\",\"alternateName\":\"TRUE OMNI LLC\",\"url\":\"http:\\\/\\\/trueomni.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"http:\\\/\\\/trueomni.com\\\/#\\\/schema\\\/logo\\\/image\\\/\",\"url\":\"http:\\\/\\\/trueomni.com\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/TrueOmni-Logo-2.png\",\"contentUrl\":\"http:\\\/\\\/trueomni.com\\\/wp-content\\\/uploads\\\/2024\\\/06\\\/TrueOmni-Logo-2.png\",\"width\":1000,\"height\":230,\"caption\":\"TRUE OMNI LLC\"},\"image\":{\"@id\":\"http:\\\/\\\/trueomni.com\\\/#\\\/schema\\\/logo\\\/image\\\/\"},\"sameAs\":[\"https:\\\/\\\/www.facebook.com\\\/trueomni\\\/\"],\"description\":\"TrueOmni is the leader in digital self-service solutions, offering innovative kiosks, digital signage, and mobile marketing platforms. Enhance customer engagement and streamline operations with our cutting-edge technology tailored for the hospitality, retail, and tourism industries.\",\"email\":\"info@trueomni.com\",\"telephone\":\"8333006664\",\"legalName\":\"True Omni LLC\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"VisitJacksonville - TrueOmni","description":"Visit Jacksonville","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/trueomni.com\/spanish\/visit-jacksonville\/","og_locale":"es_MX","og_type":"article","og_title":"VisitJacksonville","og_description":"Visit Jacksonville","og_url":"https:\/\/trueomni.com\/spanish\/visit-jacksonville\/","og_site_name":"TrueOmni","article_publisher":"https:\/\/www.facebook.com\/trueomni\/","article_modified_time":"2026-08-19T18:11:45+00:00","og_image":[{"width":1024,"height":753,"url":"https:\/\/trueomni.com\/wp-content\/uploads\/2026\/05\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-1024x753.png","type":"image\/png"}],"twitter_card":"summary_large_image","twitter_misc":{"Tiempo de lectura":"1 minuto"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/trueomni.com\/visit-jacksonville\/","url":"https:\/\/trueomni.com\/visit-jacksonville\/","name":"VisitJacksonville - TrueOmni","isPartOf":{"@id":"http:\/\/trueomni.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/trueomni.com\/visit-jacksonville\/#primaryimage"},"image":{"@id":"https:\/\/trueomni.com\/visit-jacksonville\/#primaryimage"},"thumbnailUrl":"https:\/\/trueomni.com\/wp-content\/uploads\/2026\/05\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png","datePublished":"2026-08-19T18:11:00+00:00","dateModified":"2026-08-19T18:11:45+00:00","description":"Visit Jacksonville","breadcrumb":{"@id":"https:\/\/trueomni.com\/visit-jacksonville\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/trueomni.com\/visit-jacksonville\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/trueomni.com\/visit-jacksonville\/#primaryimage","url":"https:\/\/trueomni.com\/wp-content\/uploads\/2026\/05\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png","contentUrl":"https:\/\/trueomni.com\/wp-content\/uploads\/2026\/05\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png","width":2560,"height":1883,"caption":"TrueOmni interactive kiosks in a hotel lobby"},{"@type":"BreadcrumbList","@id":"https:\/\/trueomni.com\/visit-jacksonville\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http:\/\/trueomni.com\/"},{"@type":"ListItem","position":2,"name":"VisitJacksonville"}]},{"@type":"WebSite","@id":"http:\/\/trueomni.com\/#website","url":"http:\/\/trueomni.com\/","name":"VerdaderoOmni","description":"TrueOmni es una empresa de experiencia digital de autoservicio para el cliente.","publisher":{"@id":"http:\/\/trueomni.com\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"http:\/\/trueomni.com\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"es"},{"@type":"Organization","@id":"http:\/\/trueomni.com\/#organization","name":"TRUE OMNI LLC","alternateName":"TRUE OMNI LLC","url":"http:\/\/trueomni.com\/","logo":{"@type":"ImageObject","inLanguage":"es","@id":"http:\/\/trueomni.com\/#\/schema\/logo\/image\/","url":"http:\/\/trueomni.com\/wp-content\/uploads\/2024\/06\/TrueOmni-Logo-2.png","contentUrl":"http:\/\/trueomni.com\/wp-content\/uploads\/2024\/06\/TrueOmni-Logo-2.png","width":1000,"height":230,"caption":"TRUE OMNI LLC"},"image":{"@id":"http:\/\/trueomni.com\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/trueomni\/"],"description":"TrueOmni is the leader in digital self-service solutions, offering innovative kiosks, digital signage, and mobile marketing platforms. Enhance customer engagement and streamline operations with our cutting-edge technology tailored for the hospitality, retail, and tourism industries.","email":"info@trueomni.com","telephone":"8333006664","legalName":"True Omni LLC"}]}},"_links":{"self":[{"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/pages\/58279","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/users\/11"}],"replies":[{"embeddable":true,"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/comments?post=58279"}],"version-history":[{"count":4,"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/pages\/58279\/revisions"}],"predecessor-version":[{"id":58283,"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/pages\/58279\/revisions\/58283"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/media\/57099"}],"wp:attachment":[{"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/media?parent=58279"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}