/* =========================================================================== Arizona Computer Guru, Sonoran Ledger (multipage) Shared vanilla JS across all pages. Each block guards on its own DOM. =========================================================================== */ (function () { "use strict"; var $ = function (s, c) { return (c || document).querySelector(s); }; var $$ = function (s, c) { return Array.prototype.slice.call((c || document).querySelectorAll(s)); }; /* ---- Theme ------------------------------------------------------------ */ var root = document.documentElement; var toggle = $("#themeToggle"); var icon = $("[data-theme-icon]"); var STORE = "acg-theme"; function applyTheme(mode) { root.setAttribute("data-theme", mode); var dark = mode === "dark"; if (toggle) { toggle.setAttribute("aria-pressed", String(dark)); toggle.setAttribute("aria-label", dark ? "Switch to light theme" : "Switch to dark theme"); } if (icon) icon.innerHTML = dark ? "☾" : "☀"; } var saved = null; try { saved = localStorage.getItem(STORE); } catch (e) {} var prefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches; applyTheme(saved || (prefersDark ? "dark" : "light")); if (toggle) { toggle.addEventListener("click", function () { var next = root.getAttribute("data-theme") === "dark" ? "light" : "dark"; applyTheme(next); try { localStorage.setItem(STORE, next); } catch (e) {} }); } if (window.matchMedia) { var mq = window.matchMedia("(prefers-color-scheme: dark)"); var onChange = function (e) { var explicit = null; try { explicit = localStorage.getItem(STORE); } catch (err) {} if (!explicit) applyTheme(e.matches ? "dark" : "light"); }; if (mq.addEventListener) mq.addEventListener("change", onChange); else if (mq.addListener) mq.addListener(onChange); } /* ---- Skin (Paper / Midnight / Verdigris) ----------------------------- */ var skinToggle = $("#skinToggle"); var SKIN = "acg-skin"; var SKINS = ["ledger", "midnight", "verdigris", "bold"]; var SKIN_NAME = { ledger: "Paper", midnight: "Midnight", verdigris: "Verdigris", bold: "Bold" }; // Verdigris uses its own cooler documentary photography. var VERDIGRIS_IMG = { "assets/images/hero.png": "assets/images/verdigris/hero.png", "assets/images/about.png": "assets/images/verdigris/about.png", "assets/images/services.png": "assets/images/verdigris/services.png", "assets/images/contact.png": "assets/images/verdigris/contact.png", "assets/images/story.png": "assets/images/verdigris/contact.png" }; function swapSkinImages(skin) { $$("img").forEach(function (img) { var orig = img.getAttribute("data-orig-src"); if (orig === null) { orig = img.getAttribute("src"); img.setAttribute("data-orig-src", orig); } if (skin === "verdigris" && VERDIGRIS_IMG[orig]) img.setAttribute("src", VERDIGRIS_IMG[orig]); else img.setAttribute("src", orig); }); } function applySkin(skin) { if (SKINS.indexOf(skin) < 0) skin = "ledger"; root.setAttribute("data-skin", skin); swapSkinImages(skin); if (skinToggle) { var name = SKIN_NAME[skin]; skinToggle.setAttribute("aria-label", "Current skin: " + name + ". Switch."); skinToggle.setAttribute("title", "Skin: " + name + " (click to switch)"); } } var savedSkin = "ledger"; try { savedSkin = localStorage.getItem(SKIN) || "ledger"; } catch (e) {} applySkin(savedSkin); if (skinToggle) { skinToggle.addEventListener("click", function () { var cur = SKINS.indexOf(root.getAttribute("data-skin")); var next = SKINS[(cur + 1) % SKINS.length]; applySkin(next); try { localStorage.setItem(SKIN, next); } catch (e) {} }); } /* ---- Dynamic year ----------------------------------------------------- */ var yr = $("#year"); if (yr) yr.textContent = String(new Date().getFullYear()); /* ---- Mobile nav ------------------------------------------------------- */ var header = $(".site-header"); var navToggle = $("#navToggle"); if (navToggle && header) { var closeNav = function () { header.classList.remove("nav-open"); navToggle.setAttribute("aria-expanded", "false"); navToggle.setAttribute("aria-label", "Open menu"); }; navToggle.addEventListener("click", function () { var open = header.classList.toggle("nav-open"); navToggle.setAttribute("aria-expanded", String(open)); navToggle.setAttribute("aria-label", open ? "Close menu" : "Open menu"); }); $$("#navLinks a").forEach(function (a) { a.addEventListener("click", closeNav); }); document.addEventListener("keydown", function (e) { if (e.key === "Escape") closeNav(); }); } /* ---- Money helper ----------------------------------------------------- */ function money(n) { return "$" + Math.round(n).toLocaleString("en-US"); } /* ---- Calculator (calculator.html) ------------------------------------ */ var form = $("#calcForm"); if (form) { var EQUIP = 25, M365_RATE = 14, VOIP_RATE = 28; function clampInt(v) { if (isNaN(v) || v < 0) return 0; return v > 500 ? 500 : v; } function intVal(id) { return clampInt(parseInt(($("#" + id) || {}).value, 10)); } function numSelect(id) { var v = parseFloat(($("#" + id) || {}).value); return isNaN(v) ? 0 : v; } function lineRow(name, detail, cost) { return '
' + '' + name + (detail ? ' ' + detail + "" : "") + "" + '' + money(cost) + "
"; } function recalc() { var endpoints = intVal("endpoints"); var tier = numSelect("gpsTier"); var equip = $("#equip").checked; var support = numSelect("support"); var m365 = intVal("m365"); var voip = intVal("voip"); var hosting = numSelect("hosting"); var gpsCost = endpoints * tier; var equipCost = equip ? EQUIP : 0; var m365Cost = m365 * M365_RATE; var voipCost = voip * VOIP_RATE; var tierName = tier === 19 ? "GPS-Basic" : tier === 39 ? "GPS-Advanced" : "GPS-Pro"; var supportName = support === 0 ? "" : support === 200 ? "Essential" : support === 380 ? "Standard" : support === 540 ? "Premium" : "Priority"; var hostingName = hosting === 0 ? "" : hosting === 15 ? "Starter" : hosting === 35 ? "Business" : "Commerce"; var lines = ""; lines += lineRow(tierName + " monitoring", endpoints + " × " + money(tier), gpsCost); lines += lineRow("Equipment monitoring", "up to 10 devices", equipCost); lines += lineRow((supportName || "Support plan") + " support", supportName ? "bundled labor" : "none", support); lines += lineRow("Microsoft 365", m365 + " × " + money(M365_RATE), m365Cost); lines += lineRow("Business phones", voip + " × " + money(VOIP_RATE), voipCost); lines += lineRow((hostingName || "Web hosting") + " hosting", hostingName ? "managed" : "none", hosting); var total = gpsCost + equipCost + support + m365Cost + voipCost + hosting; $("#ledgerLines").innerHTML = lines; $("#totalMonthly").textContent = money(total); $("#totalAnnual").textContent = money(total * 12) + " / year"; $("#perEndpoint").innerHTML = endpoints > 0 ? money(total / endpoints) + " all-in, per endpoint / mo" : "add endpoints to see per-seat cost"; } $$(".stepper").forEach(function (st) { var input = $("input", st); $$("button", st).forEach(function (b) { b.addEventListener("click", function () { input.value = clampInt(parseInt(input.value, 10) + parseInt(b.getAttribute("data-dir"), 10)); recalc(); }); }); input.addEventListener("change", function () { input.value = clampInt(parseInt(input.value, 10)); }); }); // Carry the built estimate across to the contact page. var sendBtn = $("#sendEstimate"); if (sendBtn) { var storeEstimate = function () { var lines = $$("#ledgerLines .lline") .filter(function (l) { return !l.classList.contains("is-zero"); }) .map(function (l) { return "- " + $(".lname", l).textContent.replace(/\s+/g, " ").trim() + ": " + $(".lcost", l).textContent.trim(); }); var summary = "Here is the estimate I built:\n" + lines.join("\n") + "\n\nMonthly: " + $("#totalMonthly").textContent + " (" + $("#totalAnnual").textContent + ")\n\nI'd like to talk it through."; try { sessionStorage.setItem("acg-estimate", summary); } catch (e) {} }; // 'click' covers keyboard + left-click; 'pointerdown' also catches // middle-click / cmd-click / open-in-new-tab, which never fire 'click'. sendBtn.addEventListener("click", storeEstimate); sendBtn.addEventListener("pointerdown", storeEstimate); } form.addEventListener("input", recalc); form.addEventListener("change", recalc); form.addEventListener("submit", function (e) { e.preventDefault(); }); recalc(); } /* ---- FAQ accordion (contact.html) ------------------------------------ */ $$(".faq__q").forEach(function (q, i) { var panel = q.nextElementSibling; var inner = panel ? panel.firstElementChild : null; if (panel) { var pid = "faq-a-" + (i + 1), qid = "faq-q-" + (i + 1); panel.id = pid; q.id = qid; panel.setAttribute("role", "region"); panel.setAttribute("aria-labelledby", qid); q.setAttribute("aria-controls", pid); } q.addEventListener("click", function () { var open = q.getAttribute("aria-expanded") === "true"; q.setAttribute("aria-expanded", String(!open)); if (panel) panel.style.maxHeight = open ? "0px" : (inner.scrollHeight + 8) + "px"; }); }); window.addEventListener("resize", function () { $$(".faq__q").forEach(function (q) { if (q.getAttribute("aria-expanded") === "true") { var panel = q.nextElementSibling, inner = panel ? panel.firstElementChild : null; if (panel && inner) panel.style.maxHeight = (inner.scrollHeight + 8) + "px"; } }); }); /* ---- Contact form (contact.html) ------------------------------------- */ var contact = $("#contactForm"); if (contact) { // Prefill from a calculator estimate, if one was just built. var msg = $("#cf-msg"); if (msg) { try { var est = sessionStorage.getItem("acg-estimate"); if (est) { msg.value = est; sessionStorage.removeItem("acg-estimate"); var fn = $("#formNote"); if (fn) fn.textContent = "Your estimate is attached below. Add your name and we'll take it from there."; // Bring the form into view and focus it so the handoff is visible. if (contact.scrollIntoView) contact.scrollIntoView({ behavior: "smooth", block: "start" }); var nameField = $("#cf-name"); if (nameField) nameField.focus({ preventScroll: true }); } } catch (e) {} } contact.addEventListener("submit", function (e) { e.preventDefault(); var name = $("#cf-name"), contactField = $("#cf-contact"), note = $("#formNote"); if (!name.value.trim() || !contactField.value.trim()) { note.textContent = "Please add your name and a phone or email so we can reach you."; note.style.color = "var(--accent-ink)"; (name.value.trim() ? contactField : name).focus(); return; } note.textContent = "Thanks, " + name.value.trim().split(" ")[0] + ". In a live build this reaches our Tucson team. (Demo: nothing was sent.)"; note.style.color = "var(--good)"; contact.reset(); }); } /* ---- Reveal on scroll ------------------------------------------------- */ var reveals = $$(".reveal"); if ("IntersectionObserver" in window && reveals.length) { var io = new IntersectionObserver(function (entries) { entries.forEach(function (en) { if (en.isIntersecting) { en.target.classList.add("in"); io.unobserve(en.target); } }); }, { rootMargin: "0px 0px -8% 0px", threshold: 0.08 }); reveals.forEach(function (el) { io.observe(el); }); } else { reveals.forEach(function (el) { el.classList.add("in"); }); } })();