BEFORE Google tags load (it
* replaces the GetSiteControl line in headcode.inc, which satisfies
* that) so consent defaults and gtag 'set' flags precede any config.
*
* Everything an editor should ever need to change lives in CONFIG below.
*/
(function () {
"use strict";
// =====================================================================
// CONFIG — edit text/links/lists here; logic lives below.
// =====================================================================
var CONFIG = {
policyVersion: 1, // bump to re-show the notice (choices are KEPT)
cookieName: "mcc_consent",
cookieDays: 365,
privacyPolicyUrl: "https://meridiancc.edu/about_mcc/policies_and_procedures/privacy_policy/",
honorGPC: true,
honorDNT: false, // legacy Do Not Track (off by default)
floatingTab: true, // persistent "Privacy choices" reopener
// Youth-page force-off was considered and deliberately OMITTED: the
// College for Kids pages are parent-directed (registration is
// parent-completed — 91¸£ÀûÉç's documented COPPA posture), so the pages'
// visitors are adults. See INSTALL.md for the analysis + verified
// paths should compliance ever want the restriction added.
google: {
// Verified against the refreshed backup 2026-07-23: analytics.inc
// configures exactly these two tags.
analyticsIds: ["G-2JV2BY02ES"],
advertisingIds: ["AW-16908016673"],
},
// Cookie-name prefixes deleted (best effort) when enhanced
// measurement is off. _gcl* are Google Ads click/conversion cookies.
cleanupPrefixes: ["_gcl"],
text: {
bannerTitle: "Your privacy choices",
// Banner copy simplified per Tony 2026-07-26: short notice + policy
// link; the full disclosures and the switch live in the panel.
bannerBody: "91¸£ÀûÉç uses web analytics to " +
"understand how visitors use this website and improve its " +
"content and performance.",
gpcBannerBody: "Your browser sent a Global Privacy Control signal. " +
"Advertising and enhanced measurement are off. Basic site " +
"analytics and site features remain active.",
policyPre: "View our ",
policyLinkLabel: "privacy policy",
policyPost: " for more details.",
acceptLabel: "Accept",
continueLabel: "Continue",
reviewLabel: "Review details",
choicesLabel: "Privacy choices",
modalTitle: "Your privacy choices",
modalIntro: "Basic site analytics and site features are always " +
"active. You control optional advertising and enhanced " +
"measurement below.",
saveLabel: "Save choices",
alwaysOn: "Always active",
optionalTag: "Optional",
gpcLockNote: "Global Privacy Control detected. Advertising and " +
"enhanced measurement are off.",
onLabel: "On",
offLabel: "Off",
closeLabel: "Close privacy choices",
},
categories: [
{
id: "features", label: "Site features", locked: true,
description: "These services support website features such as " +
"live chat, social feeds, call routing, and accessibility. " +
"They are not controlled by this preference.",
services: [
{ name: "Tawk.to", purpose: "Live chat with 91¸£ÀûÉç staff (a notice also appears before you chat)." },
{ name: "Curator.io", purpose: "Social media feed display on some pages." },
{ name: "CallRail", purpose: "Call routing and attribution — measures which pages lead to phone calls." },
{ name: "YuJa Panorama", purpose: "Website accessibility tooling." },
{ name: "OU Alerts", purpose: "Campus emergency alert banner." },
],
},
{
id: "basic", label: "Basic site analytics", locked: true,
description: "91¸£ÀûÉç measures how visitors navigate and use the " +
"website so we can improve its content and performance. These " +
"tools process technical and usage information and provide 91¸£ÀûÉç " +
"with aggregate reports. Basic analytics remains active when " +
"optional measurement is off.",
services: [
{ name: "Google Analytics (GA4)", purpose: "Page and navigation measurement." },
{ name: "Modern Campus Personalization / Matomo", purpose: "Site-usage statistics for our CMS." },
],
},
{
id: "enhanced", label: "Advertising and enhanced measurement",
locked: false,
description: "When enabled, Google may use advertising " +
"identifiers and signed-in Google account signals to help 91¸£ÀûÉç " +
"understand which ads lead to visits or actions and to provide " +
"cross-device, demographic, and interest reporting. Turn this " +
"off to disable those features. Basic site analytics remains " +
"active.",
services: [
{ name: "Google Ads", purpose: "Ad conversion measurement for 91¸£ÀûÉç campaigns." },
{ name: "Google Signals", purpose: "Signed-in, cross-device, and demographic/interest reporting in Google Analytics." },
],
},
],
};
// =====================================================================
// Engine — no config below this line.
// =====================================================================
var win = window, doc = document;
// ---- state ----------------------------------------------------------
// Schema: { v, ts, ack, em } where em is true/false when the visitor
// made an explicit choice, or null when they only acknowledged.
function migrateLegacy(s) {
// rev-2 schema had cats{analytics,advertising} + explicit[]. Migrate
// conservatively: ANY legacy explicit opt-out -> enhanced OFF; a
// legacy explicit all-on -> ON; otherwise acknowledgment only.
if (!s.cats || typeof s.cats !== "object" || !Array.isArray(s.explicit)) {
return null;
}
var em = null;
var optedOut = s.explicit.some(function (id) {
return s.cats[id] === false;
});
if (optedOut) em = false;
else if (s.explicit.length) em = true;
return { v: typeof s.v === "number" ? s.v : 0,
ts: typeof s.ts === "string" ? s.ts : "",
ack: s.ack === true, em: em };
}
function validState(s) {
if (!s || typeof s !== "object" || Array.isArray(s)) return null;
if (typeof s.em === "undefined" && s.cats) return migrateLegacy(s);
if (typeof s.v !== "number" || typeof s.ack !== "boolean") return null;
if (s.em !== null && typeof s.em !== "boolean") return null;
return { v: s.v, ts: typeof s.ts === "string" ? s.ts : "",
ack: s.ack, em: s.em };
}
function readState() {
var m = ("; " + doc.cookie).split("; " + CONFIG.cookieName + "=");
var raw = m.length > 1 ? m[1].split(";")[0] : null;
if (!raw) {
try { raw = win.localStorage.getItem(CONFIG.cookieName); } catch (e) {}
}
if (!raw) return null;
var parsed = null;
try { parsed = JSON.parse(decodeURIComponent(raw)); } catch (e) { return null; }
var s = validState(parsed);
if (!s) return null;
// Policy/schema-version bump: PRESERVE the explicit choice, drop only
// the acknowledgment so the notice re-shows.
if (s.v !== CONFIG.policyVersion) s.ack = false;
return s;
}
function writeState(state) {
var raw = encodeURIComponent(JSON.stringify(state));
var exp = new Date(Date.now() + CONFIG.cookieDays * 864e5).toUTCString();
doc.cookie = CONFIG.cookieName + "=" + raw + "; expires=" + exp +
"; path=/; SameSite=Lax" +
(win.location.protocol === "https:" ? "; Secure" : "");
try { win.localStorage.setItem(CONFIG.cookieName, raw); } catch (e) {}
}
var stored = null;
try { stored = readState(); } catch (e) { stored = null; }
var gpcSignal = !!(CONFIG.honorGPC && navigator.globalPrivacyControl === true) ||
!!(CONFIG.honorDNT && (navigator.doNotTrack === "1" || win.doNotTrack === "1"));
// Effective enhanced-measurement state, recomputed per load. The GPC
// lock wins over a saved ON but never overwrites it.
function effectiveEM() {
if (gpcSignal) return false;
if (stored && typeof stored.em === "boolean") return stored.em;
return true; // notice-model default
}
var em;
try { em = effectiveEM(); } catch (e) { stored = null; em = !gpcSignal; }
var emLocked = gpcSignal;
// ---- Google enforcement (synchronous — before gtag.js loads) --------
function gtagPush() {
win.dataLayer = win.dataLayer || [];
win.dataLayer.push(arguments);
}
function consentModePayload() {
var ads = em ? "granted" : "denied";
return {
ad_storage: ads,
ad_user_data: ads,
ad_personalization: ads,
analytics_storage: "granted", // basic analytics: always on (US posture)
};
}
// Tracks whether the restrictive 'set' flags were applied in THIS
// document, so an explicit OFF->ON change can restore them. On a fresh
// ON load nothing is pushed at all — Google property/account settings
// stay authoritative (a client-side `true` cannot enable a feature an
// administrator disabled at the property level; it only undoes OUR
// earlier `false` from this same page).
var restrictiveApplied = false;
function pushSignalFlags() {
if (!em) {
// Both control sets, per Google's 2026-06-15 Signals change:
// Consent Mode ad settings govern Ads; allow_google_signals governs
// signed-in association for Analytics behavioral reporting.
gtagPush("set", {
allow_google_signals: false,
allow_ad_personalization_signals: false,
restricted_data_processing: true,
});
gtagPush("set", "ads_data_redaction", true);
restrictiveApplied = true;
}
// When ON at initial load: leave Signals/personalization/RDP at
// property-configured defaults — nothing to push.
}
function cleanupCookies() {
if (em) return;
doc.cookie.split("; ").forEach(function (pair) {
var name = pair.split("=")[0];
var hit = CONFIG.cleanupPrefixes.some(function (p) {
return name.indexOf(p) === 0;
});
if (!hit) return;
[null, "." + win.location.hostname,
"." + win.location.hostname.split(".").slice(-2).join(".")]
.forEach(function (domain) {
doc.cookie = name + "=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/" +
(domain ? "; domain=" + domain : "");
});
});
}
// Consent Mode defaults + Signals flags — MUST precede gtag.js.
// NOTE deliberately absent: no ga-disable-* (GA4 basic analytics always
// runs; the flag was also network-verified ineffective for AW- ids)
// and no Matomo optUserOut (Matomo is basic analytics, always on).
gtagPush("consent", "default", consentModePayload());
pushSignalFlags();
cleanupCookies();
// ---- gated-script activation (type="text/plain") --------------------
// The engine contract for the knowledge-core site and the optional
// hardened analytics.inc (category "enhanced" gates AW config there).
function activateGated() {
var nodes = doc.querySelectorAll(
'script[type="text/plain"][data-mcc-consent]');
Array.prototype.forEach.call(nodes, function (node) {
var cat = node.getAttribute("data-mcc-consent");
var granted = cat === "enhanced" ? em : true; // locked cats always on
if (!granted || node.getAttribute("data-mcc-activated")) return;
var s = doc.createElement("script");
Array.prototype.forEach.call(node.attributes, function (a) {
if (a.name === "type" || a.name === "data-mcc-consent") return;
s.setAttribute(a.name, a.value);
});
s.text = node.text || "";
node.setAttribute("data-mcc-activated", "1");
node.parentNode.insertBefore(s, node.nextSibling);
});
}
// ---- persistence + live application ---------------------------------
function saveChoice(explicitEM) {
stored = {
v: CONFIG.policyVersion, ts: new Date().toISOString(),
ack: true, em: explicitEM,
};
writeState(stored);
em = effectiveEM();
gtagPush("consent", "update", consentModePayload());
if (em) {
if (restrictiveApplied) {
// Explicit OFF->ON in the same document: restore everything the
// OFF state changed (not just redaction). Property-level Google
// settings remain authoritative.
gtagPush("set", {
allow_google_signals: true,
allow_ad_personalization_signals: true,
restricted_data_processing: false,
});
gtagPush("set", "ads_data_redaction", false);
restrictiveApplied = false;
}
} else {
pushSignalFlags();
}
cleanupCookies();
activateGated();
}
function acknowledgeOnly() {
stored = {
v: CONFIG.policyVersion, ts: new Date().toISOString(),
ack: true, em: stored && typeof stored.em === "boolean" ? stored.em : null,
};
writeState(stored);
}
// =====================================================================
// UI — injected on DOMContentLoaded. All classes prefixed mccc-.
// =====================================================================
var CSS = "" +
".mccc-banner{position:fixed;left:0;right:0;bottom:0;z-index:2147483000;" +
"background:#fff;color:#1b2330;border-top:4px solid #007A33;" +
"box-shadow:0 -2px 12px rgba(0,0,0,.18);padding:14px 20px;" +
"font:15px/1.5 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif}" +
".mccc-banner-inner{max-width:1100px;margin:0 auto;display:flex;" +
"flex-wrap:wrap;align-items:center;gap:12px 24px}" +
".mccc-banner-text{flex:1 1 420px;margin:0}" +
".mccc-banner-text strong{display:block;margin-bottom:2px}" +
".mccc-banner a{color:#00662b;font-weight:600}" +
".mccc-actions{display:flex;gap:10px;flex-wrap:wrap}" +
".mccc-btn{font:inherit;font-weight:700;border-radius:999px;cursor:pointer;" +
"padding:9px 22px;border:2px solid #007A33;background:#007A33;color:#fff}" +
".mccc-btn:hover{background:#00662b;border-color:#00662b}" +
".mccc-btn--ghost{background:#fff;color:#00662b}" +
".mccc-btn--ghost:hover{background:#eef5f0}" +
".mccc-btn:focus-visible,.mccc-tab:focus-visible,.mccc-close:focus-visible," +
".mccc-switch input:focus-visible+span{outline:3px solid #1b2330;outline-offset:2px}" +
".mccc-tab{position:fixed;left:12px;bottom:12px;z-index:2147482999;" +
"font:13px/1 -apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Arial,sans-serif;" +
"font-weight:700;background:#fff;color:#00662b;border:2px solid #007A33;" +
"border-radius:999px;padding:8px 14px;cursor:pointer;" +
"box-shadow:0 1px 6px rgba(0,0,0,.25)}" +
".mccc-tab:hover{background:#eef5f0}" +
".mccc-overlay{position:fixed;inset:0;z-index:2147483001;" +
"background:rgba(27,35,48,.55);display:flex;align-items:center;" +
"justify-content:center;padding:16px}" +
".mccc-modal{background:#fff;color:#1b2330;max-width:640px;width:100%;" +
"max-height:85vh;overflow:auto;border-radius:10px;border-top:6px solid #007A33;" +
"padding:22px 24px;font:15px/1.5 -apple-system,BlinkMacSystemFont," +
"'Segoe UI',Roboto,Arial,sans-serif}" +
".mccc-modal h2{margin:0 42px 6px 0;font-size:22px}" +
".mccc-modal-intro{margin:0 0 14px}" +
".mccc-close{position:absolute;background:none;border:none;font-size:26px;" +
"line-height:1;cursor:pointer;color:#1b2330;padding:6px 10px}" +
".mccc-modal-wrap{position:relative}" +
".mccc-modal-wrap .mccc-close{top:10px;right:10px}" +
".mccc-cat{border:1px solid #d9dee3;border-radius:8px;padding:12px 14px;" +
"margin:0 0 10px}" +
".mccc-cat-head{display:flex;align-items:center;gap:12px}" +
".mccc-cat-head h3{margin:0;font-size:16px;flex:1}" +
".mccc-always{font-size:12px;font-weight:700;text-transform:uppercase;" +
"letter-spacing:.05em;color:#1f5132;background:#eef5f0;border-radius:999px;" +
"padding:3px 10px;white-space:nowrap}" +
".mccc-cat-desc{margin:8px 0 4px;color:#3d4757}" +
".mccc-services{margin:6px 0 0;padding-left:18px;color:#3d4757;font-size:14px}" +
".mccc-lock-note{margin:6px 0 0;color:#1f5132;font-size:14px;font-weight:600}" +
".mccc-switch{position:relative;display:inline-flex;align-items:center;" +
"gap:8px;flex-shrink:0}" +
".mccc-switch input{position:absolute;opacity:0;width:44px;height:24px;" +
"margin:0;cursor:pointer}" +
".mccc-switch input:disabled{cursor:not-allowed}" +
".mccc-switch span.mccc-track{width:44px;height:24px;border-radius:999px;" +
"background:#8a94a3;transition:background .15s;display:inline-block}" +
".mccc-switch span.mccc-track::after{content:'';display:block;width:18px;" +
"height:18px;border-radius:50%;background:#fff;margin:3px;" +
"transition:transform .15s}" +
".mccc-switch input:checked+span.mccc-track{background:#007A33}" +
".mccc-switch input:checked+span.mccc-track::after{transform:translateX(20px)}" +
".mccc-switch input:disabled+span.mccc-track{opacity:.55}" +
".mccc-state{font-size:13px;font-weight:700;color:#3d4757;min-width:2.2em}" +
"@media (prefers-reduced-motion:reduce){.mccc-switch span.mccc-track," +
".mccc-switch span.mccc-track::after{transition:none}}" +
".mccc-modal-actions{display:flex;gap:10px;flex-wrap:wrap;margin-top:16px}";
var bannerEl = null, overlayEl = null, tabEl = null, lastFocus = null;
function el(tag, attrs, children) {
var node = doc.createElement(tag);
if (attrs) Object.keys(attrs).forEach(function (k) {
if (k === "text") node.textContent = attrs[k];
else if (k === "html") node.innerHTML = attrs[k];
else node.setAttribute(k, attrs[k]);
});
(children || []).forEach(function (c) { node.appendChild(c); });
return node;
}
function removeBanner() {
if (bannerEl && bannerEl.parentNode) bannerEl.parentNode.removeChild(bannerEl);
bannerEl = null;
showTab();
}
function showTab() {
if (!CONFIG.floatingTab || tabEl) return;
tabEl = el("button", {
"class": "mccc-tab", type: "button", text: CONFIG.text.choicesLabel,
"aria-haspopup": "dialog",
});
tabEl.addEventListener("click", openModal);
doc.body.appendChild(tabEl);
}
function buildBanner() {
var t = CONFIG.text;
var locked = emLocked;
var msg = el("p", { "class": "mccc-banner-text" });
msg.appendChild(el("strong", { text: t.bannerTitle }));
msg.appendChild(doc.createTextNode(
(locked ? t.gpcBannerBody : t.bannerBody) + " " + t.policyPre));
msg.appendChild(el("a", {
href: CONFIG.privacyPolicyUrl, text: t.policyLinkLabel,
target: "_blank", rel: "noopener",
}));
msg.appendChild(doc.createTextNode(t.policyPost));
var actions = el("div", { "class": "mccc-actions" });
// Accept/Continue is an ACKNOWLEDGMENT, not a recorded consent:
// defaults simply apply, and GPC keeps governing for signal-senders.
// The real switch lives in the panel (Review details).
var main = el("button", { "class": "mccc-btn", type: "button",
text: locked ? t.continueLabel : t.acceptLabel });
main.addEventListener("click", function () {
acknowledgeOnly(); removeBanner();
});
actions.appendChild(main);
var review = el("button", {
"class": "mccc-btn mccc-btn--ghost", type: "button",
text: t.reviewLabel, "aria-haspopup": "dialog",
});
review.addEventListener("click", openModal);
actions.appendChild(review);
bannerEl = el("div", {
"class": "mccc-banner", role: "region", "aria-label": t.bannerTitle,
}, [el("div", { "class": "mccc-banner-inner" }, [msg, actions])]);
doc.body.appendChild(bannerEl);
}
function closeModal() {
if (overlayEl && overlayEl.parentNode) overlayEl.parentNode.removeChild(overlayEl);
overlayEl = null;
if (lastFocus && lastFocus.focus) lastFocus.focus();
}
function openModal() {
if (overlayEl) return;
lastFocus = doc.activeElement;
var t = CONFIG.text;
var wrap = el("div", { "class": "mccc-modal-wrap" });
var dialog = el("div", {
"class": "mccc-modal", role: "dialog", "aria-modal": "true",
"aria-labelledby": "mccc-title",
}, [wrap]);
wrap.appendChild(el("h2", { id: "mccc-title", text: t.modalTitle }));
var close = el("button", {
"class": "mccc-close", type: "button", "aria-label": t.closeLabel,
html: "×",
});
close.addEventListener("click", closeModal);
wrap.appendChild(close);
wrap.appendChild(el("p", { "class": "mccc-modal-intro", text: t.modalIntro }));
var emInput = null;
CONFIG.categories.forEach(function (c) {
var head = el("div", { "class": "mccc-cat-head" });
head.appendChild(el("h3", { id: "mccc-cat-" + c.id, text: c.label }));
if (c.locked) {
head.appendChild(el("span", { "class": "mccc-always", text: t.alwaysOn }));
} else {
var stateWord = el("span", {
"class": "mccc-state", "aria-hidden": "true",
text: em ? t.onLabel : t.offLabel,
});
emInput = el("input", {
type: "checkbox", role: "switch",
"aria-labelledby": "mccc-cat-" + c.id,
});
emInput.checked = em;
if (emLocked) emInput.disabled = true;
emInput.addEventListener("change", function () {
stateWord.textContent = emInput.checked ? t.onLabel : t.offLabel;
});
head.appendChild(el("label", { "class": "mccc-switch" },
[emInput, el("span", { "class": "mccc-track" }), stateWord]));
}
var box = el("div", { "class": "mccc-cat" }, [head]);
box.appendChild(el("p", { "class": "mccc-cat-desc", text: c.description }));
if (!c.locked && emLocked) {
box.appendChild(el("p", {
"class": "mccc-lock-note", text: t.gpcLockNote,
}));
}
var ul = el("ul", { "class": "mccc-services" });
c.services.forEach(function (s) {
ul.appendChild(el("li", { text: s.name + " — " + s.purpose }));
});
box.appendChild(ul);
wrap.appendChild(box);
});
var save = el("button", { "class": "mccc-btn", type: "button",
text: t.saveLabel });
save.addEventListener("click", function () {
if (emLocked) acknowledgeOnly();
else saveChoice(!!(emInput && emInput.checked));
closeModal();
removeBanner();
});
wrap.appendChild(el("div", { "class": "mccc-modal-actions" }, [save]));
overlayEl = el("div", { "class": "mccc-overlay" }, [dialog]);
overlayEl.addEventListener("mousedown", function (e) {
if (e.target === overlayEl) closeModal();
});
dialog.addEventListener("keydown", function (e) {
if (e.key === "Escape") { e.preventDefault(); closeModal(); return; }
if (e.key !== "Tab") return;
var focusables = dialog.querySelectorAll(
"button, input:not(:disabled), a[href], [tabindex]:not([tabindex='-1'])");
if (!focusables.length) return;
var first = focusables[0], last = focusables[focusables.length - 1];
if (e.shiftKey && doc.activeElement === first) {
e.preventDefault(); last.focus();
} else if (!e.shiftKey && doc.activeElement === last) {
e.preventDefault(); first.focus();
}
});
doc.body.appendChild(overlayEl);
close.focus();
}
function initUI() {
var style = doc.createElement("style");
style.textContent = CSS;
doc.head.appendChild(style);
activateGated();
Array.prototype.forEach.call(
doc.querySelectorAll("[data-mccc-open]"),
function (n) { n.addEventListener("click", function (e) {
e.preventDefault(); openModal();
}); });
if (!stored || !stored.ack) buildBanner();
else showTab();
}
if (doc.readyState === "loading") {
doc.addEventListener("DOMContentLoaded", initUI);
} else {
initUI();
}
// Public API (stable): window.91¸£ÀûÉçConsent
win.91¸£ÀûÉçConsent = {
version: CONFIG.policyVersion,
open: openModal,
state: function () {
return {
basicAnalytics: true,
enhancedMeasurement: em,
gpcSignal: gpcSignal,
gpcHonored: gpcSignal && !em, // derived live, never stored
acknowledged: !!(stored && stored.ack),
};
},
};
})();