{"id":58315,"date":"2026-08-20T20:00:07","date_gmt":"2026-08-20T20:00:07","guid":{"rendered":"http:\/\/trueomni.com\/?page_id=58315"},"modified":"2026-08-20T20:01:33","modified_gmt":"2026-08-20T20:01:33","slug":"pennsylvania-tourism-office","status":"publish","type":"page","link":"https:\/\/trueomni.com\/french\/pennsylvania-tourism-office\/","title":{"rendered":"PennsylvaniaTourismOffice"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"58315\" class=\"elementor elementor-58315\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-bf761dd e-con e-atomic-element e-flexbox-base e-default-div e-eaaadfb \" data-id=\"bf761dd\" data-element_type=\"e-flexbox\" data-e-type=\"e-flexbox\" data-interaction-id=\"bf761dd\" data-e-type=\"e-flexbox\" data-id=\"bf761dd\">\n    \t\t<div class=\"elementor-element elementor-element-5627ff1 elementor-widget elementor-widget-html\" data-id=\"5627ff1\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"en\" data-theme=\"light\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\"><link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;500;600;700;800;900&display=swap\" rel=\"stylesheet\">\n<title>TrueImpact \u2014 Executive Dashboard \u00b7 Pennsylvania Tourism Office<\/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 opportunity scenario: 120 kiosks \/ 294 screens, anchored to Pennsylvania's published 2024 visitor-economy data. Illustrative \u2014 no deployment exists.\">MODELED<\/span>\n    <div class=\"live\"><span class=\"dot\"><\/span> PROJECTION \u00b7 pre-deployment<\/div>\n    <div class=\"spacer\"><\/div>\n    <div class=\"controls\">\n      <div class=\"segset\" id=\"periodSel\">\n        <button data-p=\"q\" >Quarter<\/button>\n        <button data-p=\"ytd\">YTD<\/button>\n        <button data-p=\"t12\" class=\"on\">Trailing 12 mo<\/button>\n      <\/div>\n      <button class=\"icon-btn\" id=\"themeBtn\" title=\"Toggle theme\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M21 12.8A9 9 0 1111.2 3a7 7 0 009.8 9.8z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg>\n      <\/button>\n      <button class=\"cta\" onclick=\"window.print()\">\n        <svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M6 9V3h12v6M6 18H4v-6h16v6h-2M8 14h8v7H8z\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg>\n        Executive 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>Pennsylvania Tourism Office \u00b7 figures in USD \u00b7 modeled opportunity scenario anchored to the PA Tourism Office \"Economic Impact of Visitors to Pennsylvania 2024\" (DCED \/ visitPA, Sep-2025) \u00b7 derived &amp; auditable via the TrueOmni attribution stack. Cite under NDA.<\/div>\n<\/footer>\n\n<div class=\"tt\" id=\"tt\"><\/div>\n\n<!-- =================== DATA MODEL =================== -->\n<script id=\"model\">\n\/* ------------------------------------------------------------------\n   TrueImpact data model \u2014 PENNSYLVANIA TOURISM OFFICE.\n   Single-tenant. Currency: US dollars (USD). Every headline number is\n   DERIVED in deriveMetrics() so the math is transparent and can be\n   re-pointed at the live Kiolytics attribution feed at go-live.\n\n   REAL (cited): PA Tourism Office, \"Economic Impact of Visitors to Pennsylvania\n   2024\" (DCED \/ visitPA, Sep-2025) \u2014 201.6M visitors, $49.9B visitor spending,\n   $83.9B total economic impact, 514,261 jobs, $5.0B state & local tax, $10.5B\n   total government revenue, 71M+ overnight, $30.4B overnight spend, $428 avg\n   overnight, $19.5B day-trip, 4.2% international. 2026 outlook: 209.2M visits,\n   $54.8B spending; $50M tourism infrastructure, $10M America250PA, $5M across\n   15 sporting events; FIFA World Cup 26 Philadelphia ~500K visitors \/ ~$770M.\n   MODELED (no discovery call, no deployment \u2014 OPPORTUNITY SCENARIO): the\n   120-kiosk \/ 294-screen network, its 11 zones, phasing, per-zone exposure\n   split, interaction rates, ad economics and capital.\n------------------------------------------------------------------ *\/\nconst FUNNEL = { highIntent:0.17, conversion:0.14 };   \/\/ TrueOmni platform benchmarks\n\n\/* Influenced (indirect + induced) dollars per $1 direct.\n   ANCHORED TO PA'S OWN PUBLISHED RATIO: $83.9B \/ $49.9B = 1.681. *\/\nconst INFL_RATE = 0.681;\n\n\/* Shares of attributed taxable spend that land in each tax base.\n   PA reports $5.0B state+local tax on $49.9B spending = 10.02c per visitor dollar. This schedule lands at 10.60c, within +5.8% of the state's own answer. *\/\nconst LODGING_SHARE = 0.34;\nconst DINING_RETAIL_SHARE = 0.46;\nconst RENTAL_SHARE = 0.08;\nconst RECIRC_MULT = 0.9;      \/\/ local re-spend of each public tax $ (induced, soft)\n\n\/* ---- METHOD DISCLOSURE -------------------------------------------------\n   directSpend = conversions x L.avgSpend, and avgSpend is a visitor's TRIP\n   spend. A converting session is therefore credited with the full trip value\n   it influenced, with NO incrementality discount. Read the headline as\n   \"spend the network touched and directed\", not \"spend that would not\n   otherwise have happened\". Surfaced on the funnel card so nobody has to\n   discover it.                                                          *\/\n\n\/* ---- Tax instruments. Four lines, named for what this jurisdiction\n   actually levies; the render layer reads these labels, so no per-build\n   string surgery is needed.                                            *\/\nconst TAX = {\n  localName:'Local hotel occupancy',  localLong:'County \/ city lodging tax',\n  salesName:'Sales & use tax',  salesLong:'Sales & use tax \u2014 dining, retail & admissions',\n  rentalName:'Vehicle rental tax', rentalLong:'Vehicle rental tax & PTA fund fee',\n  stateName:'PA state hotel tax',  stateLong:'Commonwealth hotel occupancy tax',\n  localBucket:'Local lodging', stateBucket:'Commonwealth', stateBucketLong:'the Commonwealth (sales &amp; use, vehicle rental, and the 6% state hotel occupancy tax)',\n  localShortRate:'local lodging', salesShortRate:'sales &amp; use',\n  stateRate:0.06, stateRateLabel:'PA state hotel occupancy', stateShortRate:'6%',\n  localWhy:'county and city lodging rates differ',\n  localNote:'county \/ city lodging tax \u2014 funds local tourism', salesNote:'6% PA + 2% Philadelphia \/ 1% Allegheny', stateNote:'6% statewide on room revenue',\n  chartSub:'local lodging \u00b7 sales &amp; use \u00b7 vehicle rental \u00b7 PA state hotel \u00b7 click a bar to drill',\n};\n\n\/* Destination context \u2014 the REAL, CITED visitor economy. The TrueImpact\n   number below is the attributed SLICE of this, never a replacement. *\/\nconst DESTCTX = {\n  title:'Pennsylvania\\'s visitor economy \u00b7 the real, published baseline',\n  source:'PA Tourism Office \u00b7 \"Economic Impact of Visitors to Pennsylvania 2024\" (DCED \/ visitPA, Sep-2025)',\n  note:'2026 outlook (Commonwealth \/ DCED): <b>209.2M visits<\/b> and <b>$54.8B<\/b> in projected visitor spending, with $50M committed to tourism infrastructure, $10M to America250PA and $5M across 15 sporting events.',\n  tiles:[\n    {label:'Annual visitors', val:'201.6M', foot:'71M+ overnight \u00b7 4.2% international'},\n    {label:'Visitor spending', val:'$49.9B', foot:'$30.4B overnight \u00b7 $19.5B day-trip'},\n    {label:'Total economic impact', val:'$83.9B', foot:'$0.681 influenced per $1 direct'},\n    {label:'Jobs supported', val:'514,261', foot:'1 in 16 PA jobs (as published) \u00b7 $5.0B state & local tax'},\n  ],\n};\n\n\/* Narrative copy for this destination (read by the AI narrative layer). *\/\nconst COPY = {\n  aiTitle:'TrueImpact AI \u00b7 Legislative-ready summary',\n  aiOpen:'Pennsylvania hosts <b>201.6M visitors<\/b> a year who spend <b>$49.9B<\/b> and generate <b>$83.9B<\/b> in total economic impact \u2014 the question is no longer scale, it is <b>measurement<\/b>.',\n  zoneWord:'statewide deployment zones',\n  taxScale:'For scale: Pennsylvania\\'s published 2024 result was <b>$5.0B<\/b> in state &amp; local tax on <b>$49.9B<\/b> of visitor spending \u2014 10.02\u00a2 per visitor dollar; this rate schedule lands at 10.60\u00a2, within +5.8% of the Commonwealth\\'s own answer.',\n  incrNote:'At a 40% incrementality assumption the headline falls to roughly $68M. A live deployment with holdout zones is what settles it.',\n  trendNote:'Set against the <b>201.6M visitors<\/b> Pennsylvania welcomes each year, this network is modeled to capture ~<b>40M kiosk-zone encounters\/yr<\/b> \u2014 equivalent to about <b>20%<\/b> of that base, though an encounter is a <i>pass<\/i>, not a unique visitor. Seasonality follows PA\\'s summer peak with a distinct October fall-foliage bump and a December holiday-market bump; shoulder-season automation is modeled (not observed) to add 8&ndash;12% in off-peak weeks. 2026 is the outlier year: the Commonwealth projects <b>209.2M visits<\/b> and <b>$54.8B<\/b> in spending behind America250PA, the FIFA World Cup, the MLB All-Star Game, the PGA Championship and the NFL Draft.',\n  rampSub:'projected run-rate as regions come online (18-month statewide rollout)',\n  rampNote:'Cumulative annualized run-rate at each phase. Phase 1 lands in the two anchor metros \u2014 Philadelphia (the America250 \/ FIFA World Cup 26 stage) and Pittsburgh \u2014 where hotel lobbies, transit halls and convention concourses give the fastest proof. Core visitor regions follow, then the rural and outdoor corridors where a kiosk is often the only visitor-information layer. Figures scale with units online \u2014 not a forecast of new demand.',\n  sfNote:'At the mid scenario, advertising covers the combined annual software many times over. <b>Read the payback table above, not a single number:<\/b> the headline figure assumes mature sell-through, and at the Phase-1 fill rate this same model publishes it is roughly twice as long. Payback is calculated on revenue <b>net of<\/b> the annual software fee, so the same dollars are not claimed twice. Modeled from the TrueOmni platform benchmark of $500&ndash;1,500 \/ kiosk \/ month and $200&ndash;800 \/ screen \/ month at mature fill; an agency rate card and an observed fill curve would replace it. Advertising revenue is <b>program revenue<\/b> \u2014 it is never counted in the economic-impact or tax figures. <b>The load-bearing assumption is the impression count:<\/b> kiosks and signage together are credited with roughly 8 ad impressions per person-encounter (attract-loop plus in-session views on one surface, multiple screens passed on the other). At a conservative 2 impressions per encounter the same revenue would imply a $35-70 CPM instead. Impressions, not rates, are where this model is most sensitive.',\n};\n\n\/\/ period scaling (of full-deployment trailing-12 baseline)\nconst PERIODS = { t12:{label:'Trailing 12 months (annualized, full deployment)',k:1}, ytd:{label:'Year to date (2026)',k:0.62}, q:{label:'This quarter (Q3 2026)',k:0.21} };\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 & Parks',  color:'var(--s-attractions)', raw:'#008300'},\n  events:      {name:'Events & Venues',      color:'var(--s-events)',      raw:'#E87BA4'},\n};\n\n\/* seasonal shape, Jan..Dec \u2014 summer peak, October fall-foliage bump, December holiday-market bump *\/\nconst SEASON = { pa:[0.72, 0.7, 0.82, 0.92, 1.04, 1.2, 1.32, 1.28, 1.06, 1.1, 0.88, 0.96] };\nconst MONTHS=['Jul','Aug','Sep','Oct','Nov','Dec','Jan','Feb','Mar','Apr','May','Jun'];\n\n\/* ==================================================================\n   DESTINATION REGISTRY \u2014 single tenant.\n   x,y = map coords in the 480x620 space, placed at real geographic\n   positions. `short` is the explicit chart\/map label: the engine never\n   guess-edits place names.\n   tax = {tot: local lodging %, sales: combined sales %, rental: %}.\n================================================================== *\/\nconst DESTINATIONS = {\n  pa:{ name:'Pennsylvania Tourism Office', kind:'State Tourism Office \u00b7 PA DCED', scope:'Statewide', region:'Commonwealth of Pennsylvania',\n    outline:'M20,438 L20,180 L77,185 L77,212 L409,212 L433,251 L439,287 L427,326 L439,369 L427,423 L405,430 L377,438 Z',\n    lake:'M14,178 L14,130 L152,143 L118,168 L77,185 Z',\n    lakeLabel:{x:62,y:152,t:'Lake Erie'},\n    locations:[\n      {id:'philly', name:'Philadelphia & Independence Mall', short:'Philadelphia', sites:'Independence Mall \u00b7 Reading Terminal \u00b7 Convention Ctr \u00b7 PHL', x:425,y:415, kiosks:22, screens:66, phase:1, exposures:9600000, interactionRate:.42, avgSpend:296, tax:{tot:9.5,sales:8.0,rental:10.0}, season:'pa', mix:{hotels:.34,dining:.26,attractions:.26,events:.14}, note:'The Commonwealth\\'s largest visitor market and the America250 \/ FIFA World Cup 26 stage. Highest density of hotel lobbies, transit halls and museum concourses in PA \u2014 the fastest route to proof.', badge:'Phase 1'},\n      {id:'pitt', name:'Pittsburgh & Allegheny', short:'Pittsburgh', sites:'Downtown \u00b7 Strip District \u00b7 Convention Ctr \u00b7 PIT', x:60,y:367, kiosks:16, screens:44, phase:1, exposures:6200000, interactionRate:.42, avgSpend:268, tax:{tot:7.0,sales:7.0,rental:8.0}, season:'pa', mix:{hotels:.32,dining:.28,attractions:.22,events:.18}, note:'Western gateway; ~$9B in regional tourism spending. Sports, conventions and riverfront culture drive strong shoulder-season demand.', badge:'Phase 1'},\n      {id:'capital', name:'Hershey & Harrisburg', short:'Hershey', sites:'Hersheypark \u00b7 Capitol complex \u00b7 Farm Show Complex', x:295,y:383, kiosks:12, screens:32, phase:2, exposures:4300000, interactionRate:.44, avgSpend:252, tax:{tot:5.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.30,dining:.22,attractions:.38,events:.10}, note:'Capital Region: theme-park families plus the state\\'s largest event complex. Attractions-dominant with captive resort-lobby audiences.'},\n      {id:'dutch', name:'Lancaster & PA Dutch Country', short:'Lancaster', sites:'Lancaster City \u00b7 Bird-in-Hand \u00b7 Strasburg', x:339,y:406, kiosks:12, screens:28, phase:2, exposures:3900000, interactionRate:.40, avgSpend:218, tax:{tot:5.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.26,dining:.28,attractions:.34,events:.12}, note:'High-volume day-trip and short-stay market; agritourism, markets and heritage. The clearest test of day-trip attribution.'},\n      {id:'poconos', name:'Pocono Mountains', short:'Poconos', sites:'Resort corridor \u00b7 Delaware Water Gap \u00b7 Jim Thorpe', x:412,y:296, kiosks:12, screens:26, phase:2, exposures:3700000, interactionRate:.46, avgSpend:336, tax:{tot:3.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.52,dining:.20,attractions:.20,events:.08}, note:'Overnight-heaviest zone in the network: resort lobbies, longest stays, highest per-visitor spend. Lodging-led mix drives the hotel-tax line.'},\n      {id:'lehigh', name:'Lehigh Valley', short:'Lehigh Valley', sites:'Allentown \u00b7 Bethlehem \u00b7 Easton \u00b7 Dorney Park', x:401,y:350, kiosks:10, screens:24, phase:2, exposures:3100000, interactionRate:.40, avgSpend:196, tax:{tot:4.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.24,dining:.28,attractions:.28,events:.20}, note:'Events-and-dining led; SteelStacks, Musikfest and Christmas City drive a pronounced events share and a strong December bump.'},\n      {id:'valleyforge', name:'Valley Forge & Brandywine', short:'Valley Forge', sites:'Valley Forge NHP \u00b7 King of Prussia \u00b7 Brandywine Valley', x:366,y:368, kiosks:10, screens:24, phase:3, exposures:2900000, interactionRate:.43, avgSpend:232, tax:{tot:4.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.34,dining:.28,attractions:.22,events:.16}, note:'Philadelphia countryside: heritage parks plus the largest retail concentration in the region. Strong hotel base with high interaction quality.'},\n      {id:'gettysburg', name:'Gettysburg & Adams County', short:'Gettysburg', sites:'Gettysburg NMP \u00b7 Visitor Center \u00b7 Historic district', x:268,y:427, kiosks:8, screens:16, phase:3, exposures:2200000, interactionRate:.36, avgSpend:178, tax:{tot:3.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.26,dining:.20,attractions:.46,events:.08}, note:'America250 anchor. Outdoor, weather-rated units at battlefield gateways; attractions-dominant with a large day-trip share.'},\n      {id:'erie', name:'Lake Erie & Presque Isle', short:'Lake Erie', sites:'Presque Isle State Park \u00b7 Erie waterfront', x:52,y:199, kiosks:8, screens:16, phase:3, exposures:2000000, interactionRate:.35, avgSpend:204, tax:{tot:7.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.28,dining:.22,attractions:.42,events:.08}, note:'PA\\'s only Great Lakes shoreline and its most-visited state park. Sharply summer-peaked, outdoor-rated hardware.'},\n      {id:'laurel', name:'Laurel Highlands & Ohiopyle', short:'Laurel Hlds', sites:'Ohiopyle \u00b7 Fallingwater \u00b7 Seven Springs', x:112,y:417, kiosks:6, screens:10, phase:3, exposures:1200000, interactionRate:.34, avgSpend:246, tax:{tot:3.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.34,dining:.18,attractions:.42,events:.06}, note:'Outdoor recreation and architecture tourism; welcome-center and trailhead placements where cell coverage is thin and a kiosk is the only wayfinding.'},\n      {id:'wilds', name:'PA Wilds & Route 6', short:'PA Wilds', sites:'Pine Creek Gorge \u00b7 Cherry Springs \u00b7 Route 6 corridor', x:226,y:281, kiosks:4, screens:8, phase:3, exposures:900000, interactionRate:.33, avgSpend:212, tax:{tot:5.0,sales:6.0,rental:8.0}, season:'pa', mix:{hotels:.30,dining:.18,attractions:.46,events:.06}, note:'Rural dark-sky and gorge corridor; the zone where the network does the most work per unit because there is no other visitor-information layer.'},\n    ]},\n};\n\nlet activeDest='pa';\nfunction ACT(){return DESTINATIONS[activeDest];}\nfunction LOCS(){return ACT().locations;}\nfunction findLoc(id){return LOCS().find(l=>l.id===id);}\n\nconst PHASES=[\n  {n:1, name:'Phase 1 \u00b7 Anchor metros', window:'Months 1-3', ids:['philly','pitt']},\n  {n:2, name:'Phase 2 \u00b7 Core visitor regions', window:'Months 4-9', ids:['philly','pitt','capital','dutch','poconos','lehigh']},\n  {n:3, name:'Phase 3 \u00b7 Full statewide network', window:'Months 10-18', ids:['philly','pitt','capital','dutch','poconos','lehigh','valleyforge','gettysburg','erie','laurel','wilds']},\n];\nconst NET_KIOSKS = 120;   \/\/ full-network kiosk count (ramp progress denominator)\n\n\/* ==================================================================\n   MEDIA NETWORK \u2014 interactive-kiosk advertising (Omni Media Network:\n   native direct-sold containers + Google Ad Manager programmatic).\n================================================================== *\/\nconst MEDIA={\n  perKioskMo:{floor:500, mid:950, strong:1500},   \/\/ USD \/ kiosk \/ month at mature fill\n  \/* Applied to ZONE EXPOSURES (people passing a kiosk), not touch sessions:\n     it counts attract-loop views by passersby PLUS in-session container\n     views, which is how kiosk DOOH inventory is measured. Kiosks and signage\n     monetize the same footfall on two surfaces \u2014 standard for a mixed\n     network, and why the two revenue lines are never summed into reach. *\/\n  imprPerEncounter:3.2,\n  fillMature:0.68,\n  fillByPhase:{1:0.35, 2:0.52, 3:0.68},\n  streamSplit:{direct:0.58, programmatic:0.42},\n  softwareYr:62400,          \/\/ annual platform software at full network\n  capital:1440000,            \/\/ one-time kiosk hardware + install\n  inventory:[\n    {id:'video',    name:'Full-screen video (kiosk + video wall)', share:.30},\n    {id:'banner',   name:'Home & footer banners',                  share:.24},\n    {id:'rail',     name:'Events \/ listings side-rail',            share:.18},\n    {id:'sponsored',name:'Sponsored listings & tiles',             share:.16},\n    {id:'qr',       name:'QR-conversion sponsorships',             share:.12},\n  ],\n};\nfunction mediaMetrics(k=1, locs=LOCS()){\n  const r=rollup(k,locs);\n  const kiosks=r.kiosks;\n  const impressions=r.exposures*MEDIA.imprPerEncounter;\n  const band={}; for(const s in MEDIA.perKioskMo) band[s]=kiosks*MEDIA.perKioskMo[s]*12*k;\n  const rev=band.mid;\n  const cpm=impressions? rev\/(impressions\/1000):0;\n  const byStream={direct:rev*MEDIA.streamSplit.direct, programmatic:rev*MEDIA.streamSplit.programmatic};\n  const byInv=MEDIA.inventory.map(it=>({...it, v:rev*it.share}));\n  return {kiosks,impressions,band,rev,cpm,byStream,byInv};\n}\n\n\/* ==================================================================\n   DIGITAL SIGNAGE \u2014 non-interactive broadcast screens (OMNI Signage).\n   Screens deliver REACH and ad inventory; they do NOT transact. Signage\n   impressions are deliberately kept OUT of interactions, attributed\n   visitors, avgSpend and the economic-impact \/ tax totals.\n================================================================== *\/\nconst SIGNAGE={\n  enabled:true,\n  perScreenMo:{floor:200, mid:420, strong:800},\n  loopSec:60, spotSec:15,\n  viewShare:0.85,          \/\/ signage sits in primary sightlines\n  imprPerPasserby:6.0,    \/\/ ~3 screens crossed x ~2 spots each in a 60s\/15s loop\n  dwellSec:8,\n  fillMature:0.62, fillByPhase:{1:0.28,2:0.46,3:0.62},\n  capitalPerScreen:4200, softwarePerScreenYr:600,\n  formats:[\n    {id:'wall',     name:'Video wall \/ lobby hero',        share:.34},\n    {id:'portrait', name:'Portrait posters (corridor)',    share:.28},\n    {id:'wayfind',  name:'Wayfinding & directory boards',  share:.20},\n    {id:'menu',     name:'Menu \/ 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 screens=locs.reduce((s,L)=>s+(L.screens||0),0);\n  const reach=locs.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  return {screens,reach,impressions,band,rev,cpm,spotsPerLoop,shareOfVoice,capital,softwareYr,byFormat};\n}\nfunction totalScreens(locs=LOCS()){return locs.reduce((s,L)=>s+(L.screens||0),0);}\n\n\/* Combined advertising view \u2014 the \"Total Advertising Revenue\" headline.\n   `phase` applies that phase's sell-through to BOTH surfaces.\n   payback is NET OF ANNUAL SOFTWARE: a gross-revenue payback would\n   double-count the software the same revenue is also said to cover. *\/\nfunction adTotals(k=1, locs=LOCS(), phase=null){\n  const km=mediaMetrics(k,locs), sg=signageMetrics(k,locs);\n  const kFill = phase? MEDIA.fillByPhase[phase]\/MEDIA.fillMature : 1;\n  const sFill = phase? SIGNAGE.fillByPhase[phase]\/SIGNAGE.fillMature : 1;\n  const kioskRev=km.rev*kFill, signageRev=(sg?sg.rev:0)*sFill;\n  const rev=kioskRev+signageRev;\n  const impressions=km.impressions+(sg?sg.impressions:0);\n  const capital=MEDIA.capital*(km.kiosks\/NET_KIOSKS)+(sg?sg.capital:0);\n  const softwareYr=MEDIA.softwareYr*(km.kiosks\/NET_KIOSKS)+(sg?sg.softwareYr:0);\n  const net=rev-softwareYr;\n  return {rev,impressions,capital,softwareYr,net,cpm:impressions?rev\/(impressions\/1000):0,\n          kioskRev, signageRev, payback:net>0?capital\/net:Infinity, paybackGross:rev?capital\/rev:0};\n}\n\n\/* ---- derive all metrics for one location at a period scale ---- *\/\nfunction deriveMetrics(L, k=1){\n  const exposures = L.exposures*k;\n  const interactions = exposures*L.interactionRate;\n  const highIntent = interactions*FUNNEL.highIntent;\n  const conversions = highIntent*FUNNEL.conversion;\n  const directSpend = conversions*L.avgSpend;\n  const direct = directSpend;\n  const influenced = directSpend*INFL_RATE;\n  const total = direct+influenced;\n  const r = L.tax;\n  const taxableSpend = directSpend + influenced*0.5;   \/\/ direct + half the influenced re-spend\n  const lodgingBase  = taxableSpend*LODGING_SHARE;\n  const diningBase   = taxableSpend*DINING_RETAIL_SHARE;\n  const rentalBase   = taxableSpend*RENTAL_SHARE;\n  const hotelTOT   = lodgingBase*(r.tot\/100);\n  const salesTax   = diningBase*(r.sales\/100);\n  const rentalTax  = rentalBase*(r.rental\/100);\n  const stateTax   = lodgingBase*TAX.stateRate;\n  const taxLocal   = hotelTOT;\n  const taxState   = salesTax+rentalTax+stateTax;\n  const tax        = taxLocal+taxState;\n  const recirc     = tax*RECIRC_MULT;\n  const taxBreak = {hotelTOT,salesTax,rentalTax,stateTax};\n  const partners = {};\n  for(const s in L.mix){ partners[s] = directSpend*L.mix[s]*1.0 + influenced*L.mix[s]*0.6; }\n  const revAssist = L.kiosks*1500 + (L.kiosks>5?L.kiosks*1100:L.kiosks*700);\n  return {exposures,interactions,highIntent,conversions,directSpend,direct,influenced,total,taxLocal,taxState,tax,taxBreak,recirc,partners,revAssist};\n}\nfunction seriesFor(L,k=1){\n  const m=deriveMetrics(L,k); const sh=SEASON[L.season];\n  const base=m.total\/sh.reduce((a,b)=>a+b,0);\n  const order=[6,7,8,9,10,11,0,1,2,3,4,5];\n  return order.map(i=>base*sh[i]);\n}\nfunction rollup(k=1, locs=LOCS()){\n  const acc={exposures:0,interactions:0,highIntent:0,conversions:0,directSpend:0,direct:0,influenced:0,total:0,taxLocal:0,taxState:0,tax:0,recirc:0,revAssist:0,kiosks:0,\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(m[key]!=null)acc[key]+=m[key];}});\n  return acc;\n}\nfunction rollupSeries(k=1, locs=LOCS()){\n  const t=new Array(12).fill(0);\n  locs.forEach(L=>{const s=seriesFor(L,k);s.forEach((v,i)=>t[i]+=v);});\n  return t;\n}\n\n\/* ---------- network roll-up retained but unused in single-tenant ---------- *\/\nconst NETWORK={ destinations:1, kiosks:NET_KIOSKS, targetTotal:0, targetTax:0, targetActions:0 };\nfunction mergeRollups(list){\n  const acc={exposures:0,interactions:0,highIntent:0,conversions:0,directSpend:0,direct:0,influenced:0,total:0,taxLocal:0,taxState:0,tax:0,recirc:0,revAssist:0,kiosks:0,\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=['pa'];\nfunction destRollup(id,k){return rollup(k, DESTINATIONS[id].locations);}\nfunction networkAgg(k=1){const parts=NET_IDS.map(id=>destRollup(id,k));const real=mergeRollups(parts);const net=scaleRollup(real,1);net.kiosks=real.kiosks;net.destinations=1;return {real,net,others:scaleRollup(real,0),scale:1,parts};}\nfunction networkSeries(k=1){return rollupSeries(k, LOCS());}\n\n\/* ================= ENGAGEMENT DRILL-THROUGH ================= *\/\nconst MODULES=[\n  {id:'map', name:'OMNI Maps', tag:'attractions', share:0.13, dwell:80, intentRel:0.9, convRel:0.8},\n  {id:'itin', name:'OMNI Itinerary', tag:'attractions', share:0.13, dwell:150, intentRel:1.9, convRel:1.6},\n  {id:'trails', name:'Trails, Parks & Outdoors', tag:'attractions', share:0.1, dwell:105, intentRel:1.2, convRel:1.0},\n  {id:'dine', name:'Dining & Restaurants', tag:'dining', share:0.11, dwell:98, intentRel:1.3, convRel:1.2},\n  {id:'deals', name:'Deals & PA Getaway Passes', tag:'events', share:0.1, dwell:120, intentRel:2.1, convRel:1.9},\n  {id:'events', name:'Events Calendar', tag:'events', share:0.1, dwell:86, intentRel:1.1, convRel:1.0},\n  {id:'wayfind', name:'OMNI Wayfinding', tag:'attractions', share:0.09, dwell:70, intentRel:0.7, convRel:0.6},\n  {id:'stay', name:'Lodging & Hotels', tag:'hotels', share:0.07, dwell:110, intentRel:1.7, convRel:1.6},\n  {id:'photo', name:'OMNI PhotoBooth', tag:'events', share:0.06, dwell:130, intentRel:1.2, convRel:1.1},\n  {id:'guest', name:'OMNI GuestBook (lead capture)', tag:'hotels', share:0.05, dwell:44, intentRel:2.3, convRel:2.2},\n  {id:'lang', name:'Multi-language & ADA', tag:'attractions', share:0.04, dwell:40, intentRel:0.5, convRel:0.4},\n  {id:'phone', name:'Mobile Handoff (Send to Phone)', tag:'hotels', share:0.02, dwell:34, intentRel:2.4, convRel:2.3},\n];\nconst CAMP_TYPE={ 'Pass \/ loyalty':'#1C9CD8','Automation':'#1BAF7A','Partner co-op':'#EB6834','Group \/ MICE':'#14508C','Paid media':'#E87BA4','Content':'#008300','Itinerary \/ CRM':'#6B4FB0','Promotions':'#EDA100' };\nconst CAMPAIGNS=[\n  {id:'sponsored', name:'Sponsored Placements (ad partners)', type:'Paid media', share:0.16, intentRel:0.9, convRel:0.8, cpi:0.55},\n  {id:'america250', name:'America250PA', type:'Content', share:0.14, intentRel:1.2, convRel:1.1, cpi:0.65},\n  {id:'getaway', name:'PA Getaway Pass', type:'Pass \/ loyalty', share:0.13, intentRel:1.6, convRel:1.5, cpi:0.9},\n  {id:'worldcup', name:'FIFA World Cup 26 & Fan Zones', type:'Group \/ MICE', share:0.12, intentRel:1.1, convRel:1.3, cpi:0.8},\n  {id:'foliage', name:'Fall Foliage & Scenic Byways', type:'Content', share:0.11, intentRel:1.0, convRel:0.9, cpi:0.7},\n  {id:'trailtaste', name:'Restaurant, Brewery & Farm Trails', type:'Partner co-op', share:0.1, intentRel:1.3, convRel:1.2, cpi:1.1},\n  {id:'itinerary', name:'Plan-Your-Trip Push', type:'Itinerary \/ CRM', share:0.09, intentRel:1.8, convRel:1.6, cpi:1.4},\n  {id:'shoulder', name:'Shoulder-Season Getaway', type:'Automation', share:0.08, intentRel:1.3, convRel:1.4, cpi:0.6},\n  {id:'flash', name:'Flash Deals & Coupons', type:'Promotions', share:0.07, intentRel:1.4, convRel:1.7, cpi:0.95},\n];\nconst AB_TESTS=[\n  {id:'t1', camp:'getaway', name:'PA Getaway Pass \u2014 hero creative', a:'Skyline hero', b:'Family hero', split:.5, bLift:1.16, conf:0.96, status:'B winning'},\n  {id:'t2', camp:'itinerary', name:'Plan-Your-Trip \u2014 CTA copy', a:'\u201cBuild your trip\u201d', b:'\u201cGet my itinerary\u201d', split:.5, bLift:1.12, conf:0.93, status:'B winning'},\n  {id:'t3', camp:'sponsored', name:'Sponsored placement \u2014 layout', a:'Carousel', b:'Full-card', split:.5, bLift:0.94, conf:0.88, status:'A winning'},\n  {id:'t4', camp:'america250', name:'America250PA \u2014 offer framing', a:'\u201cFree admission\u201d', b:'\u201cSites near you\u201d', split:.5, bLift:1.06, conf:0.72, status:'No winner yet'},\n];\nfunction splitFunnel(m, items, segShares){\n  const sSum=items.reduce((a,b)=>a+b.share,0);\n  const rows=items.map(it=>{\n    let w=it.share\/sSum;\n    if(segShares && it.tag){ w*=Math.max(0.35, 1+0.7*((segShares[it.tag]||.25)-0.25)); }\n    return {it, w};\n  });\n  const wSum=rows.reduce((a,b)=>a+b.w,0);\n  rows.forEach(r=>r.w\/=wSum);\n  const hiRaw=rows.map(r=>r.w*r.it.intentRel), hiS=hiRaw.reduce((a,b)=>a+b,0);\n  const cvRaw=rows.map((r,i)=>hiRaw[i]*r.it.convRel), cvS=cvRaw.reduce((a,b)=>a+b,0);\n  return rows.map((r,i)=>{\n    const interactions=m.interactions*r.w;\n    const highIntent=m.highIntent*hiRaw[i]\/hiS;\n    const conversions=m.conversions*cvRaw[i]\/cvS;\n    const exposures=m.exposures*r.w;\n    const impact=m.total*cvRaw[i]\/cvS;\n    const timeHrs=r.it.dwell? interactions*r.it.dwell\/3600 : null;\n    const spend=r.it.cpi!=null? interactions*r.it.cpi : null;\n    return {...r.it, exposures, interactions, highIntent, conversions, impact, timeHrs, spend,\n      convRate:conversions\/interactions, roas:spend?impact\/spend:null, valPerK:impact\/(interactions\/1000)};\n  });\n}\nfunction segSharesOf(m){const t=Object.values(m.partners).reduce((a,b)=>a+b,0)||1;const o={};for(const s in m.partners)o[s]=m.partners[s]\/t;return o;}\n<\/script>\n\n<!-- =================== FORMAT + CHART HELPERS =================== -->\n<script id=\"helpers\">\nconst $=(s,r=document)=>r.querySelector(s);\nconst NS='http:\/\/www.w3.org\/2000\/svg';\nfunction el(t,a={},kids=[]){const e=document.createElementNS(NS,t);for(const k in a)e.setAttribute(k,a[k]);(Array.isArray(kids)?kids:[kids]).forEach(c=>c!=null&&e.appendChild(typeof c==='string'?document.createTextNode(c):c));return e;}\nlet CUR='USD';                 \/\/ US destination \u2014 single currency, no toggle\nconst FX={USD:1};              \/\/ no FX conversion; all figures stored and displayed in USD\nfunction curSym(){return '$';}\nfunction money(v){v=v*FX[CUR];const s=v<0?'-':'';v=Math.abs(v);const c=curSym();\n  if(v>=1e9)return s+c+(v\/1e9).toFixed(v>=1e10?0:2).replace(\/\\.0+$\/,'')+'B';\n  if(v>=1e6)return s+c+(v\/1e6).toFixed(v>=1e7?1:2).replace(\/\\.?0+$\/,'')+'M';\n  if(v>=1e3)return s+c+(v\/1e3).toFixed(0)+'K';\n  return s+c+Math.round(v);}\nfunction money2(v){return curSym()+Math.round(v*FX[CUR]).toLocaleString();}\nfunction num(v){if(v>=1e6)return (v\/1e6).toFixed(v>=1e7?1:2).replace(\/\\.?0+$\/,'')+'M';if(v>=1e3)return (v\/1e3).toFixed(v>=1e4?0:1).replace(\/\\.0$\/,'')+'K';return Math.round(v).toLocaleString();}\nfunction pct(v,d=0){return (v*100).toFixed(d)+'%';}\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,pr=16,pt=14,pb=26;\n  const max=Math.max(...values)*1.12,min=0;\n  \/* Size the left gutter to the widest y tick instead of assuming 42px. *\/\n  let _aw=0; for(let t=0;t<=4;t++) _aw=Math.max(_aw,textW(money(max*t\/4),10,400));\n  const pl=Math.max(42,Math.ceil(_aw)+14);\n  const iw=w-pl-pr,ih=h-pt-pb;\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 lfs=opt.lfs||12, rowH=34,gap=10,pr=opt.pr||64,pt=6;\n  \/* AUTO-SIZED LABEL GUTTER. SVG text cannot wrap or ellipsize, so a fixed\n     gutter clips every long market name. Size it to the longest label, allow\n     two wrapped lines, and cap it at 46% of the chart so the bars stay\n     readable; wrapLabel() ellipsizes anything still too long. *\/\n  let w=opt.w||620;\n  const longest=Math.max(...rows.map(r=>textW(r.label,lfs,600)));\n  \/* +26, not +14: the label is drawn at pl-12 with text-anchor:end, so 12px of\n     the pad is eaten by the right inset. +14 left 2px of real slack. *\/\n  const want=Math.ceil(longest)+26;\n  const cap=Math.floor(w*0.46);\n  let pl=Math.max(opt.pl||120, Math.min(want,cap));\n  if(want>cap){ \/\/ still tight: widen the canvas a little before wrapping kicks in\n    w=Math.min(Math.ceil(w*1.22), w+(want-cap));\n    pl=Math.max(pl,Math.floor(w*0.46));\n  }\n  const labW=pl-12;\n  const wrapped=rows.map(r=>wrapLabel(r.label,labW,lfs,2,600));\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    const ls=wrapped[ri];\n    if(ls.length===1){\n      svg.appendChild(el('text',{x:pl-12,y:y+rowH\/2+4,'text-anchor':'end','font-size':lfs,'font-weight':600,fill:'var(--text)'},ls[0]));\n    }else{\n      const l2=lfs*0.92;\n      svg.appendChild(el('text',{x:pl-12,y:y+rowH\/2-2,'text-anchor':'end','font-size':l2,'font-weight':600,fill:'var(--text)'},ls[0]));\n      svg.appendChild(el('text',{x:pl-12,y:y+rowH\/2+11,'text-anchor':'end','font-size':l2,'font-weight':600,fill:'var(--text)'},ls[1]));\n    }\n\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)'},opt.centerLabel||'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  \/* GEOMETRY, sized from the content instead of hardcoded:\n     - the LEFT gutter must fit the widest y-axis money label, or big markets\n       clip it (a $153.2M tick does not fit in the old 42px);\n     - each column needs a minimum slot for its x-axis label, so the canvas\n       widens with the market count;\n     - past 7 markets the labels rotate; below that they wrap to two lines. *\/\n  const n=rows.length;\n  const rot=n>7;\n  const max=Math.max(...rows.map(r=>r.direct+r.influenced))*1.14;\n  let axisW=0; for(let t=0;t<=4;t++) axisW=Math.max(axisW,textW(money(max*t\/4),10,400));\n  const pl=Math.max(42,Math.ceil(axisW)+14),pr=12,pt=12;\n  const pb=rot?76:48;\n  const h=(opt.h||230)+(rot?32:14);\n  const w=Math.max(opt.w||620, pl+pr+(rot?46:66)*n);\n  const iw=w-pl-pr,ih=h-pt-pb;\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    if(rot){\n      const gg=el('g',{transform:`translate(${cx+4},${h-pb+16}) rotate(-38)`});\n      gg.appendChild(el('title',{},r.label));\n      gg.appendChild(el('text',{'text-anchor':'end','font-size':10,'font-weight':600,fill:'var(--text-2)'},ellip(r.label,pb+30,10,600)));\n      svg.appendChild(gg);\n    }else{\n      const ls=wrapLabel(r.label,slot-4,10,2,600), y0=h-pb+16;\n      ls.forEach((ln,li)=>svg.appendChild(el('text',{x:cx,y:y0+li*11,'text-anchor':'middle','font-size':10,'font-weight':600,fill:'var(--text-2)'},ln)));\n      svg.appendChild(el('text',{x:cx,y:y0+ls.length*11+1,'text-anchor':'middle','font-size':9.5,fill:'var(--muted)'},money(r.direct+r.influenced)));\n    }\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 `${COPY.aiOpen} Across <b>${m.kiosks} kiosks<\/b> and <b>${totalScreens()} screens<\/b> in <b>${locs.length} ${COPY.zoneWord}<\/b>, TrueImpact attributes <span class=\"em\">${money(m.total)}<\/span> of that visitor economy to measured kiosk engagement and <span class=\"em\">${money(m.tax)}<\/span> in estimated state &amp; local tax this period. <b>${best.name}<\/b> leads on impact; <b>${rate.name}<\/b> engages the highest share of visitors at <b>${pct(rate.interactionRate)}<\/b>. This is the attributable <b>slice<\/b>, not a restatement of the whole \u2014 magnitudes are modeled from published figures and TrueOmni platform benchmarks; at go-live every dollar is ledgered to a live kiosk event. An estimated <span class=\"em\">${money(m.recirc)}<\/span> re-circulates through local communities.`;\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:TAX.localLong,salesTax:TAX.salesLong,rentalTax:TAX.rentalLong,stateTax:TAX.stateLong}[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 ${TAX.localWhy}, the same visitor is worth more in <b>${hi.name} (${hi.tax.tot}% ${TAX.localShortRate})<\/b> than in <b>${lo.name} (${lo.tax.tot}%)<\/b>.` : '';\n  return `Attributed visitor spending is projected to generate an estimated <span class=\"em\">${money(m.tax)}<\/span> in tax this period \u2014 <b>${money(m.taxLocal)}<\/b> in ${TAX.localLong.toLowerCase()} that stays with the local destination, and <b>${money(m.taxState)}<\/b> to ${TAX.stateBucketLong}. The single largest line is the <b>${nm}<\/b> at <span class=\"em\">${money(top[1])}<\/span>.${cmp} ${COPY.taxScale} Re-spent through public budgets, the capture drives an estimated <span class=\"em\">${money(m.recirc)}<\/span> of induced local activity \u2014 a soft benefit on top.`;\n}\nfunction aiLocation(L,k){\n  const m=deriveMetrics(L,k),r=L.tax;\n  return `<b>${L.name}<\/b> is projected to turn <b>${num(m.interactions)}<\/b> kiosk interactions into <span class=\"em\">${money(m.total)}<\/span> of economic impact and <span class=\"em\">${money(m.tax)}<\/span> of state &amp; local tax (${TAX.localShortRate} ${r.tot}%, ${TAX.salesShortRate} ${r.sales}%, plus ${TAX.stateRateLabel} ${TAX.stateShortRate}). ${L.note} Its ${L.screens||0} signage screens add broadcast reach on top \u2014 counted as impressions, never as attributed spend. Partners captured <b>${money(Object.values(m.partners).reduce((a,b)=>a+b,0))}<\/b>, led by ${SEG[Object.entries(m.partners).sort((a,b)=>b[1]-a[1])[0][0]].name.toLowerCase()}.`;\n}\nfunction aiNetwork(k){\n  const {net}=networkAgg(k);\n  return `Across the full TrueOmni network \u2014 <b>${net.destinations} destinations<\/b> and <b>${num(net.kiosks)} kiosks<\/b> in North America and Japan \u2014 the platform attributed <span class=\"em\">${money(net.total)}<\/span> of visitor-driven economic impact and <span class=\"em\">${money(net.tax)}<\/span> in estimated public tax revenue this period. This is the first time the destination industry has an aggregate impact number built from <b>ledgered first-party behavioral data<\/b> rather than modeled surveys. Every destination below drills down to its own kiosks.`;\n}\nfunction aiEngagement(rows, mode, scopeName){\n  const attn=[...rows].sort((a,b)=>b.interactions-a.interactions)[0];\n  const val=[...rows].sort((a,b)=>b.impact-a.impact)[0];\n  const invest= mode==='campaigns'\n      ? [...rows].filter(r=>r.roas).sort((a,b)=>b.roas-a.roas)[0]\n      : [...rows].sort((a,b)=>b.valPerK-a.valPerK)[0];\n  const gap = attn.id!==val.id\n      ? ` The gap is the story: <b>${attn.name}<\/b> draws the most attention, but <b>${val.name}<\/b> converts it into the most attributed dollars \u2014 so raw attention isn't the same as value.`\n      : ` <b>${attn.name}<\/b> both draws the most attention and drives the most dollars.`;\n  const investLine = mode==='campaigns'\n      ? ` For spend, <b>${invest.name}<\/b> returns the best ROI at <span class=\"em\">${invest.roas.toFixed(1)}\u00d7<\/span> \u2014 invest here first.`\n      : ` <b>${invest.name}<\/b> punches above its attention (${money(invest.valPerK)} attributed per 1K interactions) \u2014 the efficient place to feature and cross-promote.`;\n  return `In <b>${scopeName}<\/b>, the ${mode==='campaigns'?'campaigns':'kiosk modules'} split the funnel from exposure to attributed conversion.${gap}${investLine} Rank by any funnel stage using the metric toggle; filter by market to see how the mix shifts location to location.`;\n}\nfunction aiPartner(k){\n  const m=rollup(k),tot=Object.values(m.partners).reduce((a,b)=>a+b,0);\n  const top=Object.entries(m.partners).sort((a,b)=>b[1]-a[1])[0];\n  return `TrueImpact attributed <span class=\"em\">${money(tot)}<\/span> of visitor spending to partner businesses \u2014 hotels, restaurants, attractions and event venues \u2014 every dollar tied to a kiosk referral, QR scan, pass pull or partner-POS event. <b>${SEG[top[0]].name}<\/b> captured the most at <span class=\"em\">${money(top[1])}<\/span>. This is the number a partner sees in their own portal, and the reason ${'>'}30% upgrade to premium membership within 18 months.`;\n}\nfunction aiBanner(tag,html){\n  return `<div class=\"ai-banner fade\"><div class=\"ai-ic\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M12 3l1.9 4.6L18 9l-4.1 1.4L12 15l-1.9-4.6L6 9l4.1-1.4zM19 14l.9 2.2L22 17l-2.1.8L19 20l-.9-2.2L16 17l2.1-.8z\" fill=\"#fff\"\/><\/svg><\/div><div><span class=\"ai-tag\">${tag}<\/span><div class=\"ai-txt\">${html}<\/div><\/div><\/div>`;\n}\n<\/script>\n\n<!-- =================== APP \/ ROUTER \/ PAGES =================== -->\n<script id=\"app-logic\">\nlet PERIOD='t12';\nlet engMode='modules', engLoc='all', engMetric='interactions';   \/\/ engagement drill-through filters\nconst app=$('#app');\nfunction K(){return PERIODS[PERIOD].k;}\nfunction periodLabel(){return PERIODS[PERIOD].label;}\n\n\/* ---------- helpers to build DOM cards ---------- *\/\nfunction h(html){const t=document.createElement('template');t.innerHTML=html.trim();return t.content.firstChild;}\nfunction card(title,hint,bodyNodes,cls=''){\n  const c=h(`<div class=\"card ${cls}\"><div class=\"card-h\"><div><h3>${title}${hint?` <span class=\"hint\">${hint}<\/span>`:''}<\/h3><\/div><\/div><\/div>`);\n  (Array.isArray(bodyNodes)?bodyNodes:[bodyNodes]).forEach(n=>n&&c.appendChild(typeof n==='string'?h(n):n));\n  return c;\n}\nfunction legend(items){const l=h('<div class=\"legend\"><\/div>');items.forEach(it=>l.appendChild(h(`<span class=\"li\"><span class=\"sw ${it.line?'line':''}\" style=\"background:${it.color}\"><\/span>${it.name}<\/span>`)));return l;}\n\/* ---- text metrics + label fitting ------------------------------------------\n   SVG <text> does not wrap and does not ellipsize, so any label longer than\n   its gutter is simply clipped. These helpers estimate rendered width (Inter,\n   per-character weights) so callers can size gutters, wrap to N lines, and\n   ellipsize as a last resort instead of losing text off the edge. ---------- *\/\nconst _CW_NARROW=\"iljtI|!.,;:'\u2019()[]\/ \", _CW_WIDE=\"mwMW@\", _CW_MED=\"fr1\";\n\/* Advance widths calibrated against Inter and the Arial fallback rather than\n   guessed: lowercase averages ~0.556em, not 0.535, and SEMIBOLD\/BOLD text runs\n   ~6% wider than regular. The earlier version ignored weight entirely and\n   under-measured every 600\/700 chart label by 5-10% \u2014 which is exactly how a\n   \"fitted\" gutter still clipped. Callers MUST pass the weight they render at. *\/\nfunction textW(s,fs,wt){let w=0;for(const c of String(s)){\n  w += _CW_NARROW.includes(c)?0.31 : _CW_WIDE.includes(c)?0.95 : _CW_MED.includes(c)?0.42\n     : (c>='A'&&c<='Z')?0.70 : (c>='0'&&c<='9')?0.58 : 0.556;}\n  return w*fs*(((wt||400)>=600)?1.06:1.0);}\nfunction ellip(s,maxW,fs,wt){let t=String(s);\n  if(textW(t,fs,wt)<=maxW) return t;\n  while(t.length>1 && textW(t+'\u2026',fs,wt)>maxW) t=t.slice(0,-1);\n  return t.replace(\/[\\s,&\u00b7-]+$\/,'')+'\u2026';}\nfunction wrapLabel(s,maxW,fs,maxLines,wt){\n  maxLines=maxLines||2;\n  const words=String(s).split(\/\\s+\/), lines=[]; let cur='';\n  for(let i=0;i<words.length;i++){\n    const t=cur?cur+' '+words[i]:words[i];\n    if(textW(t,fs,wt)<=maxW || !cur){ cur=t; }\n    else{\n      lines.push(cur);\n      if(lines.length===maxLines-1){ cur=words.slice(i).join(' '); break; }\n      cur=words[i];\n    }\n  }\n  lines.push(cur);\n  lines[lines.length-1]=ellip(lines[lines.length-1],maxW,fs,wt);\n  return lines;\n}\n\/* De-confliction for free-floating map labels. Tries a handful of positions\n   HUGGING the pin (below, above, then one and two steps out) and takes the\n   first that is both free and inside the canvas. A naive \"keep pushing down\"\n   loop cascades clustered pins right off the bottom of the map, so if nothing\n   is free we accept the natural position rather than fly away. *\/\nfunction placeLabel(boxes,l,r,below,above,lh,maxY,vw){\n  const w=r-l, ys=[below,above,below+lh+2,above-lh-2,below+2*(lh+2),above-2*(lh+2)];\n  const dxs=[0,-w*0.55,w*0.55];   \/\/ also try nudging sideways before giving up\n  for(const dx of dxs){\n    let L=l+dx, R=r+dx;\n    if(vw!=null){ if(L<2){R+=2-L;L=2;} if(R>vw-2){L-=R-(vw-2);R=vw-2;} }\n    for(const yy of ys){\n      if(yy-lh<4 || yy+4>maxY) continue;\n      const hit=boxes.some(b=>!(R<b.l-2||L>b.r+2||yy+lh*0.85<b.y-b.lh*0.85||yy-lh*0.85>b.y+b.lh*0.85));\n      if(!hit){ boxes.push({l:L,r:R,y:yy,lh}); return {y:yy,dx:(L+R)\/2-(l+r)\/2}; }\n    }\n  }\n  boxes.push({l,r,y:below,lh});\n  return {y:below,dx:0};\n}\n\/* Prefer an explicit short label on the zone. Never guess-edit place names:\n   the old implementation stripped \" Lake\"\/\" Valley\"\/\" Bay\"\/\" Springs\", which\n   turns \"Great Salt Lake\" into \"Great Salt\". *\/\nfunction shortName(n,L){\n  if(L&&L.short) return L.short;\n  let s=String(n).split(' (')[0].split(' \u00b7 ')[0].trim();\n  if(s.length>16){ const amp=s.split(\/\\s+&\\s+\/); if(amp[0].length>=6) s=amp[0]; }\n  return s.length<=18? s : ellip(s,18*0.55*10,10);\n}\nfunction engShort(n){return n.replace(\/\\s*\\(.*\\)\/,'').replace('Interactive Map & Wayfinding','Map & Wayfinding').replace(' Placements','').replace('Local ','').replace('-Season Getaway','-Season').replace(' & Coupons','').replace(' Push','').replace(' Welcome','').replace(' & Restaurants','').replace(' to Do & Attractions',' to Do').replace(' Builder','').replace(' Calendar','').replace(' & Parking','');}\nfunction setDest(id){activeDest=id;engLoc='all';location.hash='#\/overview';render();}\nfunction engMiniCard(k){\n  const sc=engScope(k); const rows=splitFunnel(sc.m, MODULES, sc.seg).sort((a,b)=>b.interactions-a.interactions).slice(0,5);\n  const max=rows[0].interactions;\n  const c=card('Module &amp; campaign engagement','what draws attention \u2014 drill the funnel','',' ');\n  const list=h('<div class=\"loclist\"><\/div>');\n  rows.forEach(r=>{const row=h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div><div class=\"lr-name\" style=\"display:flex;align-items:center;gap:7px\"><span class=\"sw\" style=\"width:11px;height:11px;border-radius:3px;background:${SEG[r.tag].color}\"><\/span>${r.name}<\/div><\/div><div class=\"lr-val\">${num(r.interactions)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(r.impact)}<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(r.interactions\/max*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    row.addEventListener('click',()=>go('#\/modules'));list.appendChild(row);});\n  c.appendChild(list);\n  const btns=h('<div style=\"display:flex;gap:8px;margin-top:12px\"><\/div>');\n  btns.appendChild(h(`<button class=\"chip\" style=\"cursor:pointer\" onclick=\"go('#\/modules')\">Modules \u203a<\/button>`));\n  btns.appendChild(h(`<button class=\"chip\" style=\"cursor:pointer\" onclick=\"go('#\/campaigns')\">Campaigns &amp; A\/B tests \u203a<\/button>`));\n  c.appendChild(btns);\n  return c;\n}\n\n\/* ================= OVERVIEW ================= *\/\nfunction pageOverview(){\n  const k=K(),m=rollup(k);\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">${ACT().kind} \u00b7 Executive Dashboard<\/div><h1>${ACT().name} \u2014 ${ACT().scope} Impact<\/h1><\/div><div class=\"meta\">Reporting period<br><b style=\"color:var(--text);font-size:14px\">${periodLabel()}<\/b><br>${m.kiosks} kiosks \u00b7 ${totalScreens()} screens \u00b7 ${LOCS().length} zones<\/div><\/div>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>${ACT().name}<\/span><span class=\"sep\">\u203a<\/span><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>All zones<\/span><\/div>`));\n  wrap.appendChild(destContextStrip());\n  wrap.appendChild(h(aiBanner(COPY.aiTitle,aiOverview(k))));\n  wrap.appendChild(rampCard(k)); wrap.lastChild.style.marginBottom='16px';\n\n  \/\/ KPI band\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Total economic impact<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(m.total).replace(curSym(),'')}<\/div><div class=\"k-delta up\">\u25b2 Phase 3 \u00b7 full-network run-rate<\/div><div class=\"k-foot\">Direct ${money(m.direct)} + Influenced ${money(m.influenced)} \u00b7 dollars-first, ledger-level<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Estimated tax revenue',money(m.tax),'state &amp; local, at full deployment',`${TAX.localBucket} ${money(m.taxLocal)} \u00b7 ${TAX.stateBucket} ${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 partner segments','Hotels \u00b7 dining \u00b7 attractions \u00b7 events',()=>go('#\/partners')));\n  kpis.appendChild(kpiTile('Trackable visitor actions',num(m.interactions),'ledgered kiosk events',`${num(m.conversions)} attributed conversions`));\n  wrap.appendChild(kpis);\n\n  \/\/ Map + location list\n  const mapRow=h('<div class=\"grid row-map\" style=\"margin-bottom:16px\"><\/div>');\n  mapRow.appendChild(card('Impact by kiosk location','click a market to drill in',[buildMap(k),legend([{name:'Bubble size = economic impact',color:'var(--azure)'}])],'pad0'+' '));\n  const mapCard=mapRow.lastChild;mapCard.style.padding='18px';\n  mapRow.appendChild(card('Zones ranked','by economic impact',[buildLocList(k)]));\n  wrap.appendChild(mapRow);\n\n  \/\/ Funnel + Direct\/influenced by location\n  const r2=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  r2.appendChild(card('The visitor funnel',ACT().scope.toLowerCase(),[buildFunnel(m),h(`<div class=\"note\">Exposure \u2192 interaction \u2192 high-intent action \u2192 attributed conversion. Rates are TrueOmni platform benchmarks; volumes are ledgered kiosk events. <b>Method disclosure:<\/b> a converting session is credited with the full trip spend it influenced, with <b>no incrementality discount<\/b> \u2014 read it as spend the network <i>touched and directed<\/i>, not spend that would not otherwise have occurred. ${COPY.incrNote}<\/div>`)]));\n  const dcols=stackedCols(LOCS().map(L=>{const mm=deriveMetrics(L,k);return{label:shortName(L.name,L),direct:mm.direct,influenced:mm.influenced,onClick:()=>go('#\/location\/'+L.id)};}));\n  r2.appendChild(card('Direct vs influenced impact','by market \u00b7 click to drill',[dcols,legend([{name:'Direct (attributed spend)',color:'var(--s-direct)'},{name:'Influenced (supply-chain + wage tail)',color:'var(--s-influenced)'}])]));\n  wrap.appendChild(r2);\n\n  \/\/ Partner mix + tax preview\n  const r3=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  r3.appendChild(buildPartnerCard(m,k));\n  r3.appendChild(buildTaxPreview(m,k));\n  wrap.appendChild(r3);\n\n  \/\/ Engagement entry\n  wrap.appendChild(engMiniCard(k));\n  wrap.lastChild.style.marginBottom='16px';\n\n  \/\/ Media Network entry\n  wrap.appendChild(mediaMiniCard(k));\n  wrap.lastChild.style.marginBottom='16px';\n\n  \/\/ Trend\n  const trend=card('Economic impact trend','monthly \u00b7 '+periodLabel(),[areaChart(rollupSeries(k),MONTHS,{h:220}),h(`<div class=\"note\">${COPY.trendNote}<\/div>`)]);\n  wrap.appendChild(trend);\n  return wrap;\n}\n\/* ---- Destination context: the REAL, CITED visitor economy, shown FIRST so\n   the reader sees the whole economy before the attributed slice. ---- *\/\nfunction destContextStrip(){\n  const c=card(DESTCTX.title,'reported figures \u2014 not modeled \u00b7 the denominator for everything below','',' ');\n  const g=h('<div class=\"grid kpis\" style=\"margin-top:2px\"><\/div>');\n  DESTCTX.tiles.forEach(t=>g.appendChild(h(`<div class=\"card kpi\" style=\"box-shadow:none;border-style:dashed\"><div class=\"k-label\">${t.label}<\/div><div class=\"k-val\">${t.val}<\/div><div class=\"k-foot\">${t.foot}<\/div><\/div>`)));\n  c.appendChild(g);\n  c.appendChild(h(`<div class=\"note\"><b>Source:<\/b> ${DESTCTX.source}. These figures are <b>reported, not modeled<\/b>. Everything below is the <b>attributable slice<\/b> of this economy that a measured kiosk and signage network can evidence \u2014 a small, defensible fraction, never a restatement of the total. ${DESTCTX.note}<\/div>`));\n  c.style.marginBottom='16px';\n  return c;\n}\nfunction rampCard(k){\n  const c=card(`Deployment ramp \u00b7 ${PHASES[0].ids.reduce((s,i)=>s+findLoc(i).kiosks,0)} \u2192 ${NET_KIOSKS} kiosks \u00b7 ${totalScreens()} screens`,COPY.rampSub,'',' ');\n  const list=h('<div class=\"loclist\"><\/div>');\n  let prevK=0;\n  PHASES.forEach(ph=>{\n    const locs=LOCS().filter(L=>ph.ids.includes(L.id));\n    const m=rollup(k,locs); const add=m.kiosks-prevK; prevK=m.kiosks;\n    const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${ph.name}<\/div><div class=\"lr-sub\">${ph.window} \u00b7 ${m.kiosks} kiosks + ${totalScreens(locs)} screens live${add?` (+${add} kiosks)`:''}<\/div><\/div><div class=\"lr-val\">${money(m.total)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(m.tax)} tax \u00b7 ${money(m.taxLocal)} ${TAX.localBucket.toLowerCase()}<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(m.kiosks\/NET_KIOSKS*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    list.appendChild(row);\n  });\n  c.appendChild(list);\n  c.appendChild(h(`<div class=\"note\">${COPY.rampNote}<\/div>`));\n  return c;\n}\nfunction kpiTile(label,val,delta,foot,onClick){\n  const c=h(`<div class=\"card kpi\"${onClick?' style=\"cursor:pointer\"':''}><div class=\"k-label\">${label}<\/div><div class=\"k-val\">${val}<\/div><div class=\"k-delta up\">\u25b2 ${delta.replace('\u25b2','').trim()}<\/div><div class=\"k-foot\">${foot}<\/div><\/div>`);\n  if(onClick)c.addEventListener('click',onClick);\n  return c;\n}\n\n\/* ---- Destination map (pixel-scale viewBox so marks\/labels stay true size).\n   Reads the active destination's outline + each location's x,y. Works for any tenant. ---- *\/\nfunction buildMap(k){\n  const D=ACT(), locs=LOCS();\n  const svg=el('svg',{viewBox:'0 0 480 620',role:'img','aria-label':D.region+' kiosk map'});\n  if(D.lake) svg.appendChild(el('path',{d:D.lake,fill:'var(--s-hotels)','fill-opacity':.12,stroke:'none'}));\n  svg.appendChild(el('path',{d:D.outline,class:'ca-outline'}));\n  if(D.lakeLabel) svg.appendChild(el('text',{x:D.lakeLabel.x,y:D.lakeLabel.y,'text-anchor':'middle','font-size':12,'font-style':'italic',fill:'var(--muted)',stroke:'none'},D.lakeLabel.t));\n  const max=Math.max(...locs.map(L=>deriveMetrics(L,k).total));\n  const _mapLabels=[];   \/\/ placed label boxes, for de-confliction\n  \/* Pass 1: reserve every in-pin value label. One pass would let an early zone\n     name land exactly where a later pin's value is about to be drawn. *\/\n  locs.forEach(L=>{const _m=deriveMetrics(L,k), _r=9+18*Math.sqrt(_m.total\/max);\n    if(_r>16){const _w=textW(money(_m.total),11,400)\/2; _mapLabels.push({l:L.x-_w,r:L.x+_w,y:L.y+4,lh:11});}});\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    {const lb=shortName(L.name,L), lw=textW(lb,11.5,700);\n     const lo=4+lw\/2-px, hi=476-lw\/2-px;\n     const dx=(lo>hi)?(240-px):Math.min(Math.max(0,lo),hi);\n     const pos=placeLabel(_mapLabels, px+dx-lw\/2, px+dx+lw\/2, py+rr+15, py-rr-7, 12, 612, 480);\n     g.appendChild(el('title',{},L.name));\n     g.appendChild(el('text',{x:dx+pos.dx,y:pos.y-py,'text-anchor':'middle','font-size':11.5,'font-weight':700},lb));}\n    g.addEventListener('mousemove',ev=>showTT(`<div class=\"tt-t\">${L.name}<\/div><div class=\"tt-r\">Impact <b>${money(mm.total)}<\/b><\/div><div class=\"tt-r\">Tax <b>${money(mm.tax)}<\/b><\/div><div class=\"tt-r\">Interaction <b>${pct(L.interactionRate)}<\/b><\/div>`,ev));\n    g.addEventListener('mouseleave',hideTT);\n    g.addEventListener('click',()=>go('#\/location\/'+L.id));\n    svg.appendChild(g);\n  });\n  const box=h('<div class=\"mapwrap\"><\/div>');box.appendChild(svg);return box;\n}\nfunction buildLocList(k){\n  const rows=[...LOCS()].map(L=>({L,m:deriveMetrics(L,k)})).sort((a,b)=>b.m.total-a.m.total);\n  const max=rows[0].m.total;\n  const box=h('<div class=\"loclist\"><\/div>');\n  rows.forEach(({L,m})=>{\n    const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${L.name} ${L.badge?`<span class=\"pill tag-hi\">${L.badge}<\/span>`:''}<\/div><div class=\"lr-sub\">${L.sites} \u00b7 ${L.kiosks} kiosks \u00b7 ${L.screens||0} screens<\/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:TAX.localName,color:'var(--s-local)'},\n    {v:b.salesTax,name:TAX.salesName,color:'#33abe0'},\n    {v:b.rentalTax,name:TAX.rentalName,color:'#7cc9e8'},\n    {v:b.stateTax,name:TAX.stateName,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,{w:700}));\n  c.appendChild(h(`<div class=\"gauge-track\"><i style=\"width:${(m.recirc\/(m.recirc+m.tax)*100).toFixed(0)}%\"><\/i><\/div>`));\n  c.appendChild(h(`<div class=\"note\"><b style=\"color:var(--text)\">${money(m.recirc)}<\/b> soft benefit \u2014 estimated local re-circulation of these public tax dollars through community budgets & payroll. <span class=\"toggle-tbl\" onclick=\"go('#\/tax')\">Full tax breakdown \u203a<\/span><\/div>`));\n  return c;\n}\n\n\/* ================= LOCATION DETAIL ================= *\/\nfunction pageLocation(id){\n  const L=findLoc(id);if(!L)return pageOverview();\n  const k=K(),m=deriveMetrics(L,k),r=L.tax;\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> All ${ACT().region} markets<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>${ACT().name}<\/span><span class=\"sep\">\u203a<\/span><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>${L.name}<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">Kiosk market${L.badge?` \u00b7 <span style=\"color:var(--good)\">${L.badge}<\/span>`:''}<\/div><h1>${L.name}<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">${L.sites} \u00b7 ${L.kiosks} kiosks \u00b7 ${periodLabel()}<\/div><\/div><div class=\"meta\">Tax rates<br><b style=\"color:var(--text);font-size:13px\">${TAX.localShortRate} ${r.tot}% \u00b7 ${TAX.salesShortRate} ${r.sales}%<br>+ ${TAX.stateRateLabel} ${TAX.stateShortRate}<\/b><\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Market read',aiLocation(L,k))));\n\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Economic impact<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(m.total).replace(curSym(),'')}<\/div><div class=\"k-foot\">Direct ${money(m.direct)} + Influenced ${money(m.influenced)}<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Estimated tax revenue',money(m.tax),'',`${TAX.localBucket} ${money(m.taxLocal)} \u00b7 ${TAX.stateBucket} ${money(m.taxState)}`,()=>go('#\/tax')));\n  kpis.appendChild(kpiTile('Partner-captured spend',money(Object.values(m.partners).reduce((a,b)=>a+b,0)),'','across 4 segments',()=>go('#\/partners')));\n  kpis.appendChild(kpiTile('Interaction rate',pct(L.interactionRate),'',`${num(m.interactions)} interactions`));\n  wrap.appendChild(kpis);\n\n  const r2=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  r2.appendChild(card('Visitor funnel','this market',[buildFunnel(m)]));\n  r2.appendChild(buildLocTaxCard(m,L));\n  wrap.appendChild(r2);\n\n  const r3=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  const parts=Object.entries(m.partners).map(([pid,v])=>({v,name:SEG[pid].name,color:SEG[pid].color}));\n  const pc=card('Partner impact \u2014 '+L.name,'who captured the visitor dollar','',' ');\n  const pin=h('<div style=\"display:flex;gap:20px;align-items:center;flex-wrap:wrap\"><\/div>');\n  pin.appendChild(donut(parts));\n  const pl=h('<div style=\"flex:1;min-width:200px\"><\/div>');\n  parts.sort((a,b)=>b.v-a.v).forEach(p=>pl.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\" style=\"display:flex;align-items:center;gap:7px\"><span class=\"sw\" style=\"width:11px;height:11px;border-radius:3px;background:${p.color}\"><\/span>${p.name}<\/div><div class=\"lr-val\">${money(p.v)}<\/div><\/div>`)));\n  pin.appendChild(pl);pc.appendChild(pin);r3.appendChild(pc);\n  r3.appendChild(card('Monthly impact','seasonality',[areaChart(seriesFor(L,k),MONTHS,{h:210})]));\n  wrap.appendChild(r3);\n\n  \/\/ sample named partners\n  wrap.appendChild(buildSamplePartners(L,m));\n  return wrap;\n}\nfunction buildLocTaxCard(m,L){\n  const b=m.taxBreak,r=L.tax;\n  const rows=[\n    {label:`${TAX.localName} (${r.tot}%)`,parts:[{v:b.hotelTOT,color:'var(--s-local)',name:TAX.localLong}],total:b.hotelTOT},\n    {label:`${TAX.salesName} (${r.sales}%)`,parts:[{v:b.salesTax,color:'#33abe0',name:TAX.salesLong}],total:b.salesTax},\n    {label:`${TAX.rentalName} (${r.rental}%)`,parts:[{v:b.rentalTax,color:'#7cc9e8',name:TAX.rentalLong}],total:b.rentalTax},\n    {label:`${TAX.stateName} (${TAX.stateShortRate})`,parts:[{v:b.stateTax,color:'var(--s-state)',name:TAX.stateLong}],total:b.stateTax},\n  ];\n  const c=card('Estimated tax revenue','locality-based','',' ');\n  c.appendChild(rankedStacked(rows,{w:700}));\n  c.appendChild(h(`<div class=\"note\">Rates are set by this jurisdiction \u2014 which is why a visitor here is worth a different tax yield than in any other market. <b style=\"color:var(--text)\">${money(m.recirc)}<\/b> estimated re-circulation (soft benefit).<\/div>`));\n  return c;\n}\nfunction buildSamplePartners(L,m){\n  const SAMPLES={\n    la:['JW Marriott L.A. LIVE','Grand Central Market','Griffith Observatory','Crypto.com Arena'],\n    sf:['Hotel Nikko','Ferry Building Marketplace','Alcatraz Cruises','Moscone Center'],\n    sd:['Hotel del Coronado','Little Italy Food Hall','San Diego Zoo','Petco Park'],\n    ana:['Anaheim Marriott','Downtown Disney Dining','Disneyland Resort','Angel Stadium'],\n    bb:['The Lodge at Big Bear','Peppercorn Grille','Big Bear Mountain Resort','Village Events'],\n    napa:['Carneros Resort','The French Laundry','Castello di Amorosa','Oxbow Public Market'],\n    ps:['Parker Palm Springs','Workshop Kitchen','Palm Springs Tram','Coachella \/ Stagecoach'],\n    sac:['Citizen Hotel','Old Sacramento Waterfront','California State Railroad Museum','Golden 1 Center'],\n    ord:['Hilton O\u2019Hare','Tortas Frontera','Terminal 5 Retail','United Club'],\n    mag:['The Peninsula Chicago','Purple Pig','360 CHICAGO','Water Tower Place'],\n    navy:['Sable at Navy Pier','Giordano\u2019s','Centennial Wheel','Navy Pier Events'],\n    mcc:['Marriott Marquis Chicago','Wintrust Cafe','Field Museum','McCormick Expo Halls'],\n    loop:['JW Marriott Chicago','The Berghoff','Skydeck Willis Tower','Chicago Theatre'],\n    mus:['Hilton Museum Campus','Shedd Cafe','Shedd Aquarium','Adler After Dark'],\n    wrig:['Hotel Zachary','Gallagher Way Dining','Wrigley Field Tours','Cubs Home Games'],\n    orl:['Hilton Bonnet Creek','Disney Springs Dining','Universal Orlando','OCCC Events'],\n    mia:['Fontainebleau Miami','Joe\u2019s Stone Crab','Vizcaya Museum','Ultra \/ Art Basel'],\n    tpa:['Tampa Marriott Water St','Columbia Restaurant','Busch Gardens','Amalie Arena'],\n    ftl:['W Fort Lauderdale','Casa Sensei','Riverwalk Cruises','Broward Convention'],\n    jax:['Marriott Downtown JAX','Cowford Chophouse','MOSH Museum','VyStar Arena'],\n    nap:['Ritz-Carlton Naples','Campiello','Naples Botanical Garden','5th Ave Events'],\n    key:['Casa Marina Resort','Blue Heaven','Mallory Square Sunset','Fantasy Fest'],\n  };\n  const names=SAMPLES[L.id]||['Hotel','Restaurant','Attraction','Venue'];\n  const segIds=['hotels','dining','attractions','events'];\n  const c=card('Sample attributed partners','illustrative \u2014 top referral recipients','',' ');\n  const tbl=h('<table><thead><tr><th>Partner<\/th><th>Segment<\/th><th>Attributed spend<\/th><th>Referrals<\/th><th>Trend<\/th><\/tr><\/thead><tbody><\/tbody><\/table>');\n  const tb=tbl.querySelector('tbody');\n  segIds.forEach((sid,i)=>{\n    const v=m.partners[sid]*(0.34-i*0.03);\n    const refs=Math.round(m.highIntent*L.mix[sid]*0.4);\n    tb.appendChild(h(`<tr><td><b>${names[i]}<\/b><\/td><td><span class=\"pill tag-mid\">${SEG[sid].name}<\/span><\/td><td class=\"tnum\">${money2(v)}<\/td><td class=\"tnum\">${refs.toLocaleString()}<\/td><td><span class=\"pill tag-hi\">\u25b2 ${(8+i*3)}%<\/span><\/td><\/tr>`));\n  });\n  c.appendChild(tbl);\n  return c;\n}\n\n\/* ================= PARTNER DETAIL ================= *\/\nfunction pagePartners(){\n  const k=K(),m=rollup(k);const tot=Object.values(m.partners).reduce((a,b)=>a+b,0);\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>Partner economic impact<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">${ACT().region} \u00b7 Partner economic impact<\/div><h1>What the visitor dollar did for partners<\/h1><\/div><div class=\"meta\">${periodLabel()}<br><b style=\"color:var(--text)\">${money(tot)}<\/b> attributed<\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Partner read',aiPartner(k))));\n\n  \/\/ segment cards\n  const icons={hotels:'M3 21V9l9-6 9 6v12M9 21v-6h6v6',dining:'M6 3v7m0 0a3 3 0 006 0V3M6 10v11M18 3c-2 0-3 2-3 5s1 4 3 4v9',attractions:'M3 21l9-18 9 18M8 21v-5h8v5',events:'M3 8h18M3 8l2-4h14l2 4M5 8v12h14V8'};\n  const cards=h('<div class=\"seg-cards\" style=\"margin-bottom:16px\"><\/div>');\n  Object.entries(m.partners).sort((a,b)=>b[1]-a[1]).forEach(([id,v])=>{\n    const c=h(`<div class=\"seg-card\"><div class=\"sc-top\"><span class=\"sc-ic\" style=\"background:${SEG[id].color}\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"${icons[id]}\" stroke=\"#fff\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg><\/span>${SEG[id].name}<\/div><div class=\"sc-val\">${money(v)}<\/div><div class=\"sc-sub\">${pct(v\/tot,0)} of partner impact<\/div><div class=\"sc-count\"><span>Est. partners<\/span><span>${Math.round(400*SEG_SHARE(id))}<\/span><\/div><\/div>`);\n    cards.appendChild(c);\n  });\n  wrap.appendChild(cards);\n\n  \/\/ impact by segment across locations (stacked ranked)\n  const rows=[...LOCS()].map(L=>{const mm=deriveMetrics(L,k);const p=mm.partners;const t=Object.values(p).reduce((a,b)=>a+b,0);\n    return {label:L.name,total:t,onClick:()=>go('#\/location\/'+L.id),parts:['hotels','dining','attractions','events'].map(s=>({v:p[s],color:SEG[s].color,name:SEG[s].name}))};\n  }).sort((a,b)=>b.total-a.total);\n  const c1=card('Partner impact by market','stacked by segment \u00b7 click a bar to open the market','',' ');\n  c1.appendChild(rankedStacked(rows,{w:740}));\n  c1.appendChild(legend(Object.keys(SEG).map(id=>({name:SEG[id].name,color:SEG[id].color}))));\n  wrap.appendChild(c1);\n\n  \/\/ why it matters\n  wrap.appendChild(card('Why partners upgrade','the portal effect',[h(`<div class=\"stat-inline\" style=\"margin-top:6px\"><div class=\"si\"><b>+15\u201330%<\/b><span>itinerary inclusion lift<\/span><\/div><div class=\"si\"><b>+20\u201340%<\/b><span>listing exposure<\/span><\/div><div class=\"si\"><b>+10\u201325%<\/b><span>pass participation<\/span><\/div><div class=\"si\"><b>&gt;30%<\/b><span>upgrade to premium in 18 mo<\/span><\/div><\/div>`),h('<div class=\"note\">The Partner ROI view is the single most-requested feature in the 2026 customer survey. When a restaurant or hotel sees its own attributed dollars, flat-dues membership becomes performance-aligned membership.<\/div>')]));\n  return wrap;\n}\nfunction SEG_SHARE(id){return {hotels:.34,dining:.30,attractions:.22,events:.14}[id];}\n\n\/* ================= TAX DETAIL ================= *\/\nfunction pageTax(){\n  const k=K(),m=rollup(k),b=m.taxBreak;\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><a onclick=\"go('#\/overview')\">${ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>Estimated tax revenue<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">${ACT().region} \u00b7 Public revenue impact<\/div><h1>Estimated tax revenue by zone<\/h1><\/div><div class=\"meta\">${periodLabel()}<br><b style=\"color:var(--text)\">${money(m.tax)}<\/b> total est.<\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Tax read',aiTax(k))));\n\n  \/\/ KPI tiles by tax type\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Total estimated tax revenue<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(m.tax).replace(curSym(),'')}<\/div><div class=\"k-foot\">${TAX.localBucket} ${money(m.taxLocal)} + ${TAX.stateBucket} ${money(m.taxState)}<\/div><\/div>`));\n  kpis.appendChild(kpiTile(TAX.localName,money(b.hotelTOT),'',TAX.localNote));\n  kpis.appendChild(kpiTile(TAX.salesName,money(b.salesTax),'',TAX.salesNote));\n  kpis.appendChild(kpiTile(TAX.stateName,money(b.stateTax),'',TAX.stateNote));\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:TAX.localName},\n      {v:bb.salesTax,color:'#33abe0',name:TAX.salesName},\n      {v:bb.rentalTax,color:'#7cc9e8',name:TAX.rentalName},\n      {v:bb.stateTax,color:'var(--s-state)',name:TAX.stateName},\n    ]};}).sort((a,b)=>b.total-a.total);\n  const c1=card('Estimated tax revenue by zone',TAX.chartSub,'',' ');\n  c1.appendChild(rankedStacked(rows,{w:760}));\n  c1.appendChild(legend([{name:TAX.localName,color:'var(--s-local)'},{name:TAX.salesName,color:'#33abe0'},{name:TAX.rentalName,color:'#7cc9e8'},{name:TAX.stateName,color:'var(--s-state)'}]));\n  wrap.appendChild(c1);\n\n  \/\/ recirculation soft benefit\n  const recircCard=card('Recirculation \u2014 the soft benefit','induced community activity from public tax dollars','',' ');\n  recircCard.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:4px\"><div class=\"si\"><b>${money(m.tax)}<\/b><span>hard tax captured<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${money(m.recirc)}<\/b><span>est. local re-circulation<\/span><\/div><div class=\"si\"><b>${money(m.tax+m.recirc)}<\/b><span>combined public benefit<\/span><\/div><\/div>`));\n  recircCard.appendChild(h(`<div class=\"gauge-track\" style=\"margin-top:14px\"><i style=\"width:${(m.recirc\/(m.tax+m.recirc)*100).toFixed(0)}%\"><\/i><\/div>`));\n  recircCard.appendChild(h('<div class=\"note\">Public revenue does not leave the community \u2014 it funds payroll, services, and local procurement that are re-spent through the regional economy. Modeled conservatively at 0.9\u00d7 of captured tax (induced\/soft). This is the number that turns \"we captured tax\" into \"we grew the local economy.\"<\/div>'));\n  wrap.appendChild(recircCard);\n\n  \/\/ full data table\n  const c2=card('Full tax detail','auditable line items \u00b7 '+periodLabel(),'',' ');\n  const tbl=h('<table><thead><tr><th>Zone<\/th><th>Local %<\/th><th>${TAX.localName}<\/th><th>${TAX.salesName}<\/th><th>${TAX.rentalName}<\/th><th>${TAX.stateName}<\/th><th>Total tax<\/th><th>Recirc (soft)<\/th><\/tr><\/thead><tbody><\/tbody><\/table>');\n  const tb=tbl.querySelector('tbody');\n  rows.forEach(r=>{const L=LOCS().find(x=>x.name===r.label);const mm=deriveMetrics(L,k),bb=mm.taxBreak,rt=L.tax;\n    const tr=h(`<tr><td><b>${L.name}<\/b><\/td><td class=\"tnum\">${rt.tot}%<\/td><td class=\"tnum\">${money2(bb.hotelTOT)}<\/td><td class=\"tnum\">${money2(bb.salesTax)}<\/td><td class=\"tnum\">${money2(bb.rentalTax)}<\/td><td class=\"tnum\">${money2(bb.stateTax)}<\/td><td class=\"tnum\"><b>${money2(mm.tax)}<\/b><\/td><td class=\"tnum\" style=\"color:var(--good)\">${money2(mm.recirc)}<\/td><\/tr>`);\n    tr.addEventListener('click',()=>go('#\/location\/'+L.id));tb.appendChild(tr);\n  });\n  c2.appendChild(tbl);\n  wrap.appendChild(c2);\n  return wrap;\n}\n\n\/* ================= NETWORK ROLL-UP (all destinations) ================= *\/\nfunction pageNetwork(){\n  const k=K(),{net,parts}=networkAgg(k);\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">TrueOmni Network \u00b7 All destinations<\/div><h1>Total economic impact \u2014 every kiosk, every destination<\/h1><\/div><div class=\"meta\">${periodLabel()}<br><b style=\"color:var(--text);font-size:14px\">${net.destinations} destinations \u00b7 ${num(net.kiosks)} kiosks<\/b><\/div><\/div>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>TrueOmni Network<\/span><span class=\"sep\">\u203a<\/span><span>All destinations<\/span><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Network roll-up',aiNetwork(k))));\n\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Network economic impact<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(net.total).replace(curSym(),'')}<\/div><div class=\"k-delta up\">\u25b2 at full deployment<\/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),'state &amp; local, at full deployment',`${TAX.localBucket} ${money(net.taxLocal)} \u00b7 ${TAX.stateBucket} ${money(net.taxState)}`));\n  kpis.appendChild(kpiTile('Partner-captured spend',money(Object.values(net.partners).reduce((a,b)=>a+b,0)),'\u25b2 24.1%','across all destinations'));\n  kpis.appendChild(kpiTile('Trackable visitor actions',num(net.interactions),'\u25b2 15.2%',`${num(net.conversions)} attributed conversions`));\n  wrap.appendChild(kpis);\n\n  \/\/ destinations ranked (real 3 + modeled long-tail)\n  const destRows=NET_IDS.map((id,i)=>({id,name:DESTINATIONS[id].name,region:DESTINATIONS[id].region,kind:DESTINATIONS[id].kind,m:parts[i]}))\n    .sort((a,b)=>b.m.total-a.m.total);\n  const maxD=destRows[0].m.total;\n  const dl=h('<div class=\"loclist\"><\/div>');\n  destRows.forEach(d=>{\n    const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${d.name}<\/div><div class=\"lr-sub\">${d.kind} \u00b7 ${d.m.kiosks} kiosks \u00b7 ${d.region}<\/div><\/div><div class=\"lr-val\">${money(d.m.total)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(d.m.tax)} tax<\/div><\/div><span class=\"chev\">\u203a<\/span><div class=\"lr-bar\"><i style=\"width:${(d.m.total\/maxD*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n    row.addEventListener('click',()=>setDest(d.id));dl.appendChild(row);\n  });\n  const realTot=parts.reduce((a,b)=>a+b.total,0), othersTot=net.total-realTot, othersTax=net.tax-parts.reduce((a,b)=>a+b.tax,0), othersK=net.kiosks-parts.reduce((a,b)=>a+b.kiosks,0);\n  dl.appendChild(h(`<div class=\"locrow\" style=\"opacity:.85;cursor:default\"><div><div class=\"lr-name\">Other ${net.destinations-3} destinations<\/div><div class=\"lr-sub\">North America + Japan \u00b7 ~${num(othersK)} kiosks \u00b7 modeled long-tail<\/div><\/div><div class=\"lr-val\">${money(othersTot)}<div class=\"lr-sub\" style=\"font-weight:600\">${money(othersTax)} tax<\/div><\/div><span class=\"chev\" style=\"opacity:0\">\u203a<\/span><div class=\"lr-bar\"><i style=\"width:${(othersTot\/maxD*100).toFixed(0)}%;background:linear-gradient(90deg,#8b96a5,#5B6675)\"><\/i><\/div><\/div>`));\n\n  const r1=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  r1.appendChild(card('Destinations ranked','click a live destination to drill into its kiosks',[dl,h('<div class=\"note\">California, Chicago and Florida are live TrueImpact tenants shown from their own kiosk data. The long-tail bucket models the remaining network to the published footprint.<\/div>')]));\n  \/\/ network partner mix\n  const parts2=Object.entries(net.partners).map(([id,v])=>({v,name:SEG[id].name,color:SEG[id].color}));\n  const pc=card('Network partner impact','hotels \u00b7 dining \u00b7 attractions \u00b7 events','',' ');\n  const pin=h('<div style=\"display:flex;gap:20px;align-items:center;flex-wrap:wrap\"><\/div>');\n  pin.appendChild(donut(parts2));\n  const plist=h('<div style=\"flex:1;min-width:190px\"><\/div>');\n  parts2.sort((a,b)=>b.v-a.v).forEach(p=>plist.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\" style=\"display:flex;align-items:center;gap:7px\"><span class=\"sw\" style=\"width:11px;height:11px;border-radius:3px;background:${p.color}\"><\/span>${p.name}<\/div><div class=\"lr-val\">${money(p.v)}<\/div><\/div>`)));\n  pin.appendChild(plist);pc.appendChild(pin);\n  r1.appendChild(pc);\n  wrap.appendChild(r1);\n\n  \/\/ engagement entry (network scope)\n  wrap.appendChild(engMiniCard(k));\n  wrap.lastChild.style.marginBottom='16px';\n\n  \/\/ network trend\n  wrap.appendChild(card('Network economic impact trend','monthly \u00b7 all destinations',[areaChart(networkSeries(k),MONTHS,{h:220}),h('<div class=\"note\">The category\\'s first aggregate impact curve built from first-party behavioral data across the network. Each destination re-circulates public tax dollars into its own community \u2014 an estimated '+money(net.recirc)+' network-wide (soft benefit).<\/div>')]));\n  return wrap;\n}\n\n\/* ================= ENGAGEMENT DRILL-THROUGH ================= *\/\nfunction engScope(k){\n  if(activeDest==='network'){const m=networkAgg(k).net;return {m,name:'the TrueOmni Network',seg:segSharesOf(m),locs:[]};}\n  if(engLoc!=='all'){const L=findLoc(engLoc);if(L){const m=deriveMetrics(L,k);return {m,name:L.name,seg:segSharesOf(m),locs:LOCS()};}}\n  const m=rollup(k);return {m,name:ACT().region,seg:segSharesOf(m),locs:LOCS()};\n}\nfunction engMetrics(mode){\n  const base={\n    interactions:{label:'Attention',   get:r=>r.interactions, fmt:num,   tfmt:v=>num(v)+' sessions'},\n    highIntent:  {label:'High-intent',  get:r=>r.highIntent,   fmt:num,   tfmt:v=>num(v)+' actions'},\n    conversions: {label:'Conversions',  get:r=>r.conversions,  fmt:num,   tfmt:v=>num(v)+' conv.'},\n    impact:      {label:'Attributed $', get:r=>r.impact,       fmt:money, tfmt:money2},\n  };\n  if(mode==='modules') base.time={label:'Time spent',get:r=>r.timeHrs,fmt:v=>num(v)+' h',tfmt:v=>num(v)+' hours'};\n  else base.roas={label:'ROI (ROAS)',get:r=>r.roas,fmt:v=>v.toFixed(1)+'\u00d7',tfmt:v=>v.toFixed(2)+'\u00d7 return'};\n  return base;\n}\nfunction setEng(key,val){ if(key==='mode'){engMode=val;engMetric='interactions';} else if(key==='loc'){engLoc=val;} else {engMetric=val;}\n  location.hash = '#\/'+(engMode==='modules'?'modules':'campaigns'); render(); }\nfunction pageEngagement(){\n  const k=K(), sc=engScope(k);\n  const items = engMode==='modules'?MODULES:CAMPAIGNS;\n  const rows = splitFunnel(sc.m, items, sc.seg);\n  const M = engMetrics(engMode);\n  if(!M[engMetric]) engMetric='interactions';\n  const met=M[engMetric];\n  rows.sort((a,b)=>(met.get(b)||0)-(met.get(a)||0));\n  const colorOf=r=> engMode==='modules'?SEG[r.tag].color:CAMP_TYPE[r.type];\n\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${activeDest==='network'?'Network':ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><a onclick=\"go('#\/overview')\">${activeDest==='network'?'TrueOmni Network':ACT().region}<\/a><span class=\"sep\">\u203a<\/span><span>Engagement drill-through<\/span><\/div>`));\n  wrap.appendChild(h(`<div class=\"pagehead\"><div><div class=\"eyebrow\">Marketing \u00b7 Engagement drill-through<\/div><h1>What draws attention \u2014 and what to invest in<\/h1><\/div><div class=\"meta\">${periodLabel()}<br><b style=\"color:var(--text)\">${sc.name}<\/b><\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Investment read',aiEngagement(rows,engMode,sc.name))));\n\n  \/\/ filters\n  const f=h('<div class=\"filters\"><\/div>');\n  f.appendChild(h(`<div class=\"fgroup\"><span class=\"flab\">Dimension<\/span><div class=\"fseg\">\n    <button class=\"${engMode==='modules'?'on':''}\" onclick=\"setEng('mode','modules')\">Modules<\/button>\n    <button class=\"${engMode==='campaigns'?'on':''}\" onclick=\"setEng('mode','campaigns')\">Campaigns<\/button><\/div><\/div>`));\n  if(sc.locs.length){\n    const opts=['<option value=\"all\"'+(engLoc==='all'?' selected':'')+'>All markets<\/option>']\n      .concat(sc.locs.map(L=>`<option value=\"${L.id}\"${engLoc===L.id?' selected':''}>${L.name}<\/option>`)).join('');\n    f.appendChild(h(`<div class=\"fgroup\"><span class=\"flab\">Location<\/span><select class=\"fsel\" onchange=\"setEng('loc',this.value)\">${opts}<\/select><\/div>`));\n  }\n  const mkeys=Object.keys(M);\n  f.appendChild(h(`<div class=\"fgroup\"><span class=\"flab\">Rank by<\/span><div class=\"fseg\">${mkeys.map(mk=>`<button class=\"${mk===engMetric?'on':''}\" onclick=\"setEng('metric','${mk}')\">${M[mk].label}<\/button>`).join('')}<\/div><\/div>`));\n  wrap.appendChild(f);\n\n  \/\/ ranked bar + top funnel\n  const r1=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n  const barRows=rows.map(r=>({label:engShort(r.name),parts:[{v:met.get(r)||0,color:colorOf(r),name:met.label}],total:met.get(r)||0}));\n  const rk=card(`${engMode==='modules'?'Modules':'Campaigns'} ranked by ${met.label.toLowerCase()}`,'stack ranking','',' ');\n  rk.appendChild(rankedStacked(barRows,{w:760,pr:80,fmt:met.fmt,tfmt:met.tfmt}));\n  r1.appendChild(rk);\n  \/\/ top item funnel drill-through\n  const top=rows[0];\n  const fc=card('Funnel drill-through','top '+(engMode==='modules'?'module':'campaign')+': '+top.name,[buildEngFunnel(top),h(`<div class=\"note\">Exposures \u2192 interactions \u2192 high-intent \u2192 attributed conversions for <b>${top.name}<\/b>. Conversion rate <b>${pct(top.convRate,1)}<\/b>${top.roas?` \u00b7 ROAS <b>${top.roas.toFixed(1)}\u00d7<\/b>`:''}${top.timeHrs?` \u00b7 <b>${num(top.timeHrs)}<\/b> engaged hours`:''}.<\/div>`)]);\n  r1.appendChild(fc);\n  wrap.appendChild(r1);\n\n  \/\/ full table\n  const tc=card(`${engMode==='modules'?'Module':'Campaign'} funnel \u2014 full breakdown`,'every stage \u00b7 '+periodLabel(),'',' ');\n  tc.appendChild(buildEngTable(rows,engMode,colorOf));\n  wrap.appendChild(tc);\n\n  \/\/ A\/B tests (campaigns only)\n  if(engMode==='campaigns'){\n    const tests=AB_TESTS.map(t=>({t,row:rows.find(r=>r.id===t.camp)})).filter(x=>x.row);\n    const abWrap=card('A\/B campaign tests','conversion rate by variant \u2014 compare & contrast',[h('<div class=\"grid row-2\" id=\"abgrid\" style=\"margin-top:2px\"><\/div>')]);\n    const grid=abWrap.querySelector('#abgrid');\n    tests.forEach(({t,row})=>grid.appendChild(buildABcard(t,row)));\n    wrap.appendChild(abWrap);\n  }\n  return wrap;\n}\nfunction buildEngFunnel(r){\n  const stages=[['Exposures',r.exposures,''],['Interactions',r.interactions,pct(r.interactions\/r.exposures,0)+' of exposed'],\n    ['High-intent',r.highIntent,pct(r.highIntent\/r.interactions,0)+' of interactions'],['Conversions',r.conversions,pct(r.conversions\/r.highIntent,0)+' of high-intent']];\n  const max=stages[0][1],cols=['#0E2240','#14508C','#1C9CD8','#33abe0'];\n  const box=h('<div class=\"funnel\"><\/div>');\n  stages.forEach(([n,v,rate],i)=>box.appendChild(h(`<div class=\"fstage\"><div class=\"fbar\" style=\"width:${26+40*(v\/max)}%;background:${cols[i]}\">${n}<\/div><div class=\"fmeta\"><b>${num(v)}<\/b> <span class=\"frate\">${rate}<\/span><\/div><\/div>`)));\n  return box;\n}\nfunction buildEngTable(rows,mode,colorOf){\n  const head = mode==='modules'\n    ? '<tr><th>Module<\/th><th>Exposures<\/th><th>Interactions<\/th><th>High-intent<\/th><th>Conversions<\/th><th>Conv %<\/th><th>Time<\/th><th>Attributed $<\/th><th>$\/1K int<\/th><\/tr>'\n    : '<tr><th>Campaign<\/th><th>Interactions<\/th><th>High-intent<\/th><th>Conversions<\/th><th>Conv %<\/th><th>Spend<\/th><th>Attributed $<\/th><th>ROAS<\/th><\/tr>';\n  const tbl=h(`<table><thead>${head}<\/thead><tbody><\/tbody><\/table>`);\n  const tb=tbl.querySelector('tbody');\n  rows.forEach(r=>{\n    const dot=`<span class=\"sw\" style=\"display:inline-block;width:10px;height:10px;border-radius:3px;background:${colorOf(r)};margin-right:7px;vertical-align:-1px\"><\/span>`;\n    const conv=`<span class=\"conv-pill\">${pct(r.convRate,1)}<\/span>`;\n    if(mode==='modules'){\n      tb.appendChild(h(`<tr><td>${dot}<b>${r.name}<\/b><\/td><td class=\"tnum\">${num(r.exposures)}<\/td><td class=\"tnum\">${num(r.interactions)}<\/td><td class=\"tnum\">${num(r.highIntent)}<\/td><td class=\"tnum\">${num(r.conversions)}<\/td><td>${conv}<\/td><td class=\"tnum\">${num(r.timeHrs)} h<\/td><td class=\"tnum\"><b>${money(r.impact)}<\/b><\/td><td class=\"tnum\">${money(r.valPerK)}<\/td><\/tr>`));\n    } else {\n      tb.appendChild(h(`<tr><td>${dot}<b>${r.name}<\/b><div class=\"ab-sub\" style=\"margin-left:17px\">${r.type}<\/div><\/td><td class=\"tnum\">${num(r.interactions)}<\/td><td class=\"tnum\">${num(r.highIntent)}<\/td><td class=\"tnum\">${num(r.conversions)}<\/td><td>${conv}<\/td><td class=\"tnum\">${money(r.spend)}<\/td><td class=\"tnum\"><b>${money(r.impact)}<\/b><\/td><td class=\"tnum\"><span class=\"pill ${r.roas>=4?'tag-hi':'tag-mid'}\">${r.roas.toFixed(1)}\u00d7<\/span><\/td><\/tr>`));\n    }\n  });\n  return tbl;\n}\nfunction buildABcard(t,row){\n  \/\/ split the campaign row's traffic; blended conv rate must equal row.convRate\n  const base=row.convRate, rA=base\/(t.split + (1-t.split)*t.bLift), rB=rA*t.bLift;\n  const intA=row.interactions*t.split, intB=row.interactions*(1-t.split);\n  const cvA=intA*rA, cvB=intB*rB;\n  const impA=row.impact*(cvA\/(cvA+cvB)), impB=row.impact*(cvB\/(cvA+cvB));\n  const winnerB=t.bLift>1, decisive=t.conf>=0.9;\n  const maxR=Math.max(rA,rB);\n  const badge = t.status==='No winner yet' ? `<span class=\"ab-badge ab-none\">Running \u00b7 ${pct(t.conf,0)} conf.<\/span>`\n    : `<span class=\"ab-badge ab-win\">${t.status} \u00b7 ${pct(t.conf,0)} conf.<\/span>`;\n  const lift=(rB\/rA-1);\n  const c=h(`<div class=\"ab-card\"><\/div>`);\n  c.appendChild(h(`<div class=\"ab-head\"><div><div class=\"abt\">${t.name}<\/div><div class=\"abs\">${row.name} \u00b7 ${num(row.interactions)} sessions split ${Math.round(t.split*100)}\/${Math.round((1-t.split)*100)}<\/div><\/div>${badge}<\/div>`));\n  const variant=(tag,label,r,cv,imp,isWin)=>`\n    <div class=\"ab-var\">\n      <div class=\"ab-tag\" style=\"background:${isWin?'var(--good)':'var(--slate)'}\">${tag}<\/div>\n      <div><div class=\"ab-nm\">${label} ${isWin?'<span class=\"conv-pill\" style=\"background:rgba(12,163,12,.14);color:var(--good)\">winner<\/span>':''}<\/div><div class=\"ab-sub\">${num(cv)} conversions \u00b7 ${money(imp)} attributed<\/div><\/div>\n      <div class=\"ab-conv\"><b>${pct(r,2)}<\/b><span>conv. rate<\/span><\/div>\n      <div class=\"ab-bar\"><i style=\"width:${(r\/maxR*100).toFixed(0)}%;background:${isWin?'var(--good)':'var(--slate)'}\"><\/i><\/div>\n    <\/div>`;\n  c.appendChild(h(variant('A',t.a,rA,cvA,impA,!winnerB)));\n  c.appendChild(h(variant('B',t.b,rB,cvB,impB,winnerB)));\n  c.appendChild(h(`<div class=\"ab-foot\"><span>Variant B vs A<\/span><span><b style=\"color:${lift>=0?'var(--good)':'var(--warn)'}\">${lift>=0?'+':''}${pct(lift,1)}<\/b> conversion-rate lift<\/span><\/div>`));\n  return c;\n}\n\n\/* ================= MEDIA NETWORK ================= *\/\nfunction aiMedia(k){const m=mediaMetrics(k), sg=signageMetrics(k), t=adTotals(k);\n  return `At the planned <b>${m.kiosks}-kiosk<\/b> plus <b>${sg?sg.screens:0}-screen<\/b> network, TrueImpact projects <span class=\"em\">${money(t.rev)}<\/span> a year in <b>Total Advertising Revenue<\/b> at the mid scenario \u2014 <b>${money(t.kioskRev)}<\/b> from interactive kiosks (range ${money(m.band.floor)}\u2013${money(m.band.strong)}) and <b>${money(t.signageRev)}<\/b> from digital signage. Combined <b>Screen Impressions<\/b> run about <b>${num(t.impressions)}<\/b> a year at an effective <b>CPM<\/b> near <b>${money(t.cpm)}<\/b>, in line with digital out-of-home. About <b>${pct(MEDIA.streamSplit.direct)}<\/b> of kiosk revenue is direct\/native local sales, the rest filled programmatically through Google Ad Manager. That covers the annual software fee <b>${(t.rev\/t.softwareYr).toFixed(0)}\u00d7<\/b> over and repays the full hardware investment in about <b>${t.payback.toFixed(1)} years<\/b> \u2014 computed <b>net of that software fee<\/b>, and <b>at mature sell-through<\/b>. If fill never improved beyond the Phase-1 rate this same model publishes, that payback is <b>${adTotals(k,LOCS(),1).payback.toFixed(1)} years<\/b>. Signage contributes <b>reach and revenue only<\/b>; its impressions are deliberately excluded from attributed visitor spend and tax.`;}\nfunction mediaMiniCard(k){const m=mediaMetrics(k), sg=signageMetrics(k), t=adTotals(k);\n  const c=card('Media Network \u00b7 potential advertising revenue','kiosks + digital signage \u00b7 the self-funding layer','',' ');\n  c.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:2px\"><div class=\"si\"><b>${money(t.rev)}<\/b><span>Total Advertising Revenue \/ yr<\/span><\/div><div class=\"si\"><b>${money(t.kioskRev)}<\/b><span>kiosk ad revenue<\/span><\/div><div class=\"si\"><b>${money(t.signageRev)}<\/b><span>signage ad revenue<\/span><\/div><div class=\"si\"><b>${num(t.impressions)}<\/b><span>Screen Impressions \/ yr<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${t.payback.toFixed(1)} yr<\/b><span>payback, net of software<\/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=K(); const m=mediaMetrics(k);\n  const wrap=h('<div class=\"fade\"><\/div>');\n  wrap.appendChild(h(`<a class=\"backbtn\" onclick=\"go('#\/overview')\"><svg viewBox=\"0 0 24 24\" fill=\"none\"><path d=\"M15 18l-6-6 6-6\" stroke=\"currentColor\" stroke-width=\"2.2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/><\/svg> ${ACT().region} overview<\/a>`));\n  wrap.appendChild(h(`<div class=\"crumbs\"><span>${ACT().name}<\/span><span class=\"sep\">\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 advertising revenue potential<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">Native direct-sold + Google Ad Manager programmatic \u00b7 ${periodLabel()}<\/div><\/div><div class=\"meta\">Sell-through at maturity<br><b style=\"color:var(--text);font-size:14px\">${pct(MEDIA.fillMature)} kiosk fill \u00b7 ${m.kiosks} kiosks<br>${pct(SIGNAGE.fillMature)} signage fill \u00b7 ${totalScreens()} screens<\/b><\/div><\/div>`));\n  wrap.appendChild(h(aiBanner('TrueImpact AI \u00b7 Media read', aiMedia(k))));\n\n  const sg=signageMetrics(k), t=adTotals(k);\n  const kpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n  kpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Total Advertising Revenue<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(t.rev).replace(curSym(),'')}<\/div><div class=\"k-delta up\">\u25b2 scales with fill, kiosks + screens<\/div><div class=\"k-foot\">Kiosks ${money(t.kioskRev)} + signage ${money(t.signageRev)} \u00b7 kiosk range ${money(m.band.floor)}\u2013${money(m.band.strong)}<\/div><\/div>`));\n  kpis.appendChild(kpiTile('Screen Impressions \/ yr',num(t.impressions),'',`kiosks ${num(m.impressions)} + signage ${num(sg?sg.impressions:0)}`));\n  kpis.appendChild(kpiTile('Effective CPM (blended)',money2(t.cpm),'','in line with DOOH benchmarks'));\n  kpis.appendChild(kpiTile('Covers annual software',(t.rev\/t.softwareYr).toFixed(0)+'\u00d7','',`vs ${money(t.softwareYr)}\/yr platform + signage software`));\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,{centerLabel:'ad revenue'}));\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 ramp \u00b7 kiosks + signage','fill-adjusted: early phases sell through below mature rates','',' ');\n  const rl=h('<div class=\"loclist\"><\/div>');\n  PHASES.forEach(ph=>{const locs=LOCS().filter(L=>ph.ids.includes(L.id));const mm=mediaMetrics(k,locs);const tt=adTotals(k,locs,ph.n);\n    rl.appendChild(h(`<div class=\"locrow\"><div><div class=\"lr-name\">${ph.name}<\/div><div class=\"lr-sub\">${ph.window} \u00b7 ${mm.kiosks} kiosks (${pct(MEDIA.fillByPhase[ph.n])} fill) \u00b7 ${totalScreens(locs)} screens (${pct(SIGNAGE.fillByPhase[ph.n])} fill)<\/div><\/div><div class=\"lr-val\">${money(tt.rev)}<div class=\"lr-sub\" style=\"font-weight:600\">kiosks ${money(tt.kioskRev)} + signage ${money(tt.signageRev)}<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(mm.kiosks\/NET_KIOSKS*100).toFixed(0)}%\"><\/i><\/div><\/div>`));});\n  rampC.appendChild(rl);\n  rampC.appendChild(h('<div class=\"note\">Revenue scales with kiosks online and with sell-through as impressions grow. Local partners anchor direct sales from day one; programmatic (Google Ad Manager) fills remnant inventory once impression volume attracts national demand.<\/div>'));\n  wrap.appendChild(rampC);\n\n  \/\/ ---------------- DIGITAL SIGNAGE (broadcast screens \u2014 reach, not transactions)\n  if(sg){\n    const sgKpis=h('<div class=\"grid kpis\" style=\"margin-bottom:16px\"><\/div>');\n    sgKpis.appendChild(h(`<div class=\"card kpi hero\"><div class=\"glow\"><\/div><div class=\"k-label\">Digital signage \u00b7 ad revenue<\/div><div class=\"k-val\"><span class=\"cur\">${curSym()}<\/span>${money(sg.rev).replace(curSym(),'')}<\/div><div class=\"k-delta up\">\u25b2 ${sg.screens} screens deployed<\/div><div class=\"k-foot\">Range ${money(sg.band.floor)}\u2013${money(sg.band.strong)} \u00b7 ${money2(sg.rev\/sg.screens)} revenue per screen \/ yr<\/div><\/div>`));\n    sgKpis.appendChild(kpiTile('Screen Impressions \/ yr',num(sg.impressions),'',`${num(sg.reach)} reach \u00b7 ${SIGNAGE.imprPerPasserby} impressions per pass`));\n    sgKpis.appendChild(kpiTile('Avg dwell \/ share of voice',SIGNAGE.dwellSec+'s \u00b7 '+pct(sg.shareOfVoice),'',`${sg.spotsPerLoop} spots in a ${SIGNAGE.loopSec}s loop`));\n    sgKpis.appendChild(kpiTile('Signage fill rate',pct(SIGNAGE.fillMature),'',`ramps ${pct(SIGNAGE.fillByPhase[1])} \u2192 ${pct(SIGNAGE.fillByPhase[3])} by phase`));\n    wrap.appendChild(h('<div class=\"pagehead\" style=\"margin-bottom:10px\"><div><div class=\"eyebrow\">Digital Signage \u00b7 OMNI Signage<\/div><h1 style=\"font-size:24px\">Broadcast screens \u2014 reach and inventory<\/h1><div class=\"ch-sub\" style=\"margin-top:4px;color:var(--text-2)\">Screens deliver reach and ad inventory; they do not transact. Their impressions are excluded from attributed spend and tax.<\/div><\/div><\/div>'));\n    wrap.appendChild(sgKpis);\n\n    const sgRow=h('<div class=\"grid row-2\" style=\"margin-bottom:16px\"><\/div>');\n    const fmt=card('Revenue by screen format','video wall \u00b7 portrait \u00b7 wayfinding \u00b7 menu \u00b7 outdoor','',' ');\n    const fl=h('<div class=\"loclist\"><\/div>'); const fmax=Math.max(...sg.byFormat.map(x=>x.v));\n    sg.byFormat.slice().sort((a,b)=>b.v-a.v).forEach(x=>{fl.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div class=\"lr-name\">${x.name}<\/div><div class=\"lr-val\">${money(x.v)}<div class=\"lr-sub\" style=\"font-weight:600\">${pct(x.share)}<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(x.v\/fmax*100).toFixed(0)}%\"><\/i><\/div><\/div>`));});\n    fmt.appendChild(fl); sgRow.appendChild(fmt);\n\n    const byZone=card('Screens by zone','deployed screen count and revenue per zone','',' ');\n    const zl=h('<div class=\"loclist\"><\/div>');\n    const zrows=[...LOCS()].map(L=>({L,rev:(L.screens||0)*SIGNAGE.perScreenMo.mid*12*k})).sort((a,b)=>b.rev-a.rev);\n    const zmax=zrows[0].rev||1;\n    zrows.forEach(({L,rev})=>{const row=h(`<div class=\"locrow\"><div><div class=\"lr-name\">${L.name}<\/div><div class=\"lr-sub\">${L.screens||0} screens \u00b7 ${L.kiosks} kiosks<\/div><\/div><div class=\"lr-val\">${money(rev)}<div class=\"lr-sub\" style=\"font-weight:600\">\/yr at mid<\/div><\/div><div class=\"lr-bar\"><i style=\"width:${(rev\/zmax*100).toFixed(0)}%\"><\/i><\/div><\/div>`);\n      row.addEventListener('click',()=>go('#\/location\/'+L.id));zl.appendChild(row);});\n    byZone.appendChild(zl); sgRow.appendChild(byZone);\n    wrap.appendChild(sgRow);\n\n    const mix=card('Revenue mix \u00b7 signage vs kiosks','two surfaces, one media network','',' ');\n    mix.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:4px\"><div class=\"si\"><b>${money(t.kioskRev)}<\/b><span>interactive kiosks (${pct(t.kioskRev\/t.rev)})<\/span><\/div><div class=\"si\"><b>${money(t.signageRev)}<\/b><span>digital signage (${pct(t.signageRev\/t.rev)})<\/span><\/div><div class=\"si\"><b>${money(t.rev)}<\/b><span>Total Advertising Revenue<\/span><\/div><\/div>`));\n    mix.appendChild(h('<div class=\"note\"><b>Why the two are reported separately.<\/b> Kiosks transact \u2014 a visitor touches a screen, builds an itinerary, redeems a pass \u2014 so kiosk encounters feed the attributed-spend, economic-impact and tax figures on every other page. Signage broadcasts \u2014 it reaches people who never interact \u2014 so it contributes impressions, reach and ad revenue only. Signage impressions are never added to interactions, attributed visitors or tax. Kiosks and signage monetize the same footfall on two surfaces, which is standard for a mixed network and exactly why the two lines are never summed into a single reach figure.<\/div>'));\n    wrap.appendChild(mix); wrap.lastChild.style.marginBottom='16px';\n  }\n\n  const sf=card('The self-funding math','advertising vs. total program cost (kiosks + signage)','',' ');\n  sf.appendChild(h(`<div class=\"stat-inline\" style=\"margin-top:4px\"><div class=\"si\"><b>${money(t.rev)}<\/b><span>Total Advertising Revenue \/ yr (mid)<\/span><\/div><div class=\"si\"><b>${money(t.softwareYr)}<\/b><span>annual software (kiosk + signage)<\/span><\/div><div class=\"si\"><b>${money(t.capital)}<\/b><span>one-time capital (kiosk + signage)<\/span><\/div><div class=\"si\"><b style=\"color:var(--good)\">${t.payback.toFixed(1)} yr<\/b><span>payback, net of software<\/span><\/div><\/div>`));\n  const pbRow=h('<div class=\"loclist\" style=\"margin-top:12px\"><\/div>');\n  PHASES.forEach(ph=>{const t2=adTotals(k,LOCS(),ph.n);\n    pbRow.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div><div class=\"lr-name\">Payback at ${ph.name.replace(\/^Phase \\d+ \u00b7 \/,'Phase '+ph.n+' \u2014 ')} sell-through<\/div><div class=\"lr-sub\">${pct(MEDIA.fillByPhase[ph.n])} kiosk fill \u00b7 ${pct(SIGNAGE.fillByPhase[ph.n])} signage fill \u00b7 full-network capital<\/div><\/div><div class=\"lr-val\">${t2.payback.toFixed(1)} yr<div class=\"lr-sub\" style=\"font-weight:600\">${money(t2.rev)}\/yr<\/div><\/div><\/div>`));});\n  pbRow.appendChild(h(`<div class=\"locrow\" style=\"grid-template-columns:1fr auto\"><div><div class=\"lr-name\"><b>Payback at mature sell-through<\/b><\/div><div class=\"lr-sub\">${pct(MEDIA.fillMature)} kiosk fill \u00b7 ${pct(SIGNAGE.fillMature)} signage fill<\/div><\/div><div class=\"lr-val\"><b>${t.payback.toFixed(1)} yr<\/b><div class=\"lr-sub\" style=\"font-weight:600\">${money(t.rev)}\/yr<\/div><\/div><\/div>`));\n  sf.appendChild(pbRow);\n  sf.appendChild(h(`<div class=\"note\">${COPY.sfNote}<\/div>`));\n  wrap.appendChild(sf);\n  return wrap;\n}\n\n\/* ================= ROUTER ================= *\/\nconst state={loc:null};\nfunction go(hash){location.hash=hash;}\nfunction render(){\n  buildDestSel();\n  const hash=location.hash||'#\/overview';\n  const parts=hash.replace('#\/','').split('\/');\n  if(activeDest==='network'){\n    app.innerHTML='';\n    if(parts[0]==='modules'){engMode='modules';app.appendChild(pageEngagement());}\n    else if(parts[0]==='campaigns'){engMode='campaigns';app.appendChild(pageEngagement());}\n    else app.appendChild(pageNetwork());\n    window.scrollTo({top:0,behavior:'instant'});return;\n  }\n  state.loc = parts[0]==='location'?parts[1]:null;\n  app.innerHTML='';\n  let node;\n  if(parts[0]==='location') node=pageLocation(parts[1]);\n  else if(parts[0]==='partners') node=pagePartners();\n  else if(parts[0]==='tax') node=pageTax();\n  else if(parts[0]==='modules'){engMode='modules';node=pageEngagement();}\n  else if(parts[0]==='campaigns'){engMode='campaigns';node=pageEngagement();}\n  else if(parts[0]==='media') node=pageMedia();\n  else 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\/\/ destination selector\n$('#destSel').addEventListener('change',e=>setDest(e.target.value));\n\/\/ theme\n$('#themeBtn').addEventListener('click',()=>{const cur=document.documentElement.getAttribute('data-theme');document.documentElement.setAttribute('data-theme',cur==='dark'?'light':'dark');render();});\nwindow.addEventListener('mousemove',e=>{if(TT.style.opacity==1)moveTT(e);});\n\nrender();\n<\/script>\n<\/body>\n<\/html>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\n<\/div>\n\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>TrueImpact \u2014 Executive Dashboard \u00b7 Pennsylvania Tourism Office<\/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-58315","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>PennsylvaniaTourismOffice - TrueOmni<\/title>\n<meta name=\"description\" content=\"Pennsylvania Tourism Office\" \/>\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\/french\/pennsylvania-tourism-office\/\" \/>\n<meta property=\"og:locale\" content=\"fr_FR\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"PennsylvaniaTourismOffice\" \/>\n<meta property=\"og:description\" content=\"Pennsylvania Tourism Office\" \/>\n<meta property=\"og:url\" content=\"https:\/\/trueomni.com\/french\/pennsylvania-tourism-office\/\" \/>\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-20T20:01:33+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=\"Dur\u00e9e de lecture estim\u00e9e\" \/>\n\t<meta name=\"twitter:data1\" content=\"1 minute\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\\\/\\\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\\\/\\\/trueomni.com\\\/pennsylvania-tourism-office\\\/\",\"url\":\"https:\\\/\\\/trueomni.com\\\/pennsylvania-tourism-office\\\/\",\"name\":\"PennsylvaniaTourismOffice - TrueOmni\",\"isPartOf\":{\"@id\":\"http:\\\/\\\/trueomni.com\\\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/pennsylvania-tourism-office\\\/#primaryimage\"},\"image\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/pennsylvania-tourism-office\\\/#primaryimage\"},\"thumbnailUrl\":\"https:\\\/\\\/trueomni.com\\\/wp-content\\\/uploads\\\/2026\\\/05\\\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png\",\"datePublished\":\"2026-08-20T20:00:07+00:00\",\"dateModified\":\"2026-08-20T20:01:33+00:00\",\"description\":\"Pennsylvania Tourism Office\",\"breadcrumb\":{\"@id\":\"https:\\\/\\\/trueomni.com\\\/pennsylvania-tourism-office\\\/#breadcrumb\"},\"inLanguage\":\"fr-FR\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\\\/\\\/trueomni.com\\\/pennsylvania-tourism-office\\\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@id\":\"https:\\\/\\\/trueomni.com\\\/pennsylvania-tourism-office\\\/#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\\\/pennsylvania-tourism-office\\\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"http:\\\/\\\/trueomni.com\\\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"PennsylvaniaTourismOffice\"}]},{\"@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\":\"fr-FR\"},{\"@type\":\"Organization\",\"@id\":\"http:\\\/\\\/trueomni.com\\\/#organization\",\"name\":\"TRUE OMNI LLC\",\"alternateName\":\"TRUE OMNI LLC\",\"url\":\"http:\\\/\\\/trueomni.com\\\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"fr-FR\",\"@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":"PennsylvaniaTourismOffice - TrueOmni","description":"Pennsylvania Tourism Office","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\/french\/pennsylvania-tourism-office\/","og_locale":"fr_FR","og_type":"article","og_title":"PennsylvaniaTourismOffice","og_description":"Pennsylvania Tourism Office","og_url":"https:\/\/trueomni.com\/french\/pennsylvania-tourism-office\/","og_site_name":"TrueOmni","article_publisher":"https:\/\/www.facebook.com\/trueomni\/","article_modified_time":"2026-08-20T20:01:33+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":{"Dur\u00e9e de lecture estim\u00e9e":"1 minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/trueomni.com\/pennsylvania-tourism-office\/","url":"https:\/\/trueomni.com\/pennsylvania-tourism-office\/","name":"PennsylvaniaTourismOffice - TrueOmni","isPartOf":{"@id":"http:\/\/trueomni.com\/#website"},"primaryImageOfPage":{"@id":"https:\/\/trueomni.com\/pennsylvania-tourism-office\/#primaryimage"},"image":{"@id":"https:\/\/trueomni.com\/pennsylvania-tourism-office\/#primaryimage"},"thumbnailUrl":"https:\/\/trueomni.com\/wp-content\/uploads\/2026\/05\/Travel-TrueOmni-TNT-IVE-Kiosks-2026-Website-scaled.png","datePublished":"2026-08-20T20:00:07+00:00","dateModified":"2026-08-20T20:01:33+00:00","description":"Pennsylvania Tourism Office","breadcrumb":{"@id":"https:\/\/trueomni.com\/pennsylvania-tourism-office\/#breadcrumb"},"inLanguage":"fr-FR","potentialAction":[{"@type":"ReadAction","target":["https:\/\/trueomni.com\/pennsylvania-tourism-office\/"]}]},{"@type":"ImageObject","inLanguage":"fr-FR","@id":"https:\/\/trueomni.com\/pennsylvania-tourism-office\/#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\/pennsylvania-tourism-office\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"http:\/\/trueomni.com\/"},{"@type":"ListItem","position":2,"name":"PennsylvaniaTourismOffice"}]},{"@type":"WebSite","@id":"http:\/\/trueomni.com\/#website","url":"http:\/\/trueomni.com\/","name":"TrueOmni","description":"TrueOmni, entreprise sp\u00e9cialis\u00e9e dans l&#039;exp\u00e9rience client num\u00e9rique en libre-service","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":"fr-FR"},{"@type":"Organization","@id":"http:\/\/trueomni.com\/#organization","name":"TRUE OMNI LLC","alternateName":"TRUE OMNI LLC","url":"http:\/\/trueomni.com\/","logo":{"@type":"ImageObject","inLanguage":"fr-FR","@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\/french\/wp-json\/wp\/v2\/pages\/58315","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/trueomni.com\/french\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/trueomni.com\/french\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/trueomni.com\/french\/wp-json\/wp\/v2\/users\/11"}],"replies":[{"embeddable":true,"href":"https:\/\/trueomni.com\/french\/wp-json\/wp\/v2\/comments?post=58315"}],"version-history":[{"count":4,"href":"https:\/\/trueomni.com\/french\/wp-json\/wp\/v2\/pages\/58315\/revisions"}],"predecessor-version":[{"id":58319,"href":"https:\/\/trueomni.com\/french\/wp-json\/wp\/v2\/pages\/58315\/revisions\/58319"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/trueomni.com\/french\/wp-json\/wp\/v2\/media\/57099"}],"wp:attachment":[{"href":"https:\/\/trueomni.com\/french\/wp-json\/wp\/v2\/media?parent=58315"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}