{"id":167,"date":"2026-06-24T03:09:52","date_gmt":"2026-06-24T03:09:52","guid":{"rendered":"https:\/\/formooreservices.com\/internal\/?page_id=167"},"modified":"2026-07-23T23:53:50","modified_gmt":"2026-07-23T23:53:50","slug":"moore-tech-services-portal","status":"publish","type":"page","link":"https:\/\/formooreservices.com\/internal\/","title":{"rendered":"Moore Tech Services &#8211; Portal"},"content":{"rendered":"<div id=\"pl-167\"  class=\"panel-layout\" ><div id=\"pg-167-0\"  class=\"panel-grid panel-no-style\" ><div id=\"pgc-167-0-0\"  class=\"panel-grid-cell\" ><div id=\"panel-167-0-0-0\" class=\"widget_text so-panel widget widget_custom_html panel-first-child panel-last-child\" data-index=\"0\" ><div class=\"textwidget custom-html-widget\"><div id=\"mts-ticker\" style=\"\n    background:#ffffff;\n    color:#4a2e1f;\n    font-size:14px;            \/* smaller font *\/\n    padding:10px 15px;\n    border-bottom:2px solid #4a2e1f;\n    overflow:hidden;\n    white-space:nowrap;\n    opacity:0;\n    transition: opacity 1.5s ease-in;\n\">\n    Loading events\u2026\n<\/div>\n\n<style>\n@keyframes scroll-left {\n    0% { transform: translateX(100%); }\n    100% { transform: translateX(-100%); }\n}\n\n#mts-ticker {\n    display:inline-block;\n    width:max-content;        \/* prevents truncation *\/\n    white-space:nowrap;\n    animation: scroll-left 25s linear infinite;\n}\n\n#mts-ticker a:hover {\n    text-decoration:underline;\n}\n<\/style>\n\n<script>\nconst ICS_URL = \"https:\/\/formooreservices.com\/ics-relay.php\";\n\n\/* Normalize date to midnight *\/\nfunction normalize(d) {\n    return new Date(d.getFullYear(), d.getMonth(), d.getDate());\n}\n\n\/* Parse ICS date *\/\nfunction parseICSDate(str) {\n    if (!str) return null;\n\n    if (!str.includes(\"T\")) {\n        const y = parseInt(str.substring(0,4));\n        const m = parseInt(str.substring(4,6)) - 1;\n        const d = parseInt(str.substring(6,8));\n        return normalize(new Date(y, m, d));\n    }\n\n    const y = parseInt(str.substring(0,4));\n    const m = parseInt(str.substring(4,6)) - 1;\n    const d = parseInt(str.substring(6,8));\n    return normalize(new Date(y, m, d));\n}\n\n\/* Unfold ICS lines *\/\nfunction unfoldICS(text) {\n    const lines = text.split(\/\\r?\\n\/);\n    const unfolded = [];\n    lines.forEach(line => {\n        if (line.startsWith(\" \")) {\n            unfolded[unfolded.length - 1] += line.slice(1);\n        } else {\n            unfolded.push(line);\n        }\n    });\n    return unfolded.join(\"\\n\");\n}\n\n\/* Expand RRULE occurrences *\/\nfunction expandRecurrence(dtstart, rrule, windowStart, windowEnd) {\n    const events = [];\n    const rule = {};\n\n    rrule.split(\";\").forEach(part => {\n        const [key, value] = part.split(\"=\");\n        rule[key] = value;\n    });\n\n    let current = new Date(dtstart);\n\n    const freq = rule.FREQ;\n    const interval = parseInt(rule.INTERVAL || \"1\");\n    const until = rule.UNTIL ? parseICSDate(rule.UNTIL.substring(0,8)) : null;\n    const byday = rule.BYDAY ? rule.BYDAY.split(\",\") : null;\n\n    const weekdayCode = d => [\"SU\",\"MO\",\"TU\",\"WE\",\"TH\",\"FR\",\"SA\"][d.getDay()];\n\n    while (true) {\n        const date = normalize(current);\n\n        if (date >= windowStart && date <= windowEnd) {\n            events.push(new Date(date));\n        }\n\n        if (until && date > until) break;\n        if (date > windowEnd) break;\n\n        if (freq === \"DAILY\") {\n            current.setDate(current.getDate() + interval);\n        } else if (freq === \"WEEKLY\") {\n            if (byday && byday.length > 0) {\n                let found = false;\n                for (let i = 1; i <= 7 * interval; i++) {\n                    const next = new Date(current);\n                    next.setDate(current.getDate() + i);\n                    const code = weekdayCode(next);\n                    if (byday.includes(code)) {\n                        current = next;\n                        found = true;\n                        break;\n                    }\n                }\n                if (!found) break;\n            } else {\n                current.setDate(current.getDate() + 7 * interval);\n            }\n        } else if (freq === \"MONTHLY\") {\n            current.setMonth(current.getMonth() + interval);\n        } else if (freq === \"YEARLY\") {\n            current.setFullYear(current.getFullYear() + interval);\n        } else {\n            break;\n        }\n    }\n\n    return events;\n}\n\nasync function loadICSEvents() {\n    try {\n        const response = await fetch(ICS_URL);\n        let text = await response.text();\n\n        text = unfoldICS(text);\n\n        const events = [];\n        const raw = text.split(\"BEGIN:VEVENT\");\n\n        const today = normalize(new Date());\n        const windowEnd = new Date(today);\n        windowEnd.setDate(today.getDate() + 3);   \/* next 3 days *\/\n\n        raw.forEach(block => {\n            if (!block.includes(\"SUMMARY\")) return;\n\n            const summary = block.match(\/SUMMARY:(.*)\/);\n            const dtstart = block.match(\/DTSTART(?:;[^:]+)?:([0-9T]+)\/);\n            const uid = block.match(\/UID:(.*)\/);\n            const rrule = block.match(\/RRULE:(.*)\/);\n\n            if (!summary || !dtstart || !uid) return;\n\n            const startDate = parseICSDate(dtstart[1]);\n\n            if (rrule) {\n                const occurrences = expandRecurrence(startDate, rrule[1], today, windowEnd);\n                occurrences.forEach(occ => {\n                    events.push({\n                        title: summary[1].trim(),\n                        date: `${(occ.getMonth()+1).toString().padStart(2,'0')}\/${occ.getDate().toString().padStart(2,'0')}\/${occ.getFullYear()}`,\n                        link: `https:\/\/outlook.live.com\/calendar\/event\/${uid[1].trim()}`,\n                        isToday: occ.getTime() === today.getTime()\n                    });\n                });\n            } else {\n                if (startDate >= today && startDate <= windowEnd) {\n                    events.push({\n                        title: summary[1].trim(),\n                        date: `${(startDate.getMonth()+1).toString().padStart(2,'0')}\/${startDate.getDate().toString().padStart(2,'0')}\/${startDate.getFullYear()}`,\n                        link: `https:\/\/outlook.live.com\/calendar\/event\/${uid[1].trim()}`,\n                        isToday: startDate.getTime() === today.getTime()\n                    });\n                }\n            }\n        });\n\n        const ticker = document.getElementById(\"mts-ticker\");\n\n        if (events.length === 0) {\n            ticker.textContent = \"No events in the next 3 days.\";\n            ticker.style.opacity = 1;\n            return;\n        }\n\n        const textLine = events\n            .map((ev, i) => {\n                if (ev.isToday) {\n                    return `<a href=\"${ev.link}\" target=\"_blank\" style=\"color:#b30000;font-weight:bold;text-decoration:none;margin-right:40px;\">${ev.date} \u2014 ${ev.title}<\/a>`;\n                }\n                const color = (i % 2 === 0) ? \"#4a2e1f\" : \"#333333\";\n                return `<a href=\"${ev.link}\" target=\"_blank\" style=\"color:${color};text-decoration:none;margin-right:40px;\">${ev.date} \u2014 ${ev.title}<\/a>`;\n            })\n            .join(\"\");\n\n        ticker.innerHTML = textLine;\n\n        setTimeout(() => ticker.style.opacity = 1, 200);\n\n    } catch (e) {\n        const ticker = document.getElementById(\"mts-ticker\");\n        ticker.textContent = \"Unable to load events.\";\n        ticker.style.opacity = 1;\n    }\n}\n\nloadICSEvents();\n<\/script>\n<\/div><\/div><\/div><\/div><\/div>","protected":false},"excerpt":{"rendered":"<p>Loading events\u2026<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"home-panels.php","meta":{"footnotes":""},"class_list":["post-167","page","type-page","status-publish","hentry","post"],"_links":{"self":[{"href":"https:\/\/formooreservices.com\/internal\/wp-json\/wp\/v2\/pages\/167","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/formooreservices.com\/internal\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/formooreservices.com\/internal\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/formooreservices.com\/internal\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/formooreservices.com\/internal\/wp-json\/wp\/v2\/comments?post=167"}],"version-history":[{"count":38,"href":"https:\/\/formooreservices.com\/internal\/wp-json\/wp\/v2\/pages\/167\/revisions"}],"predecessor-version":[{"id":235,"href":"https:\/\/formooreservices.com\/internal\/wp-json\/wp\/v2\/pages\/167\/revisions\/235"}],"wp:attachment":[{"href":"https:\/\/formooreservices.com\/internal\/wp-json\/wp\/v2\/media?parent=167"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}