{"id":58253,"date":"2026-08-17T22:20:15","date_gmt":"2026-08-17T22:20:15","guid":{"rendered":"http:\/\/trueomni.com\/?page_id=58253"},"modified":"2026-08-17T22:20:50","modified_gmt":"2026-08-17T22:20:50","slug":"choose-chicago","status":"publish","type":"page","link":"https:\/\/trueomni.com\/spanish\/choose-chicago\/","title":{"rendered":"ChooseChicago"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"58253\" class=\"elementor elementor-58253\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-8d3df9a e-con e-atomic-element e-flexbox-base e-default-div e-6a246d9 \" data-id=\"8d3df9a\" data-element_type=\"e-flexbox\" data-e-type=\"e-flexbox\" data-interaction-id=\"8d3df9a\" data-e-type=\"e-flexbox\" data-id=\"8d3df9a\">\n    \t\t<div class=\"elementor-element elementor-element-c8b81b1 elementor-widget elementor-widget-html\" data-id=\"c8b81b1\" 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 Choose Chicago<\/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=\"Modeled pre-deployment projection for a Phase-1 build of 10 kiosks + 5 signage screens. Destination context is cited from Choose Chicago; all network figures are projections. Direct spend only \u2014 no ripple applied.\">MODELED&nbsp;PROJECTION<\/span>\n    <div class=\"live\"><span class=\"dot\"><\/span> PHASE 1 DEPLOYMENT PLAN \u00b7 10 kiosks + 5 screens \u00b7 Chicago<\/div>\n    <div class=\"spacer\"><\/div>\n    <div class=\"controls\">\n      <div class=\"segset\" id=\"periodSel\">\n        <button data-p=\"q\" >Opening quarter<\/button>\n        <button data-p=\"ytd\">First 6 months<\/button>\n        <button data-p=\"t12\" class=\"on\">Year-one run-rate<\/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>Destination context cited from Choose Chicago (2025 activity) \u00b7 all network, tax, advertising and signage figures are modeled pre-deployment projections \u00b7 <b>direct visitor spend only, no ripple multiplier applied<\/b> \u00b7 figures in USD.<\/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 CHOOSE CHICAGO edition.  Single tenant, USD.\n   PRE-DEPLOYMENT: Phase 1 is 10 interactive kiosks + 5 digital signage\n   screens across six districts. Every network figure is a PROJECTION.\n\n   CITED \u2014 Choose Chicago, 2025 activity:\n     56.8M visitors (record) \u00b7 $21.5B visitor spending \u00b7 135,000+ tourism-\n     supported jobs \u00b7 $2.9B+ state and local tax revenue \u00b7 11.9M occupied\n     hotel room nights (up 2.3%) \u00b7 $2.9B hotel revenue (record) \u00b7 8.2M\n     leisure hotel room nights.\n   DERIVED BY ARITHMETIC on those cited figures (shown as derived, not cited):\n     $21.5B \/ 56.8M = ~$379 direct visitor spend per visitor.\n     $2.9B hotel revenue \/ 11.9M occupied room nights = ~$244 per night.\n     11.9M total room nights - 8.2M leisure = ~3.7M group and business\n     room nights \u2014 the segment the McCormick Place placement serves.\n     $2.9B hotel revenue \/ $21.5B visitor spending = 13.5%, which is what\n     LODGING_SHARE is anchored to below.\n   ALSO CITED \u2014 the Chicago Tourism Improvement District: from 1 May 2026 a\n     1.5% assessment on hotels of 100+ rooms across sixteen downtown ZIP\n     codes, projected at ~$40M a year, controlled by Choose Chicago and an\n     11-member hotel-industry board on a five-year term. It stacks on the\n     existing 17.5% hotel tax to make downtown 19.0% \u2014 the highest hotel\n     tax rate in the United States.\n\n   DIRECT SPEND ONLY. INFL_RATE is set to 0 and total === direct. Neither\n   this destination nor any public source gives a total-economic-impact\n   figure we could derive a defensible ripple multiplier from, so rather\n   than import a generic 0.85 benchmark this model attributes only direct\n   visitor spend. Every headline number here is therefore SMALLER than a\n   conventional impact model would produce \u2014 and there is no multiplier\n   for anyone to argue with.\n------------------------------------------------------------------ *\/\nconst FUNNEL = { highIntent:0.17, conversion:0.14 };  \/\/ TrueOmni platform benchmarks\nconst INFL_RATE = 0;      \/\/ DIRECT SPEND ONLY \u2014 no ripple applied. See header.\n\n\/* Chicago capture mix. LODGING_SHARE is anchored to the destination's own\n   arithmetic: $2.9B of hotel revenue against $21.5B of visitor spending is\n   13.5%, and the network sits in public districts rather than hotel lobbies,\n   so it moves dining, tickets and retail first. *\/\nconst LODGING_SHARE = 0.16;\nconst DINING_RETAIL_SHARE = 0.62;   \/\/ dining + retail + admissions\nconst RENTAL_SHARE = 0.03;          \/\/ low \u2014 Chicago visitors are transit-served\nconst STATE_INCOME_RATE = 0.018;    \/\/ blended Illinois general-fund capture (4.95% flat income tax)\nconst RECIRC_MULT = 0;   \/\/ DIRECT SPEND ONLY \u2014 no induced\/re-circulation multiplier either.\n                         \/\/ A build that advertises \"no ripple anywhere\" cannot ship a 0.9x\n                         \/\/ induced figure on the tax page. Second-round effects are OUT.\n\n\/* Chicago tax schedule \u2014 rates live on each district as tax:{tot,sales,rental}:\n     tot    = 19.00% downtown composite hotel tax \u2014 17.5% existing plus the\n              1.5% Tourism Improvement District assessment effective 1 May\n              2026. The highest hotel tax rate in the United States. Of the\n              19.0 points, 6.0 is the Illinois Hotel Operators' Occupation\n              Tax (state) and the remaining 13.0 is City of Chicago, Cook\n              County, MPEA and the TID \u2014 treated as local.\n     sales  = 10.25% combined (6.25pp Illinois + 1.25pp Chicago + 1.75pp Cook\n              County + 1.0pp RTA). Local share = 4.0 of 10.25 points.\n     rental = 5.0% Illinois Automobile Renting Occupation Tax only. Chicago\n              and MPEA vehicle-rental surcharges were NOT verified for this\n              build and are deliberately excluded, which understates this\n              line. State levy, so no local share.\n   The taxable base is attributed direct spend (there is no ripple in this\n   model), and the 1.8% general-fund capture is a modeled rate applied to\n   attributed impact, not a statutory rate. *\/\nconst TAX_LOC = {tot:19.00, sales:10.25, rental:5.0};\nconst HOTEL_LOCAL_SHARE  = 13.0\/19.0;\nconst SALES_LOCAL_SHARE  = 4.0\/10.25;\nconst RENTAL_LOCAL_SHARE = 0;\n\/* The single most important number on this dashboard for this DMO: the\n   1.5-point TID assessment inside the 19.0% hotel tax is controlled by\n   Choose Chicago itself. 1.5 \/ 19.0 = 7.89% of every hotel-tax dollar. *\/\nconst DMO_SHARE = 1.5\/19.0;\n\nconst PERIODS = {\n  t12:{label:'Year-one run-rate \u00b7 full Phase-1 deployment',k:1},\n  ytd:{label:'First six months live (Jan\u2013Jun shape)',k:0.4483},\n  q:{label:'Opening quarter (Jan\u2013Mar shape)',k:0.1733}\n};\n\n\/* The destination's own published figures \u2014 the only cited data here. *\/\nconst CONTEXT = [\n  {v:'56.8M',   l:'Visitors in 2025 (record)'},\n  {v:'$21.5B',  l:'Visitor spending'},\n  {v:'135,000+',l:'Tourism-supported jobs'},\n  {v:'$2.9B+',  l:'State & local tax revenue'},\n  {v:'11.9M',   l:'Occupied hotel room nights'},\n  {v:'~$379',   l:'Direct spend \/ visitor (derived)'},\n];\n\n\/* Convention & group business \u2014 cited figures for the secondary-segment page. *\/\nconst SEGDATA = {\n  roomNights:11_900_000, roomNightsGrowth:0.023, leisureRoomNights:8_200_000,\n  hotelRevenue:2_900_000_000, taxRevenue:2_900_000_000, jobs:135_000,\n  tidRate:0.015, tidRevenue:40_000_000, tidZips:16, tidTermYears:5, tidBoard:11,\n  zoneIds:['mccormick','loop'], laterIds:['unitedcenter','unionstation'],\n};\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 & Culture', color:'var(--s-attractions)', raw:'#008300'},\n  events:      {name:'Events, Sports & Venues',color:'var(--s-events)',     raw:'#E87BA4'},\n};\n\n\/* Chicago seasonality: hard summer peak, weak January-February. Sums to exactly 12.0; Jan-Jun = 5.38 and Jan-Mar = 2.08, which is what the ytd and q period scales are set from. *\/\nconst SEASON = { d:[.62,.66,.80,.94,1.10,1.26,1.36,1.32,1.14,1.02,.86,.92] };\nconst MONTHS=['Jan','Feb','Mar','Apr','May','Jun','Jul','Aug','Sep','Oct','Nov','Dec'];\n\n\/* ==================================================================\n   DESTINATION REGISTRY \u2014 single tenant.\n   locations[] = the six PHASE-1 districts (10 kiosks + 5 screens).\n   exposures  = modeled annual visitor encounters at that district.\n   screens    = non-interactive OMNI Signage displays (broadcast only).\n================================================================== *\/\nconst DESTINATIONS = {\n  d:{ name:'Choose Chicago', kind:'Destination Marketing Organization', scope:'Citywide', region:'Chicago, IL',\n    outline:'M96,60 L400,60 L400,196 L372,300 L392,404 L360,560 L96,560 Z',\n    lake:'M400,60 L474,60 L474,560 L360,560 L392,404 L372,300 L400,196 Z',\n    lakeLabel:{x:432,y:310,t:'Lake Michigan'},\n    locations:[\n      {id:'magmile', name:'The Magnificent Mile', sites:'N Michigan Ave \u00b7 Water Tower \u00b7 river north end', x:330,y:206, kiosks:2, screens:1, phase:1, exposures:1_400_000, interactionRate:.011, avgSpend:395, tax:TAX_LOC, season:'d', mix:{hotels:0.20,dining:0.50,attractions:0.20,events:0.10}, note:'Chicago\\'s flagship retail and dining corridor. Enormous pedestrian volume at a low interaction rate \u2014 the classic high-traffic, low-conversion placement, which is exactly why two units and a screen sit here rather than one.', badge:'Phase 1'},\n      {id:'mccormick', name:'McCormick Place', sites:'Registration \u00b7 Grand Concourse \u00b7 exhibit halls', x:344,y:404, kiosks:2, screens:1, phase:1, exposures:700_000, interactionRate:.055, avgSpend:520, tax:TAX_LOC, season:'d', mix:{hotels:0.36,dining:0.28,attractions:0.10,events:0.26}, note:'The largest convention centre in North America, and the highest-value visitor in the city. Chicago\\'s own numbers imply roughly 3.7M group and business room nights a year (11.9M total less 8.2M leisure). Highest interaction rate and highest per-visitor spend in the network.', badge:'Phase 1'},\n      {id:'navypier', name:'Navy Pier', sites:'Festival hall \u00b7 Centennial Wheel \u00b7 lakefront promenade', x:392,y:242, kiosks:2, screens:1, phase:1, exposures:1_200_000, interactionRate:.014, avgSpend:265, tax:TAX_LOC, season:'d', mix:{hotels:0.14,dining:0.36,attractions:0.34,events:0.16}, note:'More than 8.6 million visitors a year (reported). Heavily local and family-weighted, so encounters here are modeled well below the gate and per-visitor spend is the lowest of the six.', badge:'Phase 1'},\n      {id:'loop', name:'Millennium Park & the Loop', sites:'Cloud Gate \u00b7 Pritzker Pavilion \u00b7 Loop hotels & theatres', x:330,y:286, kiosks:2, screens:1, phase:1, exposures:1_500_000, interactionRate:.010, avgSpend:310, tax:TAX_LOC, season:'d', mix:{hotels:0.24,dining:0.34,attractions:0.28,events:0.14}, note:'The City of Chicago describes Millennium Park as the number-one attraction in the Midwest and a top-ten most-visited site in the United States. The highest raw footfall in the network at the lowest interaction rate \u2014 a signage-first district as much as a kiosk one.', badge:'Phase 1'},\n      {id:'museum', name:'Museum Campus', sites:'Field Museum \u00b7 Shedd Aquarium \u00b7 Adler Planetarium', x:352,y:346, kiosks:1, screens:1, phase:1, exposures:520_000, interactionRate:.030, avgSpend:290, tax:TAX_LOC, season:'d', mix:{hotels:0.18,dining:0.24,attractions:0.46,events:0.12}, note:'Three major institutions on one lakefront campus with a ticketed, out-of-town-weighted audience. Three times the interaction rate of the open corridors, on a third of the traffic.', badge:'Phase 1'},\n      {id:'wrigley', name:'Wrigleyville & the North Side', sites:'Wrigley Field \u00b7 Gallagher Way \u00b7 Clark St', x:288,y:116, kiosks:1, screens:0, phase:1, exposures:340_000, interactionRate:.016, avgSpend:340, tax:TAX_LOC, season:'d', mix:{hotels:0.22,dining:0.40,attractions:0.12,events:0.26}, note:'Compressed, predictable event traffic on a walkable strip of bars and restaurants. Kiosk only in Phase 1 \u2014 a screen adds little where the crowd is already moving toward one destination.', badge:'Phase 1'},\n    ]},\n};\n\nlet activeDest='d';\nfunction ACT(){return DESTINATIONS[activeDest];}\nfunction LOCS(){return ACT().locations;}\nfunction findLoc(id){return LOCS().find(l=>l.id===id);}\n\n\/* ==================================================================\n   EXPANSION ZONES \u2014 Phase 2 and Phase 3 candidates, MODELED. Held OUT\n   of LOCS() so every headline, tax, partner and segment page reflects\n   only the Phase-1 build. They appear solely in the growth ladder.\n   No hotel property is named anywhere.\n================================================================== *\/\nconst EXPANSION = [\n      {id:'ohare', name:'O\\'Hare International Airport', sites:'Terminals 1\u20135 \u00b7 baggage claim \u00b7 CTA link', x:110,y:150, kiosks:3, screens:2, phase:2, exposures:3_200_000, interactionRate:.012, avgSpend:330, tax:TAX_LOC, season:'d', mix:{hotels:0.30,dining:0.30,attractions:0.24,events:0.16}, note:'A major international gateway, and the first surface most fly-in visitors touch. Modeled as the largest single addition available in Phase 2.', badge:'Phase 2'},\n      {id:'midway', name:'Midway International Airport', sites:'Concourse \u00b7 baggage claim', x:150,y:470, kiosks:1, screens:1, phase:2, exposures:780_000, interactionRate:.014, avgSpend:300, tax:TAX_LOC, season:'d', mix:{hotels:0.30,dining:0.30,attractions:0.24,events:0.16}, note:'Chicago\\'s second airport, serving a heavily leisure and domestic mix.', badge:'Phase 2'},\n      {id:'rivernorth', name:'River North & Fulton Market', sites:'Gallery district \u00b7 Fulton Market dining', x:290,y:236, kiosks:2, screens:1, phase:2, exposures:1_350_000, interactionRate:.015, avgSpend:360, tax:TAX_LOC, season:'d', mix:{hotels:0.20,dining:0.52,attractions:0.18,events:0.10}, note:'The city\\'s densest restaurant districts, walkable from the Mag Mile and the Loop hotels.', badge:'Phase 2'},\n      {id:'hotelsdt', name:'Downtown hotel lobbies', sites:'Properties inside the TID ZIP boundary \u00b7 rooms TBD', x:306,y:254, kiosks:2, screens:2, phase:2, exposures:1_100_000, interactionRate:.055, avgSpend:430, tax:TAX_LOC, season:'d', mix:{hotels:0.32,dining:0.38,attractions:0.18,events:0.12}, note:'Lobby placements are modeled as the highest-interaction surface in this build, and they sit inside the same TID footprint that funds Choose Chicago.', badge:'Phase 2'},\n      {id:'neighbourhood', name:'Chinatown & Pilsen', sites:'Wentworth Ave \u00b7 18th St \u00b7 National Museum of Mexican Art', x:248,y:420, kiosks:2, screens:1, phase:2, exposures:720_000, interactionRate:.020, avgSpend:210, tax:TAX_LOC, season:'d', mix:{hotels:0.10,dining:0.52,attractions:0.30,events:0.08}, note:'Two of Chicago\\'s strongest neighbourhood visitor districts, and the clearest way to move spend beyond the central area.', badge:'Phase 2'},\n      {id:'unitedcenter', name:'United Center & Near West', sites:'Arena concourse \u00b7 event plaza', x:214,y:300, kiosks:2, screens:2, phase:3, exposures:1_450_000, interactionRate:.013, avgSpend:330, tax:TAX_LOC, season:'d', mix:{hotels:0.20,dining:0.34,attractions:0.10,events:0.36}, note:'Major-league and touring event traffic on a compressed schedule \u2014 high impression density in short, predictable windows.', badge:'Phase 3'},\n      {id:'lincolnpark', name:'Lincoln Park & the Zoo', sites:'Lincoln Park Zoo \u00b7 Conservatory \u00b7 lakefront', x:312,y:150, kiosks:2, screens:1, phase:3, exposures:1_600_000, interactionRate:.011, avgSpend:185, tax:TAX_LOC, season:'d', mix:{hotels:0.10,dining:0.32,attractions:0.46,events:0.12}, note:'A free, very high-footfall lakefront district with a family audience \u2014 signage-led reach at a low interaction rate.', badge:'Phase 3'},\n      {id:'hydepark', name:'Hyde Park & the Museum of Science and Industry', sites:'MSI \u00b7 University of Chicago \u00b7 53rd St', x:344,y:500, kiosks:2, screens:1, phase:3, exposures:780_000, interactionRate:.026, avgSpend:255, tax:TAX_LOC, season:'d', mix:{hotels:0.16,dining:0.30,attractions:0.44,events:0.10}, note:'A ticketed cultural anchor on the South Side with a strong out-of-town share, and the district most under-served by the current plan.', badge:'Phase 3'},\n      {id:'rail', name:'Union Station & Ogilvie', sites:'Great Hall \u00b7 Metra and Amtrak concourses', x:272,y:320, kiosks:2, screens:2, phase:3, exposures:1_900_000, interactionRate:.014, avgSpend:260, tax:TAX_LOC, season:'d', mix:{hotels:0.24,dining:0.32,attractions:0.28,events:0.16}, note:'Intercity rail arrivals and a very large regional commuter flow \u2014 an arrivals surface comparable to an airport terminal.', badge:'Phase 3'},\n      {id:'hotelscity', name:'Citywide hotel lobbies', sites:'Airport, North Side & neighbourhood clusters \u00b7 rooms TBD', x:214,y:206, kiosks:2, screens:2, phase:3, exposures:980_000, interactionRate:.050, avgSpend:380, tax:TAX_LOC, season:'d', mix:{hotels:0.32,dining:0.38,attractions:0.18,events:0.12}, note:'Extends the lobby model beyond the TID boundary into the airport, North Side and neighbourhood clusters.', badge:'Phase 3'},\n];\nfunction ALLZ(){return LOCS().concat(EXPANSION);}\n\nconst LIVE_IDS = DESTINATIONS.d.locations.map(L=>L.id);\nconst P2_IDS = ['ohare','midway','rivernorth','hotelsdt','neighbourhood'];\nconst P3_IDS = ['unitedcenter','lincolnpark','hydepark','rail','hotelscity'];\nconst PHASES=[\n  {n:1, name:'Phase 1 \u00b7 10 kiosks + 5 screens',  window:'six districts \u00b7 downtown, lakefront & convention core', ids:LIVE_IDS},\n  {n:2, name:'Phase 2 \u00b7 20 kiosks + 12 screens', window:'+10 kiosks \u00b7 airports, dining districts, downtown lobbies', ids:LIVE_IDS.concat(P2_IDS)},\n  {n:3, name:'Phase 3 \u00b7 30 kiosks + 20 screens', window:'+10 kiosks \u00b7 arena, parks, South Side, rail, citywide lobbies', ids:LIVE_IDS.concat(P2_IDS,P3_IDS)},\n];\nfunction phaseLocs(ph){return ALLZ().filter(L=>ph.ids.includes(L.id));}\n\n\/* ==================================================================\n   MEDIA NETWORK \u2014 interactive kiosk advertising. CPM-driven, because\n   these districts differ enormously in footfall per unit. MODELED.\n================================================================== *\/\nconst KIOSKS_P1 = 10, SCREENS_P1 = 5;\nconst CAPEX_PER_KIOSK = 9_500, CAPEX_PER_SCREEN = 4_200;\nconst SW_PER_KIOSK_YR = 1_400, SW_PER_SCREEN_YR = 600;\nconst MEDIA={\n  cpmBand:{floor:14, mid:22, strong:34},\n  imprPerEncounter:2.6,\n  fillMature:0.70,\n  fillByPhase:{1:0.50, 2:0.62, 3:0.70},\n  streamSplit:{direct:0.58, programmatic:0.42},\n  softwarePerKioskYr:SW_PER_KIOSK_YR, capitalPerKiosk:CAPEX_PER_KIOSK,\n  get softwareYr(){return SW_PER_KIOSK_YR*KIOSKS_P1 + SW_PER_SCREEN_YR*SCREENS_P1;},\n  get capital(){return CAPEX_PER_KIOSK*KIOSKS_P1 + CAPEX_PER_SCREEN*SCREENS_P1;},\n  inventory:[\n    {id:'video',    name:'Full-screen attract-loop video',        share:.30},\n    {id:'banner',   name:'Home & footer banners',                 share:.22},\n    {id:'sponsored',name:'Sponsored dining & attraction listings',share:.20},\n    {id:'rail',     name:'Events \/ listings side-rail',           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);\n  const kiosks=r.kiosks;\n  const impressions=r.exposures*MEDIA.imprPerEncounter;\n  const sold=impressions*MEDIA.fillMature;\n  const band={}; for(const s in MEDIA.cpmBand) band[s]=sold\/1000*MEDIA.cpmBand[s];\n  const rev=band.mid;\n  const cpm=impressions? rev\/(impressions\/1000):0;\n  const perKioskMo=kiosks? rev\/kiosks\/12 : 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,sold,band,rev,cpm,perKioskMo,byStream,byInv};\n}\n\n\/* ==================================================================\n   DIGITAL SIGNAGE \u2014 non-interactive broadcast screens (OMNI Signage).\n   Screens deliver REACH and ad inventory; they do NOT transact.\n   Signage impressions never enter interactions, conversions, attributed\n   spend or tax. Only districts that actually carry a screen contribute\n   to reach.\n================================================================== *\/\nconst SIGNAGE={\n  enabled:true,\n  perScreenMo:{floor:180, mid:380, strong:660},\n  loopSec:60, spotSec:15,\n  viewShare:0.55,\n  imprPerPasserby:1.4,\n  dwellSec:8,\n  fillMature:0.68, fillByPhase:{1:0.42, 2:0.56, 3:0.68},\n  capitalPerScreen:CAPEX_PER_SCREEN, softwarePerScreenYr:SW_PER_SCREEN_YR,\n  formats:[\n    {id:'wall',     name:'Video wall \/ district hero',       share:.34},\n    {id:'portrait', name:'Portrait posters (concourse)',     share:.28},\n    {id:'wayfind',  name:'Wayfinding & directory boards',    share:.20},\n    {id:'menu',     name:'Dining & retail promo boards',     share:.12},\n    {id:'outdoor',  name:'Outdoor \/ weatherized',            share:.06},\n  ],\n};\nfunction signageMetrics(k=1, locs=LOCS()){\n  if(!SIGNAGE.enabled) return null;\n  const withScreens=locs.filter(L=>(L.screens||0)>0);\n  const screens=withScreens.reduce((s,L)=>s+(L.screens||0),0);\n  const reach=withScreens.reduce((s,L)=>s+L.exposures,0)*k*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  const shareOfVoice=1\/spotsPerLoop;\n  const capital=screens*SIGNAGE.capitalPerScreen, softwareYr=screens*SIGNAGE.softwarePerScreenYr;\n  const byFormat=SIGNAGE.formats.map(f=>({...f, v:rev*f.share}));\n  const perScreenMo=screens? rev\/screens\/12 : 0;\n  return {screens,reach,impressions,band,rev,cpm,perScreenMo,spotsPerLoop,shareOfVoice,capital,softwareYr,byFormat};\n}\nfunction totalAdRevenue(k=1, locs=LOCS()){\n  const km=mediaMetrics(k,locs), sg=signageMetrics(k,locs);\n  return {kiosk:km.rev, signage:sg?sg.rev:0, total:km.rev+(sg?sg.rev:0), km, sg};\n}\nfunction buildCost(locs){\n  const kiosks=locs.reduce((s,L)=>s+L.kiosks,0);\n  const screens=locs.reduce((s,L)=>s+(L.screens||0),0);\n  return {kiosks, screens,\n    capital:kiosks*CAPEX_PER_KIOSK + screens*CAPEX_PER_SCREEN,\n    softwareYr:kiosks*SW_PER_KIOSK_YR + screens*SW_PER_SCREEN_YR};\n}\nfunction phase1Payback(){\n  const c=buildCost(LOCS()), ad=totalAdRevenue(1, LOCS());\n  const net=ad.total-c.softwareYr;\n  return {capital:c.capital, softwareYr:c.softwareYr, ad:ad.total, net, years: net>0? c.capital\/net : 0};\n}\nfunction growthPayback(){\n  const now=buildCost(LOCS()), full=buildCost(phaseLocs(PHASES[2]));\n  const adNow=totalAdRevenue(1, LOCS()).total, adFull=totalAdRevenue(1, phaseLocs(PHASES[2])).total;\n  const capex=full.capital-now.capital;\n  const gross=adFull-adNow;\n  const gain=gross-(full.softwareYr-now.softwareYr);\n  return {capex, gross, gain, adNow, adFull, years: gain>0? capex\/gain : 0};\n}\n\n\/* ---- derive all metrics for one district 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;    \/\/ attributed DIRECT visitor spend\n  const direct = directSpend;\n  const influenced = directSpend*INFL_RATE;      \/\/ === 0, direct-spend-only build\n  const total = direct+influenced;               \/\/ === direct\n  const r = L.tax;\n  const taxableSpend = directSpend + influenced*0.5;   \/\/ === directSpend here\n  const hotelTOT   = taxableSpend*LODGING_SHARE*(r.tot\/100);\n  const salesTax   = taxableSpend*DINING_RETAIL_SHARE*(r.sales\/100);\n  const rentalTax  = taxableSpend*RENTAL_SHARE*(r.rental\/100);\n  const inc        = total*STATE_INCOME_RATE;\n  const localSub   = hotelTOT*HOTEL_LOCAL_SHARE\n                   + salesTax*SALES_LOCAL_SHARE\n                   + rentalTax*RENTAL_LOCAL_SHARE;\n  const stateTax   = inc\n                   + hotelTOT*(1-HOTEL_LOCAL_SHARE)\n                   + salesTax*(1-SALES_LOCAL_SHARE)\n                   + rentalTax*(1-RENTAL_LOCAL_SHARE);\n  const tax        = localSub+stateTax;\n  const dmoFund    = hotelTOT*DMO_SHARE;         \/\/ the slice that reaches the DMO itself\n  const recirc     = tax*RECIRC_MULT;\n  const taxLocal=localSub, taxState=stateTax;\n  const taxBreak = {hotelTOT,salesTax,rentalTax,stateTax:inc};\n  const partners = {};\n  for(const s in L.mix){ partners[s] = directSpend*L.mix[s]; }   \/\/ no influenced tail in a direct-only model\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,dmoFund,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,dmoFund: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 unreachable in single-tenant) ---------- *\/\nconst NETWORK={ destinations:1, kiosks:30, 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,dmoFund: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=['d'];\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\/* ==================================================================\n   ENGAGEMENT DRILL-THROUGH. Pre-deployment: module and campaign shares\n   are MODELED from the Phase-1 district mix. Nothing is measured \u2014 no\n   session has been recorded. Replace with the CMS export after go-live.\n================================================================== *\/\nconst MODULES=[\n  {id:'dine',    name:'Dining & Nightlife',            tag:'dining',      share:.200, dwell:112, intentRel:1.4, convRel:1.3},\n  {id:'attr',    name:'Attractions & Museums',         tag:'attractions', share:.165, dwell:98,  intentRel:1.1, convRel:1.0},\n  {id:'events',  name:'Events & Theatre',              tag:'events',      share:.125, dwell:88,  intentRel:1.1, convRel:1.0},\n  {id:'wayfind', name:'OMNI Wayfinding & Transit',     tag:'attractions', share:.120, dwell:70,  intentRel:0.7, convRel:0.6},\n  {id:'map',     name:'OMNI Maps',                     tag:'attractions', share:.095, dwell:80,  intentRel:0.9, convRel:0.8},\n  {id:'itin',    name:'OMNI Itinerary',                tag:'attractions', share:.080, dwell:150, intentRel:1.9, convRel:1.6},\n  {id:'tickets', name:'Tickets & CityPASS',            tag:'events',      share:.070, dwell:120, intentRel:2.1, convRel:1.9},\n  {id:'stay',    name:'Lodging & Hotels',              tag:'hotels',      share:.060, dwell:110, intentRel:1.7, convRel:1.6},\n  {id:'shop',    name:'Shopping & Retail',             tag:'dining',      share:.045, dwell:74,  intentRel:1.0, convRel:0.9},\n  {id:'phone',   name:'Mobile Handoff (Send to Phone)',tag:'hotels',      share:.025, dwell:34,  intentRel:2.4, convRel:2.3},\n  {id:'lang',    name:'Multi-language & ADA',          tag:'attractions', share:.015, 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:'conv',      name:'Convention Delegate Welcome',        type:'Group \/ MICE',   share:.20, intentRel:1.5, convRel:1.5, cpi:0.85},\n  {id:'dining',    name:'Chicago Restaurant Co-op',           type:'Partner co-op',  share:.17, intentRel:1.4, convRel:1.3, cpi:1.05},\n  {id:'sponsored', name:'Sponsored Placements (ad partners)', type:'Paid media',     share:.14, intentRel:0.9, convRel:0.8, cpi:0.55},\n  {id:'culture',   name:'Museum Campus & Cultural Mile',      type:'Content',        share:.12, intentRel:1.0, convRel:0.9, cpi:0.65},\n  {id:'passes',    name:'Chicago Attraction Pass',            type:'Pass \/ loyalty', share:.11, intentRel:1.6, convRel:1.5, cpi:0.90},\n  {id:'itinerary', name:'Plan-Your-Chicago Itinerary',        type:'Itinerary \/ CRM',share:.10, intentRel:1.8, convRel:1.6, cpi:1.40},\n  {id:'sports',    name:'Game Day & Wrigleyville',            type:'Content',        share:.09, intentRel:1.1, convRel:1.2, cpi:0.75},\n  {id:'shoulder',  name:'Winter Shoulder-Season Getaway',     type:'Automation',     share:.07, intentRel:1.3, convRel:1.4, cpi:0.60},\n];\nconst AB_TESTS=[\n  {id:'t1', camp:'conv',      name:'Delegate Welcome \u2014 arrival prompt',  a:'Generic welcome', b:'\u201cYou have 3 free hours \u2014 here\u2019s what\u2019s walkable\u201d', split:.5, bLift:1.28, conf:0.00, status:'Ready to launch'},\n  {id:'t2', camp:'itinerary', name:'Plan-Your-Chicago \u2014 CTA copy',       a:'\u201cBrowse things to do\u201d', b:'\u201cBuild my Chicago day\u201d', split:.5, bLift:1.15, conf:0.00, status:'Ready to launch'},\n  {id:'t3', camp:'dining',    name:'Restaurant co-op \u2014 send-to-phone',   a:'Footer link', b:'Persistent QR card',              split:.5, bLift:1.30, conf:0.00, status:'Ready to launch'},\n  {id:'t4', camp:'culture',   name:'Museum Campus \u2014 attract loop',       a:'Static hero', b:'Today\u2019s exhibitions carousel',    split:.5, bLift:1.12, conf:0.00, status:'Ready to launch'},\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\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';                 \/\/ USD only\nconst FX={USD:1};    \/\/ US destination \u2014 USD only, no FX conversion.\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)+'%';}\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  return `Phase 1 places <b>${m.kiosks} interactive kiosks<\/b> and <b>${m.screens} signage screens<\/b> across <b>${locs.length} districts<\/b>. The network is projected to reach <b>${num(m.exposures)}<\/b> visitor encounters, convert <b>${num(m.interactions)}<\/b> of them into kiosk sessions, and attribute <span class=\"em\">${money(m.total)}<\/span> of <b>direct visitor spend<\/b> and <span class=\"em\">${money(m.tax)}<\/span> of estimated public tax. <b>${best.name}<\/b> leads on attributed spend; <b>${rate.name}<\/b> carries the highest modeled interaction rate at <b>${pct(rate.interactionRate,1)}<\/b>. <b>No ripple multiplier is applied anywhere in this model<\/b> \u2014 most destination impact studies add an indirect and induced tail, and this one deliberately does not, so every figure is first-round spend and smaller than a conventional model would produce. There is no second-round effect anywhere in this model \u2014 no indirect tail on spending and no re-circulation multiplier on tax. <b>Every network figure is a pre-deployment projection.<\/b>`;\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={hotelTOT:'the hotel tax',salesTax:'sales &amp; use tax',rentalTax:'the vehicle-rental tax',stateTax:'Illinois general fund capture'}[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}% TOT)<\/b> than in <b>${lo.name} (${lo.tax.tot}%)<\/b>.` : '';\n  return `Attributed direct visitor spending is projected to generate an estimated <span class=\"em\">${money(m.tax)}<\/span> in public tax revenue this period \u2014 <b>${money(m.taxLocal)}<\/b> staying local and <b>${money(m.taxState)}<\/b> to the state. The single largest line is <b>${nm}<\/b> at <span class=\"em\">${money(top[1])}<\/span>. The line that matters most to this organisation is the hotel tax. From 1 May 2026 a 1.5-point Tourism Improvement District assessment sits inside the 19.0% downtown rate, and <b>those funds are controlled by Choose Chicago and an 11-member hotel-industry board<\/b> \u2014 so an estimated <span class=\"em\">${money(m.dmoFund)}<\/span> of the figure above flows directly into this organisation's own budget. No induced or re-circulation multiplier is applied to that figure: this build counts first-round attributed spend and the tax levied directly on it, and nothing beyond.`;\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 sessions into <span class=\"em\">${money(m.total)}<\/span> of attributed direct spend and <span class=\"em\">${money(m.tax)}<\/span> of public tax (lodging ${r.tot}%, sales ${r.sales}%, rental ${r.rental}%). ${L.note} Partners are projected to capture <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> \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. Roll-up view \u2014 unused in this single-tenant deployment. 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 projects <span class=\"em\">${money(tot)}<\/span> of direct visitor spending attributable 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> is modeled to capture the most at <span class=\"em\">${money(top[1])}<\/span>. Once live, this is the number a partner will see in their own portal \u2014 the figure that turns flat-dues membership into performance-aligned membership.`;\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 segMiniCard(k){\n  const cm=rollup(k,segLocs()), all=rollup(k);\n  const c=card('Convention &amp; Group','the segment this destination is measured on \u00b7 '+periodLabel(),'',' ');\n  c.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:2px\"><div class=\"si\"><b>${money(cm.total)}<\/b><span>attributed direct spend (modeled)<\/span><\/div><div class=\"si\"><b>${pct(cm.total\/all.total)}<\/b><span>of network impact<\/span><\/div><div class=\"si\"><b>${pct(cm.exposures\/all.exposures)}<\/b><span>of network traffic<\/span><\/div><div class=\"si\"><b>${cm.kiosks} + ${cm.screens}<\/b><span>kiosks + screens<\/span><\/div><\/div>`));\n  c.appendChild(h(`<div style=\"display:flex;gap:8px;margin-top:12px\"><button class=\"chip\" style=\"cursor:pointer\" onclick=\"go('#\/conventions')\">Open Convention &amp; Group \u203a<\/button><\/div>`));\n  return c;\n}\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 contextStrip(){\n  const c=card('Choose Chicago \u00b7 the destination\\'s own published figures','cited, not modeled','',' ');\n  const s=h('<div class=\"stat-inline\" style=\"margin-top:2px\"><\/div>');\n  CONTEXT.forEach(x=>s.appendChild(h(`<div class=\"si\"><b>${x.v}<\/b><span>${x.l}<\/span><\/div>`)));\n  c.appendChild(s);\n  c.appendChild(h(`<div class=\"note\">Five of these six are <b style=\"color:var(--text)\">cited<\/b>; the sixth (~$379 per visitor) is arithmetic on the first two. They are the only non-modeled figures on this dashboard. Everything below is a projected <b style=\"color:var(--text)\">attributed slice<\/b> of the economy above \u2014 the portion a kiosk session will be able to claim once the network is live \u2014 and it is deliberately a very small fraction of it. This build also applies <b style=\"color:var(--text)\">no ripple multiplier<\/b>, so the figures are smaller again than a conventional impact model would produce.<\/div>`));\n  c.style.marginBottom='16px';\n  return c;\n}\nfunction buildStrip(k){\n  const m=rollup(k), sg=signageMetrics(1), bc=buildCost(LOCS()), p1=phase1Payback();\n  const c=card('The Phase-1 build','what would be installed, and what it costs','',' ');\n  const s=h('<div class=\"stat-inline\" style=\"margin-top:2px\"><\/div>');\n  [[String(bc.kiosks),'Interactive kiosks'],[String(bc.screens),'Signage screens'],[String(LOCS().length),'Districts'],\n   [money2(bc.capital),'One-time capital'],[money2(bc.softwareYr),'Software \/ yr'],[p1.years.toFixed(1)+' yr','Payback from advertising \u00b7 mature fill']]\n   .forEach(x=>s.appendChild(h(`<div class=\"si\"><b>${x[0]}<\/b><span>${x[1]}<\/span><\/div>`)));\n  c.appendChild(s);\n  c.appendChild(h(`<div class=\"note\"><b style=\"color:var(--text)\">Two surfaces, two jobs.<\/b> The ${bc.kiosks} kiosks transact \u2014 they take sessions, produce high-intent actions and generate every dollar of attributed spend and tax here. The ${bc.screens} signage screens broadcast: they add ${num(sg.impressions)} Screen Impressions and ${money(sg.rev)} of advertising revenue a year at mature fill (${money(sg.rev*SIGNAGE.fillByPhase[1]\/SIGNAGE.fillMature)} at Phase-1 sell-through), and are deliberately kept out of every impact and tax figure.<\/div>`));\n  return c;\n}\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 + ${m.screens} screens \u00b7 ${LOCS().length} districts<\/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(contextStrip());\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Board-ready summary',aiOverview(k))));\n  wrap.appendChild(buildStrip(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\">Attributed direct visitor spend<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(m.total).replace(curSym(),'')}<\/div><div class=\"k-delta\" style=\"color:var(--muted)\">pre-deployment projection \u2014 no prior period<\/div><div class=\"k-foot\">Attributed <b>direct visitor spend only<\/b> \u2014 no ripple multiplier applied<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Estimated tax revenue',money(m.tax),'',`City &amp; county ${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)),'','Hotels \u00b7 dining \u00b7 attractions \u00b7 events',()=>go('#\/partners')));\n  kpis.appendChild(kpiTile('Projected kiosk sessions',num(m.interactions),'',`${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\">Encounter \u2192 kiosk session \u2192 high-intent action \u2192 attributed conversion. <b style=\"color:var(--text)\">All four stages are projected.<\/b> The 17% high-intent and 14% conversion rates are TrueOmni platform benchmarks, not measurements from this destination \u2014 nothing is installed yet. At go-live every stage becomes a ledgered kiosk event.<\/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('Attributed direct spend by district','click to drill',[dcols,legend([{name:'Direct attributed visitor spend',color:'var(--s-direct)'}]),h('<div class=\"note\">This build applies <b style=\"color:var(--text)\">no ripple multiplier<\/b>. Most destination impact models add an indirect and induced tail; this one does not, because neither a published total-economic-impact figure nor a defensible local multiplier was available. Every dollar shown is first-round visitor spend.<\/div>')]));\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  \/\/ Second-segment entry\n  wrap.appendChild(segMiniCard(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('Attributed direct spend trend','monthly \u00b7 '+periodLabel(),[areaChart(rollupSeries(k),MONTHS,{h:220}),h(`<div class=\"note\">The Phase-1 network is modeled to reach about ${num(rollup(1).exposures)} visitor encounters a year. Seasonality follows the destination's own shape; the shoulder months are where a kiosk network earns most, because that is when the destination has to work hardest for each visitor. Direct attributed spend only \u2014 no ripple.<\/div>`)]);\n  wrap.appendChild(trend);\n  return wrap;\n}\nfunction rampCard(k){\n  const c=card('Growth ladder \u00b7 10 \u2192 20 \u2192 30 kiosks','Phase 1 is the proposed build; Phases 2 and 3 are modeled scenarios \u00b7 '+periodLabel(),'',' ');\n  const list=h('<div class=\"loclist\"><\/div>');\n  let prevK=0;\n  PHASES.forEach(ph=>{\n    const locs=phaseLocs(ph);\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 + ${m.screens} screens${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)} stays local<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(m.kiosks\/30*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    list.appendChild(row);\n  });\n  c.appendChild(list);\n  c.appendChild(h(`<div class=\"note\"><b style=\"color:var(--text)\">Phase 1 is the proposed build<\/b> \u2014 10 interactive kiosks and 5 signage screens across six districts. Phases 2 and 3 are <b>modeled scenarios<\/b>, not commitments, and no hotel property is named in either. Every figure here is a projection against the destination's own cited visitor base; nothing has been installed and nothing has been measured. Note that impact scales with <i>encounters<\/i>, not with kiosk count, which is why the steps are not proportional.<\/div>`));\n  return c;\n}\nfunction kpiTile(label,val,delta,foot,onClick){\n  const d=(delta||'').replace('\u25b2','').trim();\n  const c=h(`<div class=\"card kpi\"${onClick?' style=\"cursor:pointer\"':''}><div class=\"k-label\">${label}<\/div><div class=\"k-val\">${val}<\/div>${d?`<div class=\"k-delta up\">\u25b2 ${d}<\/div>`:'<div class=\"k-delta\" style=\"visibility:hidden\">\u2013<\/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,1)}<\/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:'Hotel tax',color:'var(--s-local)'},\n    {v:b.salesTax,name:'Sales & use',color:'#33abe0'},\n    {v:b.rentalTax,name:'Vehicle rental',color:'#7cc9e8'},\n    {v:b.stateTax,name:'Illinois general fund',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:100%\"><\/i><\/div>`));\n  c.appendChild(h(`<div class=\"note\"><b style=\"color:var(--text)\">No re-circulation multiplier is applied<\/b> \u2014 this is a direct-spend-only build. <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}% \u00b7 Rental ${r.rental}%<\/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\">Attributed direct spend<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(m.total).replace(curSym(),'')}<\/div><div class=\"k-foot\">Attributed direct visitor spend only \u2014 no ripple applied<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Estimated tax revenue',money(m.tax),'',`City &amp; county ${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 \u00b7 modeled',pct(L.interactionRate,1),'',`${num(m.interactions)} projected sessions`));\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:`Chicago composite hotel tax (19.0%)`,parts:[{v:b.hotelTOT,color:'var(--s-local)',name:'Hotel tax'}],total:b.hotelTOT},\n    {label:`Sales & use (${r.sales}%)`,parts:[{v:b.salesTax,color:'#33abe0',name:'Combined sales & use tax'}],total:b.salesTax},\n    {label:`Vehicle rental (${r.rental}%)`,parts:[{v:b.rentalTax,color:'#7cc9e8',name:'Vehicle-rental tax'}],total:b.rentalTax},\n    {label:'Illinois general fund capture',parts:[{v:b.stateTax,color:'var(--s-state)',name:'Illinois general fund'}],total:b.stateTax},\n  ];\n  const c=card('Estimated tax revenue','by jurisdiction','',' ');\n  c.appendChild(rankedStacked(rows,{pl:150,w:560}));\n  c.appendChild(h(`<div class=\"note\">Chicago rates: 19.0% downtown composite hotel tax \u2014 the highest in the United States \u2014 of which 6.0pp is the Illinois Hotel Operators' Occupation Tax and 13.0pp is city, county, MPEA and the Tourism Improvement District; 10.25% combined sales &amp; use (6.25pp state \/ 4.0pp city, Cook County and RTA); 5.0% Illinois Automobile Renting Occupation Tax. Chicago and MPEA vehicle-rental surcharges are not modeled, which understates that line. <b style=\"color:var(--text)\">The taxable base<\/b> is attributed direct spend \u2014 this build applies no ripple \u2014 and the 1.8% general-fund line is a modeled capture rate, not a statutory rate. No re-circulation multiplier is applied.<\/div>`));\n  return c;\n}\nfunction buildSamplePartners(L,m){\n  \/* Real local businesses, institutions and partner groupings. Illustrative\n     placement of attributed dollars \u2014 no partner has been contacted, quoted\n     or committed, and no individual hotel property is named. *\/\n  const SAMPLES={\n    magmile:  ['Magnificent Mile hotel partners','Michigan Ave dining partners','360 CHICAGO','Mag Mile Lights Festival'],\n    mccormick:['Convention-district hotel partners','South Loop dining partners','Museum Campus','McCormick Place expo halls'],\n    navypier: ['Streeterville hotel partners','Navy Pier dining partners','Centennial Wheel','Navy Pier fireworks & festivals'],\n    loop:     ['Loop hotel partners','Loop dining partners','Millennium Park','Chicago Theatre & Loop venues'],\n    museum:   ['South Loop hotel partners','Museum Campus dining','Field Museum \u00b7 Shedd \u00b7 Adler','Museum Campus programming'],\n    wrigley:  ['Wrigleyville hotel partners','Clark St dining partners','Wrigley Field tours','Cubs home games'],\n  };\n  const names=SAMPLES[L.id]||['Hotel','Restaurant','Attraction','Venue'];\n  const segIds=['hotels','dining','attractions','events'];\n  const c=card('Attributed partners','illustrative \u2014 the split of dollars across them is modeled, and no partner has been contacted, quoted or committed','',' ');\n  const tbl=h('<table><thead><tr><th>Partner<\/th><th>Segment<\/th><th>Attributed spend<\/th><th>Referrals<\/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><\/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>`);\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('What the partner portal changes','the mechanism, not a forecast',[h(`<div class=\"stat-inline\" style=\"margin-top:6px\"><div class=\"si\"><b>Per-partner<\/b><span>attributed dollars, not blended averages<\/span><\/div><div class=\"si\"><b>Referral-level<\/b><span>every dollar ties to a kiosk event<\/span><\/div><div class=\"si\"><b>Direct only<\/b><span>no ripple in the figure a partner sees<\/span><\/div><\/div>`),h('<div class=\"note\">When a business can see its own attributed dollars rather than a blended network average, flat-dues membership becomes performance-aligned membership. No uplift percentages are shown here because this destination has no partner-portal baseline yet \u2014 that is a figure to establish after go-live, not to assert before it.<\/div>')]));\n  return wrap;\n}\n\/* SEG_SHARE removed \u2014 hardcoded template constant that contradicted the computed mix. *\/\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\">City &amp; county ${money(m.taxLocal)} + State ${money(m.taxState)}<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Hotel tax',money(b.hotelTOT),'',`19.0% downtown \u2014 highest in the US \u00b7 ${money(m.dmoFund)} of it is the TID slice Choose Chicago controls`));\n  kpis.appendChild(kpiTile('Sales & use',money(b.salesTax),'','10.25% \u2014 of which 4.0pp city, county &amp; RTA'));\n  kpis.appendChild(kpiTile('Vehicle rental',money(b.rentalTax),'','5.0% Illinois auto-renting tax (state)'));\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:'Hotel tax'},\n      {v:bb.salesTax,color:'#33abe0',name:'Sales & use'},\n      {v:bb.rentalTax,color:'#7cc9e8',name:'Vehicle rental'},\n      {v:bb.stateTax,color:'var(--s-state)',name:'Illinois general fund'},\n    ]};}).sort((a,b)=>b.total-a.total);\n  const c1=card('Estimated tax revenue by district','lodging \u00b7 sales &amp; use \u00b7 rental \u00b7 state \u00b7 click a bar to drill','',' ');\n  c1.appendChild(rankedStacked(rows,{pl:120,w:680}));\n  c1.appendChild(legend([{name:'Hotel tax',color:'var(--s-local)'},{name:'Sales & use',color:'#33abe0'},{name:'Vehicle rental',color:'#7cc9e8'},{name:'Illinois general fund',color:'var(--s-state)'}]));\n  wrap.appendChild(c1);\n\n  \/\/ no induced \/ re-circulation multiplier in a direct-spend-only build\n  const recircCard=card('What this model does NOT count','the deliberate omissions','',' ');\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>0\u00d7<\/b><span>ripple \/ indirect multiplier<\/span><\/div><div class=\"si\"><b>0\u00d7<\/b><span>induced re-circulation<\/span><\/div><div class=\"si\"><b>${money(m.total)}<\/b><span>attributed direct spend \u2014 the whole figure<\/span><\/div><\/div>`));\n  recircCard.appendChild(h(`<div class=\"note\">Most destination impact models add two things this one does not. The first is an <b style=\"color:var(--text)\">indirect and induced tail<\/b> on visitor spending, commonly 0.7\u20131.1\u00d7 the direct figure. The second is a <b style=\"color:var(--text)\">re-circulation multiplier<\/b> on public tax revenue, on the argument that public money funds payroll and procurement that get re-spent locally. Both are defensible and both are standard. Neither is applied here, because Choose Chicago publishes no total-economic-impact figure to derive a local multiplier from, and an imported benchmark is the first thing a sceptical reader attacks. Every dollar on this dashboard is first-round attributed visitor spend and the tax levied directly on it \u2014 nothing else.<\/div>`));\n  wrap.appendChild(recircCard);\n\n  \/\/ full data table\n  const c2=card('Full tax detail','modeled line items \u00b7 '+periodLabel()+' \u00b7 levied on attributed direct spend; no ripple in the base','',' ');\n  const tbl=h('<table><thead><tr><th>District<\/th><th>Lodging %<\/th><th>Hotel tax<\/th><th>Sales &amp; use<\/th><th>Rental<\/th><th>State<\/th><th>Total tax<\/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><\/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-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),'',`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)),'','across all destinations'));\n  kpis.appendChild(kpiTile('Trackable visitor actions',num(net.interactions),'',`${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 destinations<\/div><div class=\"lr-sub\">~${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\">Roll-up view, unused in this single-tenant deployment. 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\">Roll-up view, unused in this single-tenant deployment. Aggregate curve built from behavioral data across the network. Each destination re-circulates public tax dollars into its own community \u2014 an estimated '+money(net.recirc)+' network-wide (direct spend only, no multipliers).<\/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 class=\"ch-sub\" style=\"margin-top:6px;color:var(--text-2);max-width:780px\">Nothing on this page is measured. Module and campaign shares are <b>modeled<\/b> from the Phase-1 district mix and the TrueOmni module set. They exist so the network launches with a hypothesis worth testing, and should be replaced with the CMS export within the first quarter of operation.<\/div><\/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()+' \u00b7 module shares are modeled pre-deployment','',' ');\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 proven=t.conf>=0.9;   \/\/ pre-launch tests have conf 0 \u2014 never show a winner\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    : proven ? `<span class=\"ab-badge ab-win\">${t.status} \u00b7 ${pct(t.conf,0)} conf.<\/span>`\n    : `<span class=\"ab-badge ab-none\">${t.status} \u00b7 not yet run<\/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&&proven?'<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,proven&&!winnerB)));\n  c.appendChild(h(variant('B',t.b,rB,cvB,impB,proven&&winnerB)));\n  c.appendChild(h(`<div class=\"ab-foot\"><span>Variant B vs A<\/span><span><b style=\"color:${proven?(lift>=0?'var(--good)':'var(--warn)'):'var(--muted)'}\">${lift>=0?'+':''}${pct(lift,1)}<\/b> modeled target lift (hypothesis \u2014 test not yet run)<\/span><\/div>`));\n  return c;\n}\n\n\/* ================= MEDIA NETWORK ================= *\/\n\/* The Media Network view is always an ANNUAL run-rate \u2014 scaling it by the\n   period selector would print partial-year values under \"\/ yr\" labels. *\/\nfunction aiMedia(_k){const m=mediaMetrics(1), sg=signageMetrics(1), ad=totalAdRevenue(1), p1=phase1Payback(), gp=growthPayback();\n  return `Phase 1 puts <b>${m.kiosks} interactive kiosks<\/b> and <b>${sg.screens} digital signage screens<\/b> across six districts. Together they are projected to deliver <b>${num(m.impressions+sg.impressions)}<\/b> Screen Impressions a year \u2014 ${num(m.impressions)} from the kiosk attract loop and ${num(sg.impressions)} from signage reaching ${num(sg.reach)} passers-by \u2014 for <span class=\"em\">${money(ad.total)}<\/span> of Total Advertising Revenue at mature Fill Rate. That splits <b>${money(ad.kiosk)}<\/b> kiosk (effective CPM $${m.cpm.toFixed(2)}, ${money2(m.perKioskMo)} per kiosk per month at mature fill) and <b>${money(ad.signage)}<\/b> signage (${money2(sg.perScreenMo)} per screen per month at mature fill, ${pct(sg.shareOfVoice)} share of voice on a ${SIGNAGE.loopSec}-second loop). About <b>${pct(MEDIA.streamSplit.direct)}<\/b> of kiosk revenue is direct\/native local sales, the rest programmatic through Google Ad Manager. <b>The economics that matter:<\/b> against ${money2(p1.capital)} of Phase-1 hardware and ${money2(p1.softwareYr)} a year of software, the advertising layer alone repays the entire build in about <b>${p1.years.toFixed(1)} years<\/b> at mature fill \u2014 and scaling to 30 kiosks and 20 screens lifts Total Advertising Revenue to <span class=\"em\">${money(gp.adFull)}<\/span>, repaying that expansion in roughly <b>${gp.years.toFixed(1)} years<\/b>.`;}\nfunction mediaMiniCard(_k){const m=mediaMetrics(1), sg=signageMetrics(1), ad=totalAdRevenue(1), p1=phase1Payback();\n  const c=card('Media Network \u00b7 Total Advertising Revenue','the self-funding layer \u00b7 kiosks + signage \u00b7 annual run-rate at mature fill','',' ');\n  c.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:2px\"><div class=\"si\"><b>${money(ad.total)}<\/b><span>Total Advertising Revenue \/ yr<\/span><\/div><div class=\"si\"><b>${num(m.impressions+sg.impressions)}<\/b><span>Screen Impressions \/ yr \u00b7 kiosk ${num(m.impressions)} + signage ${num(sg.impressions)}<\/span><\/div><div class=\"si\"><b>${money2(m.perKioskMo)}<\/b><span>per kiosk \/ mo \u00b7 mature fill<\/span><\/div><div class=\"si\"><b>${money2(sg.perScreenMo)}<\/b><span>per screen \/ mo \u00b7 mature fill<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${p1.years.toFixed(1)} yr<\/b><span>Phase-1 payback \u00b7 at mature fill<\/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}\nfunction pageMedia(){\n  const k=1; const m=mediaMetrics(k); const sg=signageMetrics(k); const ad=totalAdRevenue(k);  \/\/ always annual run-rate\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\">\u203a<\/span><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>Media Network<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">Advertising \u00b7 Media Network<\/div><h1>Media Network \u2014 Total Advertising Revenue<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">Interactive kiosks + OMNI digital signage \u00b7 annual run-rate, independent of the period selector<\/div><\/div><div class=\"meta\">Sell-through at maturity<br><b style=\"color:var(--text);font-size:14px\">${m.kiosks} kiosks at ${pct(MEDIA.fillMature)} \u00b7 ${sg.screens} screens at ${pct(SIGNAGE.fillMature)}<\/b><\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 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 \/ yr <span style=\"opacity:.62;font-weight:600\">\u00b7 at mature fill<\/span><\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(ad.total).replace(curSym(),'')}<\/div><div class=\"k-delta up\">\u25b2 scales with fill, kiosks and screens<\/div><div class=\"k-foot\">Kiosk ${money(ad.kiosk)} + signage ${money(ad.signage)} \u00b7 kiosk range ${money(m.band.floor)}\u2013${money(m.band.strong)}<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Screen Impressions \/ yr',num(m.impressions+sg.impressions),'',`kiosk ${num(m.impressions)} \u00b7 signage ${num(sg.impressions)}`));\n  kpis.appendChild(kpiTile('Effective CPM \u00b7 kiosk','$'+m.cpm.toFixed(2),'',`signage $${sg.cpm.toFixed(2)} \u00b7 ${pct(MEDIA.fillMature)} kiosk Fill Rate`));\n  kpis.appendChild(kpiTile('Covers annual software',(ad.total\/MEDIA.softwareYr).toFixed(1)+'\u00d7 at mature fill','',`vs ${money2(MEDIA.softwareYr)}\/yr for 10 kiosks + 5 screens`));\n  wrap.appendChild(kpis);\n\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); 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',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); r2.appendChild(streamCard);\n  wrap.appendChild(r2);\n\n  const rampC=card('Total Advertising Revenue \u00b7 10 \u2192 20 \u2192 30 kiosks','kiosk + signage, at the sell-through achievable at each phase','',' ');\n  const rl=h('<div class=\"loclist\"><\/div>');\n  PHASES.forEach(ph=>{const locs=phaseLocs(ph);const mm=mediaMetrics(k,locs);const sgm=signageMetrics(k,locs);\n    const kfill=MEDIA.fillByPhase[ph.n], sfill=SIGNAGE.fillByPhase[ph.n];\n    const krev=mm.rev*(kfill\/MEDIA.fillMature), srev=sgm?sgm.rev*(sfill\/SIGNAGE.fillMature):0;\n    rl.appendChild(h(`<div class=\"locrow\"><div><div class=\"lr-name\">${ph.name}<\/div><div class=\"lr-sub\">${mm.kiosks} kiosks at ${pct(kfill)} fill \u00b7 ${sgm?sgm.screens:0} screens at ${pct(sfill)} fill<\/div><\/div><div class=\"lr-val\">${money(krev+srev)}<div class=\"lr-sub\" style=\"font-weight:600\">kiosk ${money(krev)} \u00b7 signage ${money(srev)}<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(mm.kiosks\/30*100).toFixed(0)}%\"><\/i><\/div><\/div>`));});\n  rampC.appendChild(rl);\n  rampC.appendChild(h(`<div class=\"note\">Two surfaces, two revenue models. Kiosk revenue is CPM-driven on ${MEDIA.imprPerEncounter} addressable impressions per encounter; signage is priced per screen per month, because a broadcast display sells as a placement rather than as an audience. The rows above run at the sell-through realistically achievable at each stage; the headline tile states everything at mature fill. Chicago is one of the largest media markets in the United States, which is what the per-unit footfall reflects. <b style=\"color:var(--text)\">All modeled<\/b> \u2014 replace the CPM band, the per-screen rate and the fill curve with the destination's own rate card. Note that Chicago's projected revenue per kiosk per month lands <i>above<\/i> the $500\u20131,500 platform benchmark: the CPM used here is the standard band, so the whole difference is per-unit footfall rather than price. It is the figure most in need of a real rate card before anyone quotes it externally.<\/div>`));\n  wrap.appendChild(rampC);\n\n  \/\/ ---- Digital Signage (broadcast reach \u2014 never transacts) ----\n  const sgc=card('Digital Signage \u00b7 OMNI Signage','5 non-interactive broadcast screens \u2014 reach and ad inventory, no transactions','',' ');\n  sgc.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:2px\"><div class=\"si\"><b>${sg.screens}<\/b><span>screens deployed<\/span><\/div><div class=\"si\"><b>${num(sg.reach)}<\/b><span>reach \/ yr<\/span><\/div><div class=\"si\"><b>${num(sg.impressions)}<\/b><span>Screen Impressions \/ yr<\/span><\/div><div class=\"si\"><b>${SIGNAGE.dwellSec}s<\/b><span>avg attention<\/span><\/div><div class=\"si\"><b>${sg.spotsPerLoop} \/ ${pct(sg.shareOfVoice)}<\/b><span>spots per loop \u00b7 share of voice<\/span><\/div><div class=\"si\"><b>${money2(sg.perScreenMo)}<\/b><span>revenue per screen \/ mo \u00b7 mature fill<\/span><\/div><\/div>`));\n  const fl=h('<div class=\"loclist\" style=\"margin-top:10px\"><\/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)}%;background:linear-gradient(90deg,var(--s-events),var(--s-dining))\"><\/i><\/div><\/div>`)));\n  sgc.appendChild(fl);\n  const scr=h('<div class=\"loclist\" style=\"margin-top:12px\"><\/div>');\n  const withS=LOCS().filter(L=>(L.screens||0)>0), noS=LOCS().filter(L=>!(L.screens||0)); const smax=Math.max(...withS.map(L=>L.exposures));\n  withS.forEach(L=>scr.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\">${L.name}<\/div><div class=\"lr-val\">${L.screens} screen${L.screens>1?'s':''}<div class=\"lr-sub\" style=\"font-weight:600\">${num(L.exposures*SIGNAGE.viewShare)} reach<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(L.exposures\/smax*100).toFixed(0)}%\"><\/i><\/div><\/div>`)));\n  sgc.appendChild(scr);\n  sgc.appendChild(h(`<div class=\"note\"><b style=\"color:var(--text)\">Signage broadcasts; kiosks transact.<\/b> Screen Impressions above are <i>reach<\/i> \u2014 deliberately excluded from interactions, attributed conversions, direct spend and tax everywhere else on this dashboard, which stay kiosk-derived. ${noS.length?`${noS.map(L=>L.name).join(' and ')} run${noS.length===1?'s':''} a kiosk with no screen, so ${noS.length===1?'its':'their'} ${num(noS.reduce((s,L)=>s+L.exposures,0))} encounters are not in the reach figure above.`:''}<\/div>`));\n  wrap.appendChild(sgc);\n\n  const sf=card('The self-funding math','advertising vs. the Phase-1 build cost','',' ');\n  const p1=phase1Payback(), gp=growthPayback(), bc=buildCost(LOCS());\n  const phaseAd=ad.kiosk*MEDIA.fillByPhase[1]\/MEDIA.fillMature + ad.signage*SIGNAGE.fillByPhase[1]\/SIGNAGE.fillMature;\n  sf.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:4px\"><div class=\"si\"><b>${money(ad.total)}<\/b><span>Total Advertising Revenue \/ yr \u00b7 mature fill<\/span><\/div><div class=\"si\"><b>${money2(p1.softwareYr)}<\/b><span>annual software<\/span><\/div><div class=\"si\"><b>${money2(p1.capital)}<\/b><span>Phase-1 capital<\/span><\/div><div class=\"si\"><b>${money(p1.net)}<\/b><span>net of software \/ yr<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${p1.years.toFixed(1)} yr<\/b><span>Phase-1 payback \u00b7 mature fill<\/span><\/div><\/div>`));\n  sf.appendChild(h(`<div class=\"note\">Phase-1 capital of ${money2(p1.capital)} covers ${bc.kiosks} kiosks at ${money2(CAPEX_PER_KIOSK)} and ${bc.screens} screens at ${money2(CAPEX_PER_SCREEN)}; software runs ${money2(SW_PER_KIOSK_YR)} per kiosk and ${money2(SW_PER_SCREEN_YR)} per screen per year. At mature fill the advertising layer returns ${money(p1.net)} a year net of software \u2014 repaying the entire build in about <b style=\"color:var(--text)\">${p1.years.toFixed(1)} years<\/b> on that basis, or ${(p1.capital\/(phaseAd-p1.softwareYr)).toFixed(1)} years at the sell-through realistically achievable in Phase 1 \u2014 before a single dollar of attributed visitor spend or public tax is counted. Scaling to 30 kiosks and 20 screens costs a further ${money2(gp.capex)} and adds ${money(gp.gain)} a year net, a ${gp.years.toFixed(1)}-year payback. <b>All modeled.<\/b> The CPM band, the per-screen rate, the fill curve and the hardware pricing all need replacing with the destination's actual rate card and quoted build cost.<\/div>`));\n  wrap.appendChild(sf);\n  return wrap;\n}\n\n\nfunction segDetailRow(k,cl,cm,phaseNote){\n  const r2=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  const dc=card('District detail','the Phase-1 districts serving this segment','',' ');\n  const dl=h('<div class=\"loclist\"><\/div>'); const dmax=Math.max(...cl.map(L=>deriveMetrics(L,k).total));\n  cl.forEach(L=>{const mm=deriveMetrics(L,k);\n    const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${L.name}<\/div><div class=\"lr-sub\">${L.kiosks} kiosks \u00b7 ${L.screens} screen${L.screens===1?'':'s'} \u00b7 ${pct(L.interactionRate,1)} interaction \u00b7 ${money2(L.avgSpend)} \/ visitor<\/div><\/div><div class=\"lr-val\">${money(mm.total)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(mm.tax)} tax<\/div><\/div><span class=\"chev\">\u203a<\/span><div class=\"lr-bar\"><i style=\"width:${(mm.total\/dmax*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    row.addEventListener('click',()=>go('#\/location\/'+L.id)); dl.appendChild(row);});\n  dc.appendChild(dl); dc.appendChild(h(`<div class=\"note\">${phaseNote}<\/div>`)); r2.appendChild(dc);\n  const mc=card('Where the visitor dollar lands','partner mix across these districts','',' ');\n  const parts=Object.keys(cm.partners).map(s=>({v:cm.partners[s],name:SEG[s].name,color:SEG[s].color}));\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>');\n  parts.slice().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); mc.appendChild(pin);\n  mc.appendChild(h('<div class=\"note\">Direct attributed spend only \u2014 no ripple multiplier is applied anywhere in this model, so these are the dollars a partner could in principle reconcile against their own till.<\/div>'));\n  r2.appendChild(mc);\n  return r2;\n}\n\n\/* ================= CONVENTION & GROUP BUSINESS ================= *\/\nfunction segLocs(){return LOCS().filter(L=>SEGDATA.zoneIds.includes(L.id));}\nfunction aiSeg(k){\n  const cm=rollup(k,segLocs()), all=rollup(k);\n  const cc=findLoc('mccormick');\n  const groupNights=SEGDATA.roomNights-SEGDATA.leisureRoomNights;\n  const perNight=SEGDATA.hotelRevenue\/SEGDATA.roomNights;\n  return `Chicago filled <b>${SEGDATA.roomNights.toLocaleString()}<\/b> hotel room nights in 2025, up ${pct(SEGDATA.roomNightsGrowth,1)}, for a record <b>${money(SEGDATA.hotelRevenue)}<\/b> of hotel revenue. Two of those figures do arithmetic worth saying out loud: <b>${SEGDATA.leisureRoomNights.toLocaleString()}<\/b> were leisure, which leaves roughly <b>${groupNights.toLocaleString()} group and business room nights<\/b> \u2014 the segment McCormick Place feeds \u2014 and the revenue divides to about <b>${money2(perNight)}<\/b> per occupied night. Phase 1 puts <b>${cm.kiosks} kiosks and ${cm.screens} screens<\/b> into McCormick Place and the Loop. They are modeled to carry <span class=\"em\">${money(cm.total)}<\/span> of attributed direct spend, <b>${pct(cm.total\/all.total)}<\/b> of the whole network, on <b>${pct(cm.exposures\/all.exposures)}<\/b> of its foot traffic \u2014 because a delegate at McCormick Place is modeled at <b>${money2(cc.avgSpend)}<\/b> of attributable direct spend against a citywide average of about $379, at the highest interaction rate in the network at <b>${pct(cc.interactionRate,1)}<\/b>. And from 1 May 2026 every one of those room nights carries the <b>${pct(SEGDATA.tidRate,1)} Tourism Improvement District<\/b> assessment that Choose Chicago controls.`;\n}\nfunction pageSeg(){\n  const k=K(); const cl=segLocs(); const cm=rollup(k,cl); const all=rollup(k);\n  const groupNights=SEGDATA.roomNights-SEGDATA.leisureRoomNights;\n  const perNight=SEGDATA.hotelRevenue\/SEGDATA.roomNights;\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\">\u203a<\/span><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>Convention &amp; Group<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">Group business \u00b7 convention, corporate &amp; citywide events<\/div><h1>The room nights leisure doesn't explain<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">${cm.kiosks} kiosks \u00b7 ${cm.screens} screens \u00b7 ${cl.length} districts \u00b7 ${periodLabel()}<\/div><\/div><div class=\"meta\">Share of network impact<br><b style=\"color:var(--text);font-size:14px\">${pct(cm.total\/all.total)}<\/b><\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Group business read', aiSeg(k))));\n\n  const cited=card('Choose Chicago \u00b7 2025 hotel and group performance','cited, and two figures derived from it by arithmetic','',' ');\n  const cs=h('<div class=\"stat-inline\" style=\"margin-top:2px\"><\/div>');\n  [[SEGDATA.roomNights.toLocaleString(),'Occupied hotel room nights (cited)'],\n   ['+'+pct(SEGDATA.roomNightsGrowth,1),'Room-night growth (cited)'],\n   [money(SEGDATA.hotelRevenue),'Hotel revenue, record (cited)'],\n   [SEGDATA.leisureRoomNights.toLocaleString(),'Leisure room nights (cited)'],\n   ['~'+groupNights.toLocaleString(),'Group &amp; business room nights (derived)'],\n   ['~'+money2(perNight),'Hotel revenue per occupied night (derived)']]\n   .forEach(x=>cs.appendChild(h(`<div class=\"si\"><b>${x[0]}<\/b><span>${x[1]}<\/span><\/div>`)));\n  cited.appendChild(cs);\n  cited.appendChild(h(`<div class=\"note\">The first four are Choose Chicago's own reported 2025 figures. The last two are simple arithmetic on them \u2014 11.9M less 8.2M, and $2.9B divided by 11.9M \u2014 and are labelled derived rather than cited. The dollar figures below are <b style=\"color:var(--text)\">modeled projections<\/b> for the Phase-1 kiosks in these districts, and are not calculated from the room-night total.<\/div>`));\n  cited.style.marginBottom='16px'; wrap.appendChild(cited);\n\n  const tid=card('The Tourism Improvement District','cited \u00b7 effective 1 May 2026','',' ');\n  const ts=h('<div class=\"stat-inline\" style=\"margin-top:2px\"><\/div>');\n  [[pct(SEGDATA.tidRate,1),'assessment on hotels of 100+ rooms'],\n   ['~'+money(SEGDATA.tidRevenue),'projected annually'],\n   [String(SEGDATA.tidZips),'downtown ZIP codes in the district'],\n   [String(SEGDATA.tidBoard),'-member hotel-industry board'],\n   [SEGDATA.tidTermYears+' yr','initial term'],\n   ['19.0%','downtown composite hotel tax \u2014 highest in the US']]\n   .forEach(x=>ts.appendChild(h(`<div class=\"si\"><b>${x[0]}<\/b><span>${x[1]}<\/span><\/div>`)));\n  tid.appendChild(ts);\n  tid.appendChild(h(`<div class=\"note\"><b style=\"color:var(--text)\">This is the line that connects a screen to a budget.<\/b> The TID assessment is controlled by Choose Chicago and an 11-member hotel-industry board. Of the estimated ${money(rollup(k).taxBreak.hotelTOT)} of hotel tax this network's attributed spend would generate, roughly <b style=\"color:var(--text)\">${money(rollup(k).dmoFund)}<\/b> is the TID slice \u2014 modest today, and the only figure on this dashboard that returns to this organisation directly.<\/div>`));\n  tid.style.marginBottom='16px'; wrap.appendChild(tid);\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\">Attributed direct spend \u00b7 convention &amp; Loop districts<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(cm.total).replace(curSym(),'')}<\/div><div class=\"k-delta up\">\u25b2 ${pct(cm.total\/all.total)} of network impact on ${pct(cm.exposures\/all.exposures)} of its traffic<\/div><div class=\"k-foot\">Direct spend only \u2014 no ripple multiplier applied<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Public tax',money(cm.tax),'',`City &amp; county ${money(cm.taxLocal)} \u00b7 State ${money(cm.taxState)}`,()=>go('#\/tax')));\n  kpis.appendChild(kpiTile('Events &amp; venues captured',money(cm.partners.events),'','the segment these districts feed',()=>go('#\/partners')));\n  kpis.appendChild(kpiTile('Projected kiosk sessions',num(cm.interactions),'',`${num(cm.conversions)} attributed conversions`));\n  wrap.appendChild(kpis);\n  wrap.appendChild(segDetailRow(k,cl,cm,'Phase 3 adds two more districts to this segment \u2014 <b style=\"color:var(--text)\">United Center<\/b> and <b style=\"color:var(--text)\">Union Station &amp; Ogilvie<\/b>. Both are modeled scenarios, not commitments.'));\n  wrap.appendChild(card('Group-business impact through the year','monthly \u00b7 convention &amp; Loop districts',[areaChart(rollupSeries(k,cl),MONTHS,{h:210}),h('<div class=\"note\">Chicago\\'s convention calendar tracks the city\\'s wider seasonality but softens the winter trough \u2014 the part of the year where a delegate-facing kiosk earns most, because the visitor is already indoors and looking for somewhere to go.<\/div>')]));\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]==='conventions') node=pageSeg();\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 toggle removed \u2014 US destination, USD only.\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>Choose Chicago<\/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-58253","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>ChooseChicago - TrueOmni<\/title>\n<meta name=\"description\" content=\"Choose Chicago\" \/>\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\/choose-chicago\/\" \/>\n<meta property=\"og:locale\" content=\"es_MX\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"ChooseChicago\" \/>\n<meta property=\"og:description\" content=\"Choose Chicago\" \/>\n<meta property=\"og:url\" content=\"https:\/\/trueomni.com\/spanish\/choose-chicago\/\" \/>\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-17T22:20:50+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\\\/choose-chicago\\\/\",\"url\":\"https:\\\/\\\/trueomni.com\\\/choose-chicago\\\/\",\"name\":\"ChooseChicago - TrueOmni\",\"isPartOf\":{\"@id\":\"http:\\\/\\\/trueomni.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/choose-chicago\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/choose-chicago\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/trueomni.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png\",\"datePublished\":\"2026-08-17T22:20:15+00:00\",\"dateModified\":\"2026-08-17T22:20:50+00:00\",\"description\":\"Choose Chicago\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/choose-chicago\\\/#breadcrumb\"},\"inLanguage\":\"es\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/trueomni.com\\\/choose-chicago\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"es\",\"@id\":\"https:\\\/\\\/trueomni.com\\\/choose-chicago\\\/#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\\\/choose-chicago\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"http:\\\/\\\/trueomni.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"ChooseChicago\"}]},{\"@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":"ChooseChicago - TrueOmni","description":"Choose Chicago","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\/choose-chicago\/","og_locale":"es_MX","og_type":"article","og_title":"ChooseChicago","og_description":"Choose Chicago","og_url":"https:\/\/trueomni.com\/spanish\/choose-chicago\/","og_site_name":"TrueOmni","article_publisher":"https:\/\/www.facebook.com\/trueomni\/","article_modified_time":"2026-08-17T22:20:50+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\/choose-chicago\/","url":"https:\/\/trueomni.com\/choose-chicago\/","name":"ChooseChicago - TrueOmni","isPartOf":{"@id":"http:\/\/trueomni.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/trueomni.com\/choose-chicago\/#primaryimage"},"image":{"@id":"https:\/\/trueomni.com\/choose-chicago\/#primaryimage"},"thumbnailUrl":"https:\/\/trueomni.com\/wp-content\/uploads\/2026\/05\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png","datePublished":"2026-08-17T22:20:15+00:00","dateModified":"2026-08-17T22:20:50+00:00","description":"Choose Chicago","breadcrumb":{"@id":"https:\/\/trueomni.com\/choose-chicago\/#breadcrumb"},"inLanguage":"es","potentialAction":[{"@type":"ReadAction","target":["https:\/\/trueomni.com\/choose-chicago\/"]}]},{"@type":"ImageObject","inLanguage":"es","@id":"https:\/\/trueomni.com\/choose-chicago\/#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\/choose-chicago\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http:\/\/trueomni.com\/"},{"@type":"ListItem","position":2,"name":"ChooseChicago"}]},{"@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\/58253","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=58253"}],"version-history":[{"count":4,"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/pages\/58253\/revisions"}],"predecessor-version":[{"id":58258,"href":"https:\/\/trueomni.com\/spanish\/wp-json\/wp\/v2\/pages\/58253\/revisions\/58258"}],"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=58253"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}