Technology That Works For You.

Comprehensive managed IT services, cloud infrastructure, and cybersecurity for South Carolina businesses. We handle the technology so you can run your business — proactively, not reactively.

PDK Platinum Partner Control4 Gold Dealer
NVR
Network video recorder
RAID protected · AI-tagged · 30–90 day retention
Move mouse quickly to transform
Scroll to explore
The Problem

Technology Problems Stop Businesses Cold.

Most businesses and properties operate on a patchwork of physical keys, pin codes, and access cards that cannot be revoked instantly, cannot be audited in real-time, and cannot adapt when someone leaves the organisation. Traditional access control is reactive. Cloud-based PDK access control is intelligent — giving you total visibility and instant control from anywhere in the world.

PDK CLOUD Legacy Keys
Our Approach

Complete IT Management. One Partner.

Three pillars of managed IT that cover your entire technology stack — from infrastructure and cloud to cybersecurity and user support.

Server infrastructure management
01

Infrastructure & Cloud

On-premise server management, Microsoft 365, Azure, and AWS deployment. Business-critical systems monitored, patched, and optimised around the clock. We migrate businesses to cloud infrastructure that reduces capital expenditure, improves resilience, and scales with growth.

Microsoft Partner · Azure · AWS · M365
Cybersecurity protection
02

Cybersecurity

Endpoint detection and response, email filtering, multi-factor authentication, and security awareness training. Layered security that protects your business and your clients — built into every system we manage, not added as an afterthought.

EDR · MFA · Email Security · Awareness Training
IT help desk support
03

Help Desk & User Support

Fast, knowledgeable remote and on-site support for your team. Engineers who know your environment resolve issues on the first contact — not after three escalations to someone reading from a script. User onboarding, offboarding, and device management included.

Remote Support · On-Site SC · First-Call Resolution
What We Deliver

Everything Your Business Technology Requires.

01
Infrastructure Management

Servers and Cloud Managed Proactively

Physical and virtual server monitoring, patch management, and performance optimisation. Microsoft 365, Azure, and AWS administration with licence management, security configuration, and cost optimisation. Your infrastructure stays current and available without your involvement.

  • 24/7 server and cloud monitoring with automated alerts
  • Monthly security patching and firmware updates
  • Microsoft 365 and Azure administration
  • Storage management and capacity planning
Server infrastructure
02
Cybersecurity

Multi-Layer Protection Built Into Every System

Endpoint detection and response on every managed device. Email filtering and anti-phishing. Multi-factor authentication enforced across all accounts. Annual security awareness training for your staff. Vulnerability scanning and monthly security reporting included as standard.

  • Endpoint detection and response (EDR) on all managed devices
  • Email filtering, anti-phishing, and impersonation protection
  • MFA deployment and enforcement across all accounts
  • Annual security awareness training for all staff
Cybersecurity monitoring
03
Backup & Recovery

Your Data Protected and Recoverable

Automated daily backups with tested recovery procedures. Local and cloud redundancy — your data is never in only one place. Recovery time objectives measured in hours, not days. We test restores regularly so you are never surprised when recovery is needed most.

  • Automated daily backups with verified restore testing
  • Local and cloud-redundant backup storage
  • Ransomware-resistant immutable backup copies
  • Documented disaster recovery plan with tested RTO/RPO
Backup and recovery
04
Help Desk & Support

Real Engineers Who Know Your Environment

Remote support tickets acknowledged within 30 minutes and resolved by engineers who know your specific setup. On-site support across South Carolina for issues that require physical attendance. User onboarding, offboarding, device provisioning, and software management handled end-to-end.

  • Remote support acknowledged within 30 minutes
  • On-site response across South Carolina
  • User onboarding and device provisioning
  • Software licence management and deployment
IT help desk

Why MK Technology

The IT Partner Your Business Can Rely On.

01

Certified Engineers

Microsoft, CompTIA, and Cisco certified engineers with genuine expertise across the full technology stack. Not generalists who do a little of everything. Specialists who know the platforms your business runs on.

Microsoft · CompTIA · Cisco Certified
02

Local & Accountable

South Carolina-based team. We are not a national call centre. When your business has a problem, you speak to an engineer who knows your environment and responds in person when required — same day or next business day.

South Carolina Based · Local Response
03

Full Transparency

Monthly reports covering system health, ticket volumes, security events, and forward-looking recommendations. You always know what we are doing and why. No black box IT management — complete visibility into your technology estate.

Monthly Reporting Included
04

Proactive, Not Reactive

We monitor, patch, and optimise your systems continuously. Our goal is to resolve issues before your team experiences them — not to react after something breaks and the business is already down.

24/7 Proactive Monitoring

MK Technology didn't just install an access system — they transformed how our entire facility is managed. Zero unauthorised entries since day one. That speaks for itself.

David Harrington Owner, Store-It-All — Myrtle Beach, SC
Google Review

The PDK platform is genuinely remarkable. Managing 200 users across three buildings is now something I do from my phone in under a minute. It just works.

Christine & Robert Ellison Homeowners — DeBordieu Colony, SC
Google Review

We were sceptical about moving away from traditional key cards. Now our entire team uses their phones. Zero complaints, zero lockouts, zero lost credentials.

Chief James Thornton Fire Chief — Murrells Inlet Fire Department
Google Review

I called at 8:45 on a Friday evening — a door wouldn't lock before a holiday weekend. Someone answered, someone showed up, and the system was secured before we closed.

Margaret Fontaine Homeowner — Pawleys Island, SC
Google Review

From the front gate to every internal door — every credential works perfectly across every location. The team treated our campus as if it were their own facility.

Thomas & Sandra Whitmore Property Managers — NMB RV Park & Resort
Google Review
5.0
Google Reviews
02 / 05
Investment Calculator

What Is Unreliable IT Actually Costing Your Business?

Adjust the sliders to your business. We'll estimate the annual cost of IT downtime and support overhead your team is absorbing right now.

5 25 100 500
2 8 30 100
0.5hr 6hrs 12hrs 20hrs
$15 $45 $90 $150
Your Annual IT Cost Exposure
$1,890,048 per year
Annual downtime productivity cost $48 /yr
IT support overhead cost $1,890,000 /yr
Cybersecurity incident risk (avg.) +$1,125
Managed IT monthly cost (est.) $20K–$32K
Estimate confidence
Get a Free IT Assessment

Based on industry IT downtime and cybersecurity incident benchmarks. Estimates only — actual costs vary by industry and business profile.

Common Questions

Frequently Asked Questions

How is managed IT different from break-fix support?
Break-fix means you call someone when something breaks and pay per incident. Managed IT means a partner monitors your systems continuously, prevents problems from occurring, and resolves issues often before you notice them. You pay a predictable monthly fee for comprehensive management rather than unpredictable invoices for emergency repairs.
Do you replace our existing IT staff or work alongside them?
Either. Some clients have no internal IT resource and we act as their complete IT department. Others have an IT manager or team and we provide a specialist managed layer for infrastructure, security, and complex issues. We adapt to your existing structure and fill the gaps that make the most sense for your business.
How quickly do you respond to support requests?
Remote support tickets are acknowledged within 30 minutes and resolved by a qualified engineer within the SLA defined for the issue priority. Critical business system outages receive immediate response. For issues requiring on-site attendance, we provide same-day or next-business-day response across our South Carolina service area.
What cybersecurity protection is included?
Our standard managed IT service includes endpoint detection and response on all managed devices, email filtering and anti-phishing, multi-factor authentication deployment, monthly security patching, and annual security awareness training for your staff. Advanced packages include vulnerability scanning and SIEM monitoring.
Can you help us migrate to Microsoft 365 or cloud services?
Yes. Cloud migration is one of our most common engagements. We plan and execute migrations from on-premise Exchange, file servers, and legacy systems to Microsoft 365, Azure, and cloud storage. Migrations are planned to minimise disruption and include staff training so your team is productive from day one.
What is included in your monthly reporting?
Monthly reports cover system health scores for servers, network, and endpoints; ticket volumes and resolution times; security events and patch compliance; storage capacity trends; and a recommendations section. Quarterly business reviews give your leadership team a strategic view of your technology estate and roadmap.
What areas of South Carolina do you serve?
We serve the entire state including Pawleys Island, Myrtle Beach, Charleston, Mount Pleasant, Georgetown, Summerville, Columbia, Greenville, and Fort Mill. If your project is in South Carolina, we can help.
How much does managed IT cost?
Managed IT is typically priced per user per month. For a 25-user business our all-inclusive plans including monitoring, patching, cybersecurity, help desk, and monthly reporting start from around $1,500 per month. Pricing scales with user count, infrastructure complexity, and the level of support required. We provide a fixed-price proposal after an initial assessment.
Get Started Today

Your Business Deserves IT That Just Works.

Schedule a free IT assessment. We review your current infrastructure, identify risks and gaps, and present a clear managed service proposal.

(843) 286-5425
' + '
' + sd.detail + '
' + '
' + sd.spec + '
'; } else { shapeLabelInner.textContent = name; } shapeLabel.classList.remove('fading'); shapeLabel.classList.add('visible'); }, 350); } else { shapeLabel.classList.add('fading'); setTimeout(() => shapeLabel.classList.remove('visible'), 600); } } const clock = new THREE.Clock(); let accTime = 0, heroInView = true; const heroObs = new IntersectionObserver( (entries) => { heroInView = entries[0].isIntersecting; }, { threshold: 0 } ); heroObs.observe(document.querySelector('.hero')); // Show shape label on desktop only if (!isMobile) { document.querySelector('.shape-label').style.display = 'block'; } (function animate() { requestAnimationFrame(animate); if (!heroInView) return; const dt = Math.min(clock.getDelta(), 0.05); accTime += dt; morphTimer += dt; mouseVelocity *= 0.88; if (mouseVelocity < 10) mouseVelocity = 0; switch (morphState) { case 'SWARM': morphProgress = 0; scatterPhase = 0; tLifeAmplitude = 0; material.uniforms.uMouseRepel.value = 0; if (morphTimer >= 0) { const ni = (currentShapeIndex + 1) % shapeData.length; loadShape(ni); morphState = 'MORPH_IN'; morphTimer = 0; setShapeLabel(shapeData[ni].name, true); } break; case 'MORPH_IN': morphProgress = Math.min(morphTimer / MORPH_IN_DURATION, 1.0); tLifeAmplitude = morphProgress * 0.32; tMouseRepel = morphProgress; if (morphProgress >= 1.0) { morphState = 'SHAPE_HELD'; morphTimer = 0; } break; case 'SHAPE_HELD': morphProgress = 1.0; tMouseRepel = 1.0; tLifeAmplitude = 0.32; if (mouseVelocity > VELOCITY_THRESHOLD) { morphState = 'SCATTER'; morphTimer = 0; setShapeLabel('', false); } break; case 'SCATTER': const t2 = morphTimer / SCATTER_DURATION; scatterPhase = t2 < 0.5 ? t2 * 2 : (1 - t2) * 2; morphProgress = 1.0 - t2; tMouseRepel = 0; tLifeAmplitude = 0; if (morphTimer >= SCATTER_DURATION) { const ni = (currentShapeIndex + 1) % shapeData.length; loadShape(ni); morphState = 'MORPH_IN'; morphTimer = 0; setShapeLabel(shapeData[ni].name, true); } break; } material.uniforms.uTime.value = accTime; material.uniforms.uMorphProgress.value += (morphProgress - material.uniforms.uMorphProgress.value) * 0.06; material.uniforms.uScatterStrength.value += (scatterPhase - material.uniforms.uScatterStrength.value) * 0.12; material.uniforms.uMouseRepel.value += (tMouseRepel - material.uniforms.uMouseRepel.value) * 0.08; material.uniforms.uGravityStrength.value += (tGrav - material.uniforms.uGravityStrength.value) * 0.1; material.uniforms.uLifeAmplitude.value += (tLifeAmplitude - material.uniforms.uLifeAmplitude.value) * 0.025; const BASE_Y = 0.38, BASE_X = 0.12, oscRange = 0.1, oscSpeed = 0.18; const targetY = BASE_Y + Math.sin(accTime * oscSpeed) * oscRange * (1.0 - material.uniforms.uMorphProgress.value * 0.7); swarm.rotation.y += (targetY - swarm.rotation.y) * 0.015; swarm.rotation.x += (BASE_X - swarm.rotation.x) * 0.015; renderer.render(scene, camera); })(); window.addEventListener('resize', () => { renderer.setSize(window.innerWidth, window.innerHeight); camera.aspect = window.innerWidth / window.innerHeight; camera.updateProjectionMatrix(); }); })(); // ═══════════════════════════════════════════ // // GALLERY LIGHTBOX // ═══════════════════════════════════════════ (function () { const backdrop = document.getElementById('lbBackdrop'); const closeBtn = document.getElementById('lbClose'); const img = document.getElementById('lbImg'); const cat = document.getElementById('lbCat'); const title = document.getElementById('lbTitle'); const cap = document.getElementById('lbCap'); const desc = document.getElementById('lbDesc'); const thumbs = document.getElementById('lbThumbs'); const prev = document.getElementById('lbPrev'); const next = document.getElementById('lbNext'); let imgs = [], cur = 0; function open(data) { imgs = data.images; cur = 0; cat.textContent = data.category; title.textContent = data.title; desc.textContent = data.desc; thumbs.innerHTML = ''; imgs.forEach((im, i) => { const d = document.createElement('div'); d.className = 'lb-thumb' + (i === 0 ? ' active' : ''); const im2 = document.createElement('img'); im2.src = im.src; im2.alt = im.caption; d.appendChild(im2); d.addEventListener('click', () => setImg(i)); thumbs.appendChild(d); }); setImg(0); backdrop.classList.add('open'); document.body.style.overflow = 'hidden'; } function setImg(i) { cur = ((i % imgs.length) + imgs.length) % imgs.length; img.style.opacity = '0'; setTimeout(() => { img.src = imgs[cur].src; img.style.opacity = '1'; }, 150); img.style.transition = 'opacity 0.15s'; cap.textContent = imgs[cur].caption; document.querySelectorAll('.lb-thumb').forEach((t, j) => t.classList.toggle('active', j === cur)); } function close() { backdrop.classList.remove('open'); document.body.style.overflow = ''; } document.querySelectorAll('.gallery-card').forEach((card) => { card.addEventListener('click', () => { const d = JSON.parse(card.getAttribute('data-gal')); open(d); }); }); closeBtn.addEventListener('click', close); backdrop.addEventListener('click', (e) => { if (e.target === backdrop) close(); }); prev.addEventListener('click', (e) => { e.stopPropagation(); setImg(cur - 1); }); next.addEventListener('click', (e) => { e.stopPropagation(); setImg(cur + 1); }); document.addEventListener('keydown', (e) => { if (!backdrop.classList.contains('open')) return; if (e.key === 'Escape') close(); if (e.key === 'ArrowLeft') setImg(cur - 1); if (e.key === 'ArrowRight') setImg(cur + 1); }); })(); // TESTIMONIALS CAROUSEL (homepage design) (function () { const TOTAL = 5; const AUTO_MS = 7000; const track = document.getElementById('tmTrack'); const pips = Array.from(document.querySelectorAll('.tm-pip')); const prevBtn = document.getElementById('tmPrev'); const nextBtn = document.getElementById('tmNext'); const counter = document.getElementById('tmCounter'); let curr = 0, timer = null, busy = false; function goto(idx, animate) { if (busy && animate) return; busy = true; curr = ((idx % TOTAL) + TOTAL) % TOTAL; track.style.transition = animate ? 'transform 0.75s cubic-bezier(0.4,0,0.2,1)' : 'none'; track.style.transform = `translateX(-${curr * 100}%)`; pips.forEach((p, i) => p.classList.toggle('tm-pip--on', i === curr)); counter.textContent = String(curr + 1).padStart(2, '0') + ' / ' + String(TOTAL).padStart(2, '0'); setTimeout(() => { busy = false; }, 800); } function startAuto() { clearInterval(timer); timer = setInterval(() => goto((curr + 1) % TOTAL, true), AUTO_MS); } prevBtn.addEventListener('click', () => { goto((curr - 1 + TOTAL) % TOTAL, true); startAuto(); }); nextBtn.addEventListener('click', () => { goto((curr + 1) % TOTAL, true); startAuto(); }); pips.forEach((p) => p.addEventListener('click', () => { goto(parseInt(p.dataset.goto), true); startAuto(); }) ); // Swipe support let tx0 = 0; track.addEventListener( 'touchstart', (e) => { tx0 = e.touches[0].clientX; }, { passive: true } ); track.addEventListener( 'touchend', (e) => { const dx = e.changedTouches[0].clientX - tx0; if (Math.abs(dx) > 48) { goto(dx < 0 ? (curr + 1) % TOTAL : (curr - 1 + TOTAL) % TOTAL, true); startAuto(); } }, { passive: true } ); const section = document.querySelector('.tm-section'); if (section) { section.addEventListener('mouseenter', () => clearInterval(timer)); section.addEventListener('mouseleave', startAuto); } goto(0, false); startAuto(); })(); // ═══════════════════════════════════════════ // // ROI CALCULATOR // ═══════════════════════════════════════════ (function () { const sliders = [ { sl: document.getElementById('sSize'), fill: document.getElementById('fSize'), thumb: document.getElementById('tSize'), }, { sl: document.getElementById('sSys'), fill: document.getElementById('fSys'), thumb: document.getElementById('tSys'), }, { sl: document.getElementById('sEnergy'), fill: document.getElementById('fEnergy'), thumb: document.getElementById('tEnergy'), }, { sl: document.getElementById('sHome'), fill: document.getElementById('fHome'), thumb: document.getElementById('tHome'), }, ]; function track(sl, fill, thumb) { const pct = ((sl.value - sl.min) / (sl.max - sl.min)) * 100; fill.style.width = pct + '%'; if (thumb) thumb.style.left = pct + '%'; } function fmt(n) { return '$' + Math.round(n).toLocaleString(); } function calc() { const doors = +sliders[0].sl.value; const users = +sliders[1].sl.value; const cardM = +sliders[2].sl.value; const homeK = +sliders[3].sl.value; sliders.forEach((s) => track(s.sl, s.fill, s.thumb)); document.getElementById('vSize').textContent = doors; document.getElementById('vSys').textContent = users; document.getElementById('vEnergy').textContent = cardM; const hv = homeK / 1000; document.getElementById('vHome').textContent = hv >= 1 ? hv.toFixed(1) : homeK + 'K'; // Access control ROI const cardSaving = Math.round(cardM * 12 * 0.65); // replace physical cards const adminHrs = Math.round(users * 0.9 + doors * 1.2); // hrs saved annually const upliftPct = Math.min(0.01 + doors * 0.0008, 0.025); const propUplift = homeK * 1000 * upliftPct; const costBase = 800 + doors * 900 + users * 4; const costLow = Math.round((costBase * 0.85) / 500) * 500; const costHigh = Math.round((costBase * 1.35) / 500) * 500; const annReturn = cardSaving + adminHrs * 45 + propUplift * 0.1; const payback = annReturn > 0 ? (costLow + costHigh) / 2 / annReturn : 99; const conf = Math.min(52 + doors * 2 + Math.min((users / 500) * 20, 20), 94); document.getElementById('rPayback').innerHTML = (payback > 20 ? '20+' : payback.toFixed(1)) + ' yrs payback'; document.getElementById('rEnergy').textContent = fmt(cardSaving); document.getElementById('rTime').textContent = adminHrs; document.getElementById('rProp').textContent = '+' + fmt(propUplift); document.getElementById('rCost').textContent = '$' + (costLow / 1000).toFixed(0) + 'K–$' + (costHigh / 1000).toFixed(0) + 'K'; document.getElementById('rConf').style.width = conf + '%'; } function initROI() { sliders[0].sl = document.getElementById('sSize'); sliders[0].fill = document.getElementById('fSize'); sliders[1].sl = document.getElementById('sSys'); sliders[1].fill = document.getElementById('fSys'); sliders[2].sl = document.getElementById('sEnergy'); sliders[2].fill = document.getElementById('fEnergy'); sliders[3].sl = document.getElementById('sHome'); sliders[3].fill = document.getElementById('fHome'); if (!sliders[0].sl) return; sliders.forEach((s) => s.sl.addEventListener('input', calc)); calc(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', initROI); } else { initROI(); } })(); // ═══════════════════════════════════════════ // CHALLENGE HUB INFOGRAPHIC — animated // ═══════════════════════════════════════════ (function () { const ns = 'http://www.w3.org/2000/svg'; const CX = 250, CY = 250, R = 185; const nodes = [ { id: 'nc0', iconId: 'ni0', angle: 0, icon: 'M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z', }, { id: 'nc1', iconId: 'ni1', angle: Math.PI / 2, icon: 'M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z', }, { id: 'nc2', iconId: 'ni2', angle: Math.PI, icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2', }, { id: 'nc3', iconId: 'ni3', angle: (3 * Math.PI) / 2, icon: 'M12 18h.01M8 21h8a2 2 0 002-2v-2a2 2 0 00-2-2H8a2 2 0 00-2 2v2a2 2 0 002 2zM12 3v9m0 0l-3-3m3 3l3-3', }, ]; // Render icons into the icon group const iconG = document.getElementById('nodeIcons'); if (iconG) { nodes.forEach((n) => { const nx = CX + R * Math.cos(n.angle); const ny = CY + R * Math.sin(n.angle); const size = 20, ox = nx - size / 2, oy = ny - size / 2; const g = document.createElementNS(ns, 'g'); g.setAttribute('id', n.iconId); g.setAttribute('transform', `translate(${ox},${oy})`); const s = document.createElementNS(ns, 'svg'); s.setAttribute('width', size); s.setAttribute('height', size); s.setAttribute('viewBox', '0 0 24 24'); s.setAttribute('fill', 'none'); const p = document.createElementNS(ns, 'path'); p.setAttribute('d', n.icon); p.setAttribute('stroke', '#222'); p.setAttribute('stroke-width', '1.5'); p.setAttribute('stroke-linecap', 'round'); p.setAttribute('stroke-linejoin', 'round'); s.appendChild(p); g.appendChild(s); iconG.appendChild(g); }); } const arrowEl = document.getElementById('infoArrow'); const pulseRing = document.getElementById('hubPulseRing'); const nodeCircles = nodes.map((n) => document.getElementById(n.id)); const nodeIcons = nodes.map((n) => document.getElementById(n.iconId)); const PERIOD = 8000; // ms for full 360° const HIT_TOL = 0.18; // radians — how close arrow must be to trigger const litState = [false, false, false, false]; const litTimers = [null, null, null, null]; let startTime = null; let running = false; // Hub pulse: clone the ring and re-trigger animation let pulseTimer = null; function triggerHubPulse() { if (!pulseRing) return; pulseRing.classList.remove('pulsing'); // Force reflow so animation replays void pulseRing.getBoundingClientRect(); pulseRing.classList.add('pulsing'); clearTimeout(pulseTimer); pulseTimer = setTimeout(() => { pulseRing.classList.remove('pulsing'); }, 750); } const section = document.querySelector('.challenge'); const vis = new IntersectionObserver( (entries) => { running = entries[0].isIntersecting; if (running) { startTime = null; requestAnimationFrame(tick); } }, { threshold: 0.2 } ); if (section) vis.observe(section); function tick(t) { if (!running) return; if (!startTime) startTime = t; const elapsed = (t - startTime) % PERIOD; // Clockwise angle (SVG y-down = +y is down, so clockwise is +angle) const angle = (elapsed / PERIOD) * Math.PI * 2; // Arrow position on circle const ax = CX + R * Math.cos(angle); const ay = CY + R * Math.sin(angle); // Inward-pointing: arrow should face from (ax,ay) toward (CX,CY) // Direction = atan2(CY - ay, CX - ax) → points toward centre const inwardDeg = (Math.atan2(CY - ay, CX - ax) * 180) / Math.PI; if (arrowEl) { arrowEl.setAttribute('transform', `translate(${ax},${ay}) rotate(${inwardDeg + 90})`); } // Node activation check nodes.forEach((n, i) => { let diff = (((angle - n.angle) % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2); if (diff > Math.PI) diff = Math.PI * 2 - diff; const near = diff < HIT_TOL; if (near && !litState[i]) { litState[i] = true; // Light up node circle (solid orange) if (nodeCircles[i]) nodeCircles[i].classList.add('lit'); // Turn icon white if (nodeIcons[i]) { const p = nodeIcons[i].querySelector('path'); if (p) p.setAttribute('stroke', '#ffffff'); } // Pulse the central hub triggerHubPulse(); // Restore after 900ms clearTimeout(litTimers[i]); litTimers[i] = setTimeout(() => { litState[i] = false; if (nodeCircles[i]) nodeCircles[i].classList.remove('lit'); if (nodeIcons[i]) { const p = nodeIcons[i].querySelector('path'); if (p) p.setAttribute('stroke', '#222'); } }, 900); } }); requestAnimationFrame(tick); } })(); // ═══════════════════════════════════════════ // FAQ ACCORDION // ═══════════════════════════════════════════ document.querySelectorAll('.faq-q').forEach((q) => { q.addEventListener('click', function () { const item = this.parentElement; const ans = item.querySelector('.faq-a'); const open = item.classList.contains('open'); document.querySelectorAll('.faq-item.open').forEach((i) => { i.classList.remove('open'); i.querySelector('.faq-a').style.maxHeight = '0'; }); if (!open) { item.classList.add('open'); ans.style.maxHeight = ans.scrollHeight + 'px'; } }); }); // ═══════════════════════════════════════════ // TESTIMONIALS CAROUSEL // ═══════════════════════════════════════════ (function () { const TOTAL = 5; const AUTO_MS = 7000; const track = document.getElementById('tmTrack'); const pips = Array.from(document.querySelectorAll('.tm-pip')); const prevBtn = document.getElementById('tmPrev'); const nextBtn = document.getElementById('tmNext'); const counter = document.getElementById('tmCounter'); let curr = 0, timer = null, busy = false; function goto(idx, animate) { if (busy && animate) return; busy = true; curr = ((idx % TOTAL) + TOTAL) % TOTAL; track.style.transition = animate ? 'transform 0.75s cubic-bezier(0.4,0,0.2,1)' : 'none'; track.style.transform = `translateX(-${curr * 100}%)`; pips.forEach((p, i) => p.classList.toggle('tm-pip--on', i === curr)); counter.textContent = String(curr + 1).padStart(2, '0') + ' / ' + String(TOTAL).padStart(2, '0'); setTimeout(() => { busy = false; }, 800); } function startAuto() { clearInterval(timer); timer = setInterval(() => goto((curr + 1) % TOTAL, true), AUTO_MS); } prevBtn.addEventListener('click', () => { goto((curr - 1 + TOTAL) % TOTAL, true); startAuto(); }); nextBtn.addEventListener('click', () => { goto((curr + 1) % TOTAL, true); startAuto(); }); pips.forEach((p) => p.addEventListener('click', () => { goto(parseInt(p.dataset.goto), true); startAuto(); }) ); // Swipe support let tx0 = 0; track.addEventListener( 'touchstart', (e) => { tx0 = e.touches[0].clientX; }, { passive: true } ); track.addEventListener( 'touchend', (e) => { const dx = e.changedTouches[0].clientX - tx0; if (Math.abs(dx) > 48) { goto(dx < 0 ? (curr + 1) % TOTAL : (curr - 1 + TOTAL) % TOTAL, true); startAuto(); } }, { passive: true } ); const section = document.querySelector('.tm-section'); if (section) { section.addEventListener('mouseenter', () => clearInterval(timer)); section.addEventListener('mouseleave', startAuto); } goto(0, false); startAuto(); })(); // ═══════════════════════════════════════════ // CHALLENGE HUB INFOGRAPHIC — animated // ═══════════════════════════════════════════ (function () { const ns = 'http://www.w3.org/2000/svg'; const CX = 250, CY = 250, R = 185; const nodes = [ { id: 'nc0', iconId: 'ni0', angle: 0, icon: 'M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z', }, { id: 'nc1', iconId: 'ni1', angle: Math.PI / 2, icon: 'M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z', }, { id: 'nc2', iconId: 'ni2', angle: Math.PI, icon: 'M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2', }, { id: 'nc3', iconId: 'ni3', angle: (3 * Math.PI) / 2, icon: 'M12 18h.01M8 21h8a2 2 0 002-2v-2a2 2 0 00-2-2H8a2 2 0 00-2 2v2a2 2 0 002 2zM12 3v9m0 0l-3-3m3 3l3-3', }, ]; // Render icons into the icon group const iconG = document.getElementById('nodeIcons'); if (iconG) { nodes.forEach((n) => { const nx = CX + R * Math.cos(n.angle); const ny = CY + R * Math.sin(n.angle); const size = 20, ox = nx - size / 2, oy = ny - size / 2; const g = document.createElementNS(ns, 'g'); g.setAttribute('id', n.iconId); g.setAttribute('transform', `translate(${ox},${oy})`); const s = document.createElementNS(ns, 'svg'); s.setAttribute('width', size); s.setAttribute('height', size); s.setAttribute('viewBox', '0 0 24 24'); s.setAttribute('fill', 'none'); const p = document.createElementNS(ns, 'path'); p.setAttribute('d', n.icon); p.setAttribute('stroke', '#222'); p.setAttribute('stroke-width', '1.5'); p.setAttribute('stroke-linecap', 'round'); p.setAttribute('stroke-linejoin', 'round'); s.appendChild(p); g.appendChild(s); iconG.appendChild(g); }); } const arrowEl = document.getElementById('infoArrow'); const pulseRing = document.getElementById('hubPulseRing'); const nodeCircles = nodes.map((n) => document.getElementById(n.id)); const nodeIcons = nodes.map((n) => document.getElementById(n.iconId)); const PERIOD = 8000; // ms for full 360° const HIT_TOL = 0.18; // radians — how close arrow must be to trigger const litState = [false, false, false, false]; const litTimers = [null, null, null, null]; let startTime = null; let running = false; // Hub pulse: clone the ring and re-trigger animation let pulseTimer = null; function triggerHubPulse() { if (!pulseRing) return; pulseRing.classList.remove('pulsing'); // Force reflow so animation replays void pulseRing.getBoundingClientRect(); pulseRing.classList.add('pulsing'); clearTimeout(pulseTimer); pulseTimer = setTimeout(() => { pulseRing.classList.remove('pulsing'); }, 750); } const section = document.querySelector('.challenge'); const vis = new IntersectionObserver( (entries) => { running = entries[0].isIntersecting; if (running) { startTime = null; requestAnimationFrame(tick); } }, { threshold: 0.2 } ); if (section) vis.observe(section); function tick(t) { if (!running) return; if (!startTime) startTime = t; const elapsed = (t - startTime) % PERIOD; // Clockwise angle (SVG y-down = +y is down, so clockwise is +angle) const angle = (elapsed / PERIOD) * Math.PI * 2; // Arrow position on circle const ax = CX + R * Math.cos(angle); const ay = CY + R * Math.sin(angle); // Inward-pointing: arrow should face from (ax,ay) toward (CX,CY) // Direction = atan2(CY - ay, CX - ax) → points toward centre const inwardDeg = (Math.atan2(CY - ay, CX - ax) * 180) / Math.PI; if (arrowEl) { arrowEl.setAttribute('transform', `translate(${ax},${ay}) rotate(${inwardDeg + 90})`); } // Node activation check nodes.forEach((n, i) => { let diff = (((angle - n.angle) % (Math.PI * 2)) + Math.PI * 2) % (Math.PI * 2); if (diff > Math.PI) diff = Math.PI * 2 - diff; const near = diff < HIT_TOL; if (near && !litState[i]) { litState[i] = true; // Light up node circle (solid orange) if (nodeCircles[i]) nodeCircles[i].classList.add('lit'); // Turn icon white if (nodeIcons[i]) { const p = nodeIcons[i].querySelector('path'); if (p) p.setAttribute('stroke', '#ffffff'); } // Pulse the central hub triggerHubPulse(); // Restore after 900ms clearTimeout(litTimers[i]); litTimers[i] = setTimeout(() => { litState[i] = false; if (nodeCircles[i]) nodeCircles[i].classList.remove('lit'); if (nodeIcons[i]) { const p = nodeIcons[i].querySelector('path'); if (p) p.setAttribute('stroke', '#222'); } }, 900); } }); requestAnimationFrame(tick); } })(); // ═══════════════════════════════════════════ // FAQ ACCORDION // ═══════════════════════════════════════════ function toggleFaq(el) { const item = el.parentElement; const answer = item.querySelector('.faq-a'); const wasOpen = item.classList.contains('open'); document.querySelectorAll('.faq-item.open').forEach((i) => { i.classList.remove('open'); i.querySelector('.faq-a').style.maxHeight = '0'; }); if (!wasOpen) { item.classList.add('open'); answer.style.maxHeight = answer.scrollHeight + 'px'; } } // ── ROI CALCULATOR (Surveillance) ─────────────────────────── (function () { function upd(id, fillId, mn, mx, lv) { const el = document.getElementById(id), fEl = document.getElementById(fillId), vEl = document.getElementById(lv); if (el && fEl) fEl.style.width = ((el.value - mn) / (mx - mn)) * 100 + '%'; if (el && vEl) vEl.textContent = id === 'sEnergy' ? Number(el.value).toLocaleString() : el.value; } function calc() { const sites = parseFloat(document.getElementById('sSize')?.value || 3); const inc = parseFloat(document.getElementById('sSys')?.value || 12); const cost = parseFloat(document.getElementById('sEnergy')?.value || 5000); const staff = parseFloat(document.getElementById('sHome')?.value || 2); upd('sSize', 'fSize', 1, 20, 'vSize'); upd('sSys', 'fSys', 1, 100, 'vSys'); upd('sEnergy', 'fEnergy', 500, 50000, 'vEnergy'); upd('sHome', 'fHome', 0, 20, 'vHome'); const incident_cost = inc * cost; const staff_cost = staff * 42000; const total = incident_cost + staff_cost; const fmt = (v) => '$' + Math.round(v).toLocaleString(); const e1 = document.getElementById('rEnergy'), e2 = document.getElementById('rTime'), e3 = document.getElementById('rPayback'); if (e1) e1.textContent = fmt(incident_cost); if (e2) e2.textContent = fmt(staff_cost); if (e3) e3.innerHTML = fmt(total) + ' per year'; } ['sSize', 'sSys', 'sEnergy', 'sHome'].forEach((id) => { const el = document.getElementById(id); if (el) el.addEventListener('input', calc); }); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', calc); } else { calc(); } })(); } })();