{"id":1979,"date":"2026-10-26T16:19:45","date_gmt":"2026-10-26T16:19:45","guid":{"rendered":"https:\/\/www.ekkotitle.com\/?page_id=1979"},"modified":"2026-10-26T17:19:17","modified_gmt":"2026-10-26T17:19:17","slug":"ce","status":"publish","type":"page","link":"https:\/\/www.ekkotitle.com\/index.php\/ce\/","title":{"rendered":"Continuing Education"},"content":{"rendered":"\n<!-- Fonts: your site already loads these. Remove this line if it's a duplicate on the live page. -->\n<link rel=\"stylesheet\" href=\"https:\/\/fonts.googleapis.com\/css2?family=Playfair+Display:wght@500;600&#038;family=Roboto:wght@400;500;700&#038;display=swap\">\n\n<style>\n\n.ice {\n    --ice-max: 1120px;\n    width: 100%;\n    margin: 0 auto;\n    --blue: #0a4a94;\n    --navy: #0d2b52;\n    --tint: #d9e8fb;\n    --edge: #b7cfee;\n    --ink: #1a2330;\n    --slate: #556173;\n    --line: #d3dce8;\n    --ple: #12766a;\n    font-family: Roboto, system-ui, -apple-system, \"Segoe UI\", sans-serif;\n    font-size: 16px;\n    line-height: 1.65;\n    color: var(--ink);\n}\n.ice *, .ice *::before, .ice *::after { box-sizing: border-box; }\n.ice h1, .ice h2, .ice h3 {\n    font-family: \"Playfair Display\", Georgia, serif;\n    font-weight: 500;\n    color: var(--navy);\n    line-height: 1.2;\n    margin: 0;\n}\n.ice p { margin: 0; }\n.ice a, .ice a:visited { color: var(--blue); }\n.ice a:hover, .ice a:focus, .ice a:active { color: var(--navy); }\n.ice :focus-visible { outline: 3px solid #7db3f0; outline-offset: 2px; }\n\n.ice .ice-section { padding: 56px 24px; scroll-margin-top: 16px; background: #fff; }\n.ice .ice-wrap { max-width: 1080px; margin: 0 auto; }\n.ice .ice-lede { max-width: 62ch; color: var(--slate); margin-top: 10px; }\n.ice h2 { font-size: clamp(1.5rem, 3vw, 2rem); }\n\n\/* Tinted sections alternate with white ones *\/\n.ice .is-tint { background: var(--tint); }\n\n\/* Buttons: every state sets its own colors so theme link styles can't override them *\/\n.ice .ice-btn, .ice .ice-btn:visited {\n    display: inline-block;\n    font: 500 1rem\/1 Roboto, system-ui, sans-serif;\n    padding: 14px 22px;\n    border-radius: 4px;\n    border: 2px solid var(--blue);\n    background: var(--blue);\n    color: #fff;\n    text-decoration: none;\n    cursor: pointer;\n}\n.ice .ice-btn:hover, .ice .ice-btn:focus, .ice .ice-btn:active { background: var(--navy); border-color: var(--navy); color: #fff; }\n.ice .ice-btn.is-small { padding: 10px 18px; font-size: .9375rem; }\n\n\/* Outlined button *\/\n.ice .ice-btn.is-outline, .ice .ice-btn.is-outline:visited { background: transparent; border-color: var(--blue); color: var(--blue); }\n.ice .ice-btn.is-outline:hover, .ice .ice-btn.is-outline:focus, .ice .ice-btn.is-outline:active { background: var(--blue); border-color: var(--blue); color: #fff; }\n\n\/* Hero *\/\n.ice .ice-hero { padding: 64px 24px 56px; }\n.ice .ice-hero h1 { font-size: clamp(2rem, 5vw, 3.25rem); max-width: 24ch; }\n.ice .ice-hero p { max-width: 58ch; margin-top: 18px; font-size: 1.125rem; color: var(--ink); }\n.ice .ice-hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }\n\n\/* Events *\/\n.ice .ice-events { list-style: none; margin: 28px 0 0; padding: 0; border-top: 1px solid var(--line); }\n.ice .ice-event {\n    display: grid;\n    grid-template-columns: 76px 1fr auto;\n    gap: 8px 24px;\n    align-items: start;\n    padding: 24px 0;\n    border-bottom: 1px solid var(--line);\n}\n.ice .ice-date { text-align: center; line-height: 1.1; }\n.ice .ice-date-month { display: block; font-size: .9375rem; color: var(--slate); }\n.ice .ice-date-day { display: block; font: 500 2.25rem\/1.1 \"Playfair Display\", Georgia, serif; color: var(--blue); }\n.ice .ice-event h3 { font-size: 1.25rem; }\n.ice .ice-event-lines { margin-top: 6px; color: var(--slate); font-size: .9375rem; }\n.ice .ice-event-lines p + p { margin-top: 2px; }\n.ice .ice-event-credit { margin-top: 10px; font-size: .9375rem; display: flex; flex-wrap: wrap; align-items: center; gap: 6px 10px; }\n.ice .ice-featured { font: 700 .75rem\/1 Roboto, sans-serif; color: #fff; background: var(--navy); border-radius: 999px; padding: 4px 10px; margin-left: 10px; vertical-align: middle; }\n.ice .ice-event-sessions { margin: 10px 0 0; padding-left: 20px; font-size: .9375rem; }\n.ice .ice-event-desc { margin-top: 10px; max-width: 64ch; font-size: .9375rem; }\n.ice .ice-sample { font: 400 .8125rem\/1 Roboto, sans-serif; color: var(--slate); border: 1px dashed var(--slate); border-radius: 3px; padding: 2px 6px; margin-left: 8px; vertical-align: middle; }\n.ice .ice-empty { margin-top: 28px; padding: 24px; border: 1px dashed #9fb0c6; border-radius: 4px; }\n.ice .ice-more { margin-top: 20px; }\n\n\/* Office class request *\/\n.ice .ice-office {\n    display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 16px 32px;\n    margin-top: 36px; padding: 24px 28px; border: 1px solid var(--line); border-left: 6px solid var(--blue); border-radius: 4px;\n}\n.ice .ice-office h3 { font-size: 1.25rem; }\n.ice .ice-office p { max-width: 56ch; margin-top: 6px; }\n\n\/* Type tags *\/\n.ice .ice-type { display: inline-block; font-weight: 700; font-size: .8125rem; line-height: 1.4; color: #fff; background: var(--blue); border-radius: 3px; padding: 2px 8px; }\n.ice .ice-type.is-ple { background: var(--ple); }\n.ice .ice-pending { font-size: .8125rem; color: #7a4a00; background: #fff3d6; border-radius: 3px; padding: 2px 8px; }\n\n\/* Course finder: filters *\/\n.ice .ice-controls { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; align-items: center; }\n.ice .ice-search { flex: 1 1 240px; max-width: 380px; }\n.ice .ice-search input, .ice .ice-select {\n    width: 100%; font: 400 1rem\/1.4 Roboto, sans-serif; color: var(--ink);\n    padding: 11px 14px; border: 2px solid var(--blue); border-radius: 4px; background: #fff;\n}\n.ice .ice-select { width: auto; min-width: 220px; }\n.ice .ice-seg { display: inline-flex; border: 2px solid var(--blue); border-radius: 4px; overflow: hidden; }\n.ice .ice-seg button {\n    font: 500 .9375rem\/1 Roboto, sans-serif; padding: 12px 16px; border: 0; background: #fff; color: var(--blue); cursor: pointer;\n}\n.ice .ice-seg button + button { border-left: 2px solid var(--blue); }\n.ice .ice-seg button:hover { background: var(--tint); }\n.ice .ice-seg button[aria-pressed=\"true\"] { background: var(--blue); color: #fff; }\n.ice .ice-count { margin-top: 20px; font-size: .9375rem; color: var(--slate); }\n\n\/* Course finder: list and detail *\/\n.ice .ice-finder { display: grid; grid-template-columns: minmax(300px, 5fr) 7fr; gap: 20px; margin-top: 10px; }\n.ice .ice-list, .ice .ice-detail { height: 460px; overflow-y: auto; background: #fff; color: var(--ink); border: 1px solid var(--edge); border-radius: 4px; }\n.ice .ice-list { list-style: none; margin: 0; padding: 0; }\n.ice .ice-row {\n    display: block; width: 100%; text-align: left; padding: 12px 16px 12px 13px; background: #fff; color: var(--ink);\n    border: 0; border-left: 4px solid transparent; border-bottom: 1px solid var(--line); cursor: pointer; font: inherit;\n}\n.ice .ice-row:hover { border-left-color: #9fb9da; }\n.ice .ice-row[aria-current=\"true\"] { border-left-color: var(--blue); background: #f4f8fd; }\n.ice .ice-row-name { display: block; font-weight: 500; line-height: 1.35; }\n.ice .ice-row-meta { display: flex; align-items: center; gap: 8px; margin-top: 4px; font-size: .875rem; color: var(--slate); }\n.ice .ice-no-results { padding: 20px 16px; color: var(--slate); }\n.ice .ice-detail { padding: 28px; }\n.ice .ice-detail-top { display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; color: var(--slate); font-size: .9375rem; }\n.ice .ice-detail h3 { font-size: 1.5rem; margin-top: 12px; }\n.ice .ice-detail-meta { margin-top: 8px; color: var(--slate); font-size: .9375rem; }\n.ice .ice-detail-blurb { margin-top: 16px; max-width: 60ch; }\n.ice .ice-next { margin-top: 24px; padding-top: 20px; border-top: 1px solid var(--line); }\n.ice .ice-next strong { display: block; color: var(--navy); }\n.ice .ice-next p { margin-top: 4px; }\n.ice .ice-next .ice-btn { margin-top: 14px; }\n.ice .ice-detail-prompt { color: var(--slate); max-width: 38ch; }\n.ice .ice-full-link { margin-top: 20px; }\n\n\/* Requirements *\/\n.ice .ice-tabs { display: flex; flex-wrap: wrap; gap: 4px 28px; margin-top: 28px; border-bottom: 1px solid var(--line); }\n.ice .ice-tab {\n    font: 500 1.0625rem\/1.3 Roboto, sans-serif; padding: 10px 0; border: 0; border-bottom: 3px solid transparent;\n    margin-bottom: -1px; background: none; color: var(--slate); cursor: pointer;\n}\n.ice .ice-tab:hover { color: var(--navy); }\n.ice .ice-tab[aria-selected=\"true\"] { color: var(--navy); border-bottom-color: var(--blue); }\n.ice .ice-panel { margin-top: 28px; }\n.ice .ice-panel-head { display: flex; flex-wrap: wrap; align-items: baseline; gap: 4px 20px; }\n.ice .ice-total { font: 500 2.75rem\/1 \"Playfair Display\", Georgia, serif; color: var(--blue); white-space: nowrap; }\n.ice .ice-panel-head p { flex: 1 1 340px; max-width: 62ch; }\n.ice .ice-ledger { list-style: none; margin: 24px 0 0; padding: 0; max-width: 760px; }\n.ice .ice-ledger li {\n    display: grid; grid-template-columns: 28px 132px 1fr; gap: 16px; align-items: center;\n    padding: 10px 0; border-bottom: 1px solid var(--line);\n}\n.ice .ice-hrs { font-weight: 700; text-align: right; color: var(--navy); }\n.ice .ice-ticks { display: flex; flex-wrap: wrap; gap: 3px; width: 132px; }\n.ice .ice-ticks i { width: 12px; height: 12px; background: var(--blue); border-radius: 2px; display: block; }\n.ice .ice-ticks.is-open i { background: transparent; border: 2px solid var(--blue); }\n.ice .ice-note { margin-top: 28px; font-style: italic; color: #a1121a; }\n.ice .ice-note a, .ice .ice-note a:visited { color: #a1121a; }\n.ice .ice-note a:hover, .ice .ice-note a:focus { color: var(--navy); }\n.ice .ice-extras {\n    display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 32px;\n    margin-top: 40px; padding-top: 32px; border-top: 1px solid var(--line);\n}\n.ice .ice-extras h3 { font-size: 1.1875rem; }\n.ice .ice-extras p { margin-top: 8px; max-width: 58ch; }\n.ice .ice-extras a.ice-link { display: inline-block; margin-top: 10px; }\n\n\/* Email signup (posts to Mailchimp) *\/\n.ice .ice-subscribe { margin-top: 24px; }\n.ice .ice-subscribe label { display: block; margin-bottom: 6px; font-weight: 500; font-size: .9375rem; color: var(--navy); }\n.ice .ice-subscribe-row { display: flex; flex-wrap: wrap; align-items: stretch; gap: 12px; }\n.ice .ice-subscribe input[type=\"email\"] {\n    flex: 1 1 260px; max-width: 380px; font: 400 1rem\/1.4 Roboto, sans-serif; color: var(--ink);\n    padding: 11px 14px; border: 2px solid var(--blue); border-radius: 4px; background: #fff;\n}\n.ice .ice-hp { position: absolute; left: -5000px; }\n.ice .ice-contact { margin-top: 32px; padding-top: 24px; border-top: 1px solid var(--edge); }\n\n\/* Screen-reader-only text *\/\n.ice .ice-sr { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }\n\n@media (max-width: 720px) {\n    .ice .ice-finder { grid-template-columns: 1fr; }\n    .ice .ice-list { height: 340px; }\n    .ice .ice-detail { height: auto; order: -1; padding: 22px; }\n    .ice .ice-detail.is-empty { display: none; }\n}\n@media (max-width: 640px) {\n    .ice .ice-section { padding: 40px 18px; }\n    .ice .ice-hero { padding: 44px 18px 40px; }\n    .ice .ice-event { grid-template-columns: 56px 1fr; }\n    .ice .ice-event .ice-btn { grid-column: 2; justify-self: start; }\n    .ice .ice-ledger li { grid-template-columns: 24px 1fr; }\n    .ice .ice-ticks { grid-column: 2; }\n    .ice .ice-ledger li > span:last-child { grid-column: 2; grid-row: 1; }\n    .ice .ice-select { width: 100%; }\n}\n<\/style>\n\n<div class=\"ice\">\n\n    <header class=\"ice-section ice-hero is-tint\">\n        <div class=\"ice-wrap\">\n            <h1>EKKO Title&#8217;s Institute of Continuing Education<\/h1>\n            <p>Board-approved continuing education and post-license courses for Virginia real estate professionals. Every ICE course counts toward the hours you need to renew your license.<\/p>\n            <div class=\"ice-hero-actions\">\n                <a class=\"ice-btn\" href=\"#ice-events\" data-scroll>See upcoming classes<\/a>\n                <a class=\"ice-btn is-outline\" href=\"#ice-catalogue\" data-scroll>Find a course<\/a>\n            <\/div>\n        <\/div>\n    <\/header>\n\n    <section class=\"ice-section\" id=\"ice-events\" aria-labelledby=\"ice-events-title\">\n        <div class=\"ice-wrap\">\n            <h2 id=\"ice-events-title\">Upcoming classes<\/h2>\n            <ul class=\"ice-events\" id=\"iceEvents\"><\/ul>\n            <p class=\"ice-more\"><a href=\"#\" id=\"iceAllEvents\">See the full class schedule<\/a><\/p>\n\n            <div class=\"ice-office\">\n                <div>\n                    <h3>Want a class just for your office?<\/h3>\n                    <p>We&#8217;ll come to you. If your team wants CE or PLE credits, or a closer look at one topic, our instructors tailor the class to your agents&#8217; needs and schedule.<\/p>\n                <\/div>\n                <a class=\"ice-btn\" href=\"https:\/\/form.jotform.com\/instofce\/ce-course-request-form\" target=\"_blank\" rel=\"noopener\">Request an office class<\/a>\n            <\/div>\n        <\/div>\n    <\/section>\n\n    <section class=\"ice-section is-tint\" id=\"ice-catalogue\" aria-labelledby=\"ice-catalogue-title\">\n        <div class=\"ice-wrap\">\n            <h2 id=\"ice-catalogue-title\">Find a course<\/h2>\n            <p class=\"ice-lede\">Search by name, switch between CE and PLE, or pick a requirement. Select a course to see what it covers and when it&#8217;s next offered.<\/p>\n\n            <div class=\"ice-controls\">\n                <div class=\"ice-search\">\n                    <input type=\"search\" id=\"iceSearch\" placeholder=\"Search courses by name\" aria-label=\"Search courses by name\">\n                <\/div>\n                <div class=\"ice-seg\" id=\"iceType\" role=\"group\" aria-label=\"Course type\">\n                    <button type=\"button\" data-type=\"all\" aria-pressed=\"true\">All<\/button>\n                    <button type=\"button\" data-type=\"CE\" aria-pressed=\"false\">CE<\/button>\n                    <button type=\"button\" data-type=\"PLE\" aria-pressed=\"false\">PLE<\/button>\n                <\/div>\n                <select class=\"ice-select\" id=\"iceRequirement\" aria-label=\"Filter by requirement\"><\/select>\n            <\/div>\n\n            <p class=\"ice-count\" id=\"iceCount\" aria-live=\"polite\"><\/p>\n            <div class=\"ice-finder\">\n                <ul class=\"ice-list\" id=\"iceList\"><\/ul>\n                <div class=\"ice-detail is-empty\" id=\"iceDetail\" aria-live=\"polite\"><\/div>\n            <\/div>\n            <p class=\"ice-full-link\"><a href=\"#\" id=\"iceFullCatalogue\">See the full catalogue with every course description<\/a><\/p>\n        <\/div>\n    <\/section>\n\n    <section class=\"ice-section\" id=\"ice-requirements\" aria-labelledby=\"ice-req-title\">\n        <div class=\"ice-wrap\">\n            <h2 id=\"ice-req-title\">How many hours do you need?<\/h2>\n            <p class=\"ice-lede\">Virginia Real Estate Board requirements for continuing education (CE) and post-license education (PLE). Each square is one credit hour. Open squares are hours you can fill with any real estate related course.<\/p>\n            <div class=\"ice-tabs\" id=\"iceTabs\" role=\"tablist\" aria-label=\"License type\"><\/div>\n            <div id=\"icePanels\"><\/div>\n            <!-- Update the month below whenever you re-check the Board's requirements. -->\n            <p class=\"ice-note\">Requirements as of October 2026 and subject to change. For the latest, check the <a href=\"https:\/\/www.dpor.virginia.gov\/Boards\/Real-Estate\" target=\"_blank\" rel=\"noopener\">Virginia Real Estate Board<\/a>.<\/p>\n\n            <div class=\"ice-extras\">\n                <div>\n                    <h3>Credit from another state<\/h3>\n                    <p>Brokers and experienced salespersons can apply up to 5 hours of approved continuing education taken in another state. You must hold a current real estate license there and have completed the courses during your current two-year license term. These hours count only toward Real Estate Related Subjects (electives). You&#8217;ll send the Board a copy of your out-of-state license and a certificate for each course.<\/p>\n                    <a class=\"ice-link\" href=\"https:\/\/www.dpor.virginia.gov\/Continuing_Education\/\" target=\"_blank\" rel=\"noopener\">See the Board&#8217;s CE page<\/a>\n                <\/div>\n                <div>\n                    <h3>Extra hours? Carry them over<\/h3>\n                    <p>If you finished all your required hours in your previous license term (16 for salespersons, 24 for brokers) and took extra hours in the last six months of that term, you may be able to carry those extra hours into your current term.<\/p>\n                    <a class=\"ice-link\" href=\"https:\/\/www.dpor.virginia.gov\/Boards\/Real-Estate\/CE_Carryover\/\" target=\"_blank\" rel=\"noopener\">How carryover works<\/a>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/section>\n\n    <section class=\"ice-section ice-close is-tint\" id=\"ice-subscribe\" aria-labelledby=\"ice-subscribe-title\">\n        <div class=\"ice-wrap\">\n            <h2 id=\"ice-subscribe-title\">Stay in the loop<\/h2>\n            <p class=\"ice-lede\">Subscribe to get ICE updates, including new classes, by email.<\/p>\n\n            <form class=\"ice-subscribe\" id=\"mc-embedded-subscribe-form\" name=\"mc-embedded-subscribe-form\" method=\"post\" target=\"_blank\"\n                  action=\"https:\/\/instituteofce.us10.list-manage.com\/subscribe\/post?u=5e9c327c91ed26e91694ad389&amp;id=c3c77ad3f3&amp;f_id=00e7e0e5f0\">\n                <label for=\"mce-EMAIL\">Email address<\/label>\n                <div class=\"ice-subscribe-row\">\n                    <input type=\"email\" name=\"EMAIL\" id=\"mce-EMAIL\" required autocomplete=\"email\" placeholder=\"you@example.com\">\n                    <button type=\"submit\" name=\"subscribe\" id=\"mc-embedded-subscribe\" class=\"ice-btn\" value=\"Subscribe\">Subscribe<\/button>\n                <\/div>\n                <!-- Mailchimp spam trap: real people never fill this in. Do not remove it. -->\n                <div class=\"ice-hp\" aria-hidden=\"true\">\n                    <input type=\"text\" name=\"b_5e9c327c91ed26e91694ad389_c3c77ad3f3\" tabindex=\"-1\" value=\"\">\n                <\/div>\n            <\/form>\n\n            <p class=\"ice-contact\">Questions about a course or how your hours count? Email <a href=\"mailto:ice@ekkotitle.com\">ice@ekkotitle.com<\/a>.<\/p>\n        <\/div>\n    <\/section>\n<\/div>\n\n<script>\n(function () {\n    'use strict';\n\n    \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n       0. LINKS\n       \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n    const EVENTS_PAGE_URL = 'https:\/\/www.ekkotitle.com\/index.php\/ice-schedule\/';\n    const CATALOGUE_URL = 'https:\/\/www.ekkotitle.com\/index.php\/course-offerings\/';\n    const OFFICE_CLASS_URL = 'https:\/\/form.jotform.com\/instofce\/ce-course-request-form';\n\n    \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n       1. UPCOMING CLASSES \u2014 add a row for each class. Classes drop off the\n          page automatically the day after their date.\n          code          course code from the catalogue below (fills in the name,\n                        CE or PLE, credit hours, and category)\n          codes         use this instead of code when one class covers several\n                        courses, e.g. codes: ['214030500', '214030499'].\n                        Add a title too, since there's no single course name.\n          featured      true adds a \"Featured\" tag\n          date          YYYY-MM-DD\n          time          shown as written\n          venue, address, instructor, host, cost   all optional\n          description   optional. Defaults to the catalogue blurb.\n          url           RSVP or registration link\n          cta           optional button text. Defaults to \"RSVP\".\n          title, hours  optional, only for a class that isn't in the catalogue\n       \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n    const EVENTS_SHOWN = 4;\n    const EVENTS = [\n        {\n            codes: ['214030500', '214030499'],\n            title: 'Spooky Tales from the Settlement Table',\n            featured: true,\n            date: '2026-10-28',\n            time: '10:00 AM to 12:00 PM',\n            venue: 'EKKO Title \\u2013 Centreville',\n            address: '14245-K Centreville Square, Centreville, VA 20121',\n            instructor: 'Lisa Mitchell & Valerie Triplett',\n            cost: 'Free to attend',\n            description: 'Two hours of scary-good CE \\u2014 wire fraud, payoff fraud, vacant land scams, and FIRPTA tax withholding for foreign sellers. Everything your clients need you to know, just in time for Halloween.',\n            url: 'https:\/\/ekkoce.zite.so\/rsvp\/c5fcc266-c153-467b-b693-47b9299023ab'\n        },\n        {\n            code: '214031216',\n            date: '2026-11-12',\n            time: '10:00 AM to 12:00 PM',\n            venue: 'Aloft Hotel \u2013 Ashburn',\n            address: '22390 Flagstaff Plaza, Ashburn, VA 20148',\n            instructor: 'Karen Daily',\n            host: 'EKKO Ashburn @ One Loudoun',\n            cost: 'Free to attend',\n            description: 'Protect your transactions by mastering the contract pitfalls that derail closings \u2014 legal description problems, special seller considerations, ICRA nightmares, and more.',\n            url: 'https:\/\/ekkoce.zite.so\/rsvp\/66a936fb-0443-4411-9632-54a5d1047717'\n        }\n    ];\n\n    \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n       2. COURSE CATALOGUE \u2014 same data as the catalogue page.\n          type      \"CE\" or \"PLE\"\n          expires   approval expiration (YYYY-MM-DD), not shown on the page\n          pending   add  pending: true  for a \"Pending approval\" tag, delete when approved\n       \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n    const COURSES = [\n        { type: \"CE\", name: \"1031 Exchange Basics\", code: \"214028352\", hours: 1, expires: \"2026-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Learn the fundamentals of 1031 exchanges and how investors can defer capital gains taxes when selling and reinvesting in real estate. This course covers timelines, key rules, and the role of qualified intermediaries.\" },\n        { type: \"CE\", name: \"A Title's Life After Death: How to Survive an Estate Closing\", code: \"214029836\", hours: 1, expires: \"2027-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"When a property owner dies, the path to closing can become complicated quickly. Learn how estates affect title, authority to sell, and what agents should expect in probate and non-probate transactions.\" },\n        { type: \"CE\", name: \"Authority to Sell: Probate, Title, & Estate Sales\", code: \"214030501\", hours: 2, expires: \"2028-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Who actually has the legal authority to sell property when an owner has passed away? This class explains probate procedures, title considerations, and common pitfalls in estate transactions.\" },\n        { type: \"CE\", name: \"Breaking the Chain: Divorce and Bankruptcy in Real Estate\", code: \"214030818\", hours: 2, expires: \"2028-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Divorce and bankruptcy can complicate even the simplest real estate transaction. Learn how these legal issues impact title, ownership rights, and the ability to sell.\" },\n        { type: \"CE\", name: \"Changes Start Now: NVAR Forms Update\", code: \"214029795\", hours: 2, expires: \"2027-12-31\",\n          categories: [\"Real Estate Contracts\"],\n          blurb: \"Stay current with the latest updates to NVAR forms and how they impact real estate transactions. This course highlights key changes agents need to understand to avoid contract mistakes.\" },\n        { type: \"CE\", name: \"Code of Ethics\", code: \"214028613\", hours: 3, expires: \"2026-12-31\",\n          categories: [\"Ethics & Standards of Conduct\"],\n          blurb: \"Review the REALTOR\\u00ae Code of Ethics and how it applies to everyday practice. Learn how ethical standards guide professional conduct, protect consumers, and resolve disputes.\" },\n        { type: \"CE\", name: \"Deconstructing the New Home Contract\", code: \"214029016\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"New construction contracts differ significantly from resale agreements. This course breaks down key provisions, builder protections, and risks agents should understand before advising clients.\" },\n        { type: \"CE\", name: \"Deconstructing the NVAR Standard Real Estate Contract\", code: \"214031449\", hours: 2, expires: \"2029-12-31\",\n          categories: [\"Real Estate Contracts\"],\n          blurb: \"Take a deeper look at the NVAR Standard Real Estate Contract. This course breaks down key provisions, timelines, and obligations agents must understand to manage transactions effectively.\" },\n        { type: \"CE\", name: \"Demystifying FIRPTA\", code: \"214030499\", hours: 1, expires: \"2028-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Foreign sellers can trigger complex tax withholding requirements at closing. Learn when FIRPTA applies, how withholding works, and what agents should watch for in these transactions.\" },\n        { type: \"CE\", name: \"Distressed Properties: Short Sales, Foreclosures\", code: \"214029018\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Distressed sales present unique legal and logistical challenges. This course explores the processes behind short sales and foreclosures and how they affect real estate transactions.\" },\n        { type: \"CE\", name: \"Don't Get Taken: Modern Real Estate Fraud Tactics & Prevention\", code: \"214030500\", hours: 1, expires: \"2028-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Real estate fraud is evolving and becoming more sophisticated. Learn the most common scams targeting agents and consumers \\u2014 and the best practices to prevent them.\" },\n        { type: \"CE\", name: \"Easements in VA Real Estate Practice\", code: \"214031827\", hours: 1, expires: \"2029-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Easements are everywhere and clients often have questions about them. In this class you\\u2019ll learn how to advise clients on what easements are, how they are created, how they are terminated, and be able to spot and properly address common easement issues.\" },\n        { type: \"CE\", name: \"Fair Housing\", code: \"214030318\", hours: 2, expires: \"2028-12-31\",\n          categories: [\"Fair Housing\"],\n          blurb: \"Understand the history, purpose, and requirements of Fair Housing laws. This course reviews protected classes, prohibited practices, and how agents can ensure compliance.\" },\n        { type: \"CE\", name: \"General Principles of Escrow\", code: \"214028454\", hours: 3, expires: \"2026-12-31\",\n          categories: [\"Contracts\", \"Electives\"],\n          blurb: \"Escrow plays a critical role in protecting funds and documents during a transaction. Learn how escrow works, who holds responsibility, and common issues that can arise.\" },\n        { type: \"CE\", name: \"Home Inspection Contingency: A Refresher\", code: \"214029343\", hours: 2, expires: \"2027-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"The home inspection contingency is one of the most frequently negotiated contract provisions. This class reviews timelines, negotiation strategies, and common mistakes agents should avoid.\" },\n        { type: \"CE\", name: \"Legislative Updates in Virginia Real Estate\", code: \"214031817\", hours: 2, expires: \"2029-12-31\",\n          categories: [\"Legal Updates with Flood Content\"],\n          blurb: \"Stay compliant with the latest industry shifts. Covers essential legislative updates and contract form changes effective this year.\" },\n        { type: \"CE\", name: \"Mastering Homeowners Insurance: A Realtor's Guide\", code: \"214030664\", hours: 2, expires: \"2028-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Insurance issues can derail a transaction if they arise late in the process. Learn the basics of homeowners insurance, common coverage issues, and how agents can help clients prepare.\" },\n        { type: \"CE\", name: \"Mortgage Assumptions\", code: \"214028350\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"With rising interest rates, mortgage assumptions are becoming more attractive to buyers. This course explains how assumptions work, lender requirements, and potential challenges.\" },\n        { type: \"CE\", name: \"Preoccupied with Occupancy Agreements?\", code: \"214029837\", hours: 1, expires: \"2027-12-31\",\n          categories: [\"Real Estate Contracts\"],\n          blurb: \"Post-settlement occupancy agreements can create unexpected risks for buyers and sellers. Learn when they are appropriate and how to structure them properly.\" },\n        { type: \"CE\", name: \"Realtor Math: Prorations & Calculations\", code: \"214031215\", hours: 1, expires: \"2029-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Confidently handle the numbers behind real estate transactions. Review common calculations including prorations, commissions, taxes, and other figures agents encounter during settlement.\" },\n        { type: \"CE\", name: \"Resale Disclosure Act: Rules for Common Interest Communities\", code: \"214028769\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Contracts\", \"Electives\"],\n          blurb: \"Transactions involving HOAs, condos, and property owners associations have specific disclosure requirements. This class reviews the Virginia Resale Disclosure Act and key compliance issues.\" },\n        { type: \"CE\", name: \"Spring Market Strategies\", code: \"214029345\", hours: 2, expires: \"2027-12-31\",\n          categories: [\"Real Estate Contracts\"],\n          blurb: \"The spring market moves fast and competition can be fierce. Learn practical strategies to help buyers and sellers succeed in a high-activity real estate market.\" },\n        { type: \"CE\", name: \"Successful Settlements\", code: \"214028457\", hours: 3, expires: \"2026-12-31\",\n          categories: [\"Contracts\", \"Electives\"],\n          blurb: \"A smooth closing requires coordination between agents, lenders, and title professionals. This course walks through the settlement process and how agents can help ensure success.\" },\n        { type: \"CE\", name: \"The Complete Settlement Playbook\", code: \"214030320\", hours: 2, expires: \"2028-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"From contract to closing, every transaction follows a complex timeline. Learn the key milestones, documents, and responsibilities involved in the settlement process.\" },\n        { type: \"CE\", name: \"Till Death Do Us Part?\", code: \"214028399\", hours: 1, expires: \"2026-12-31\",\n          categories: [\"Real Estate Contracts\"],\n          blurb: \"Divorce can create serious complications when selling real estate. This course examines how marital property laws, court orders, and title issues affect transactions.\" },\n        { type: \"CE\", name: \"To Survey or Not to Survey...\", code: \"214029838\", hours: 1, expires: \"2027-12-31\",\n          categories: [\"Electives\"],\n          blurb: \"Property surveys can reveal boundary issues, encroachments, and title concerns. Learn when surveys are recommended and how they can impact real estate transactions.\" },\n        { type: \"CE\", name: \"Top Contract Mistakes and How to Avoid Them\", code: \"214031216\", hours: 2, expires: \"2029-12-31\",\n          categories: [\"Real Estate Contracts\"],\n          blurb: \"Small contract mistakes can create major transaction problems. Learn the most common errors agents make in real estate contracts \\u2014 and how to prevent them.\" },\n        { type: \"CE\", name: \"Top Preleasing Issues in Landlord Tenant Law\", code: \"214029020\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Electives\", \"Legal Updates\"],\n          blurb: \"Before a lease is signed, legal issues can already arise. This course reviews common pre-leasing mistakes and the legal obligations of landlords and tenants.\" },\n        { type: \"CE\", name: \"Understanding the Financing Addendum\", code: \"214028391\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Contracts\", \"Electives\"],\n          blurb: \"Financing contingencies can make or break a deal. Learn how the financing addendum works, key deadlines, and how agents can protect their clients.\" },\n        { type: \"CE\", name: \"Virginia Agency Law\", code: \"214029764\", hours: 3, expires: \"2027-12-31\",\n          categories: [\"Agency Law\", \"Electives\"],\n          blurb: \"Agency relationships define how agents represent clients in real estate transactions. This course reviews the duties owed to clients and customers under Virginia law.\" },\n        { type: \"CE\", name: \"Walk-Throughs Gone Wrong\", code: \"214028456\", hours: 1, expires: \"2026-12-31\",\n          categories: [\"Real Estate Contracts\"],\n          blurb: \"The final walkthrough can uncover unexpected problems before closing. Learn how to handle common issues and protect your client when something goes wrong.\" },\n        { type: \"CE\", name: \"Writing It Right: Contract Fundamentals\", code: \"214031548\", hours: 2, expires: \"2029-12-31\",\n          categories: [\"Real Estate Contracts\"],\n          blurb: \"Strong contracts start with strong fundamentals. Learn the essential elements of real estate contract writing, including common clauses, timelines, and best practices for accuracy and clarity.\" },\n        { type: \"PLE\", name: \"Breaking the Chain: Divorce and Bankruptcy in Real Estate\", code: \"214030819\", hours: 2, expires: \"2028-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"Divorce and bankruptcy can complicate even the simplest real estate transaction. Learn how these legal issues impact title, ownership rights, and the ability to sell.\" },\n        { type: \"PLE\", name: \"Changes Start Now: NVAR Forms Update\", code: \"214029796\", hours: 2, expires: \"2027-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"Stay current with the latest updates to NVAR forms and how they impact real estate transactions. This course highlights key changes agents need to understand to avoid contract mistakes.\" },\n        { type: \"PLE\", name: \"Code of Ethics\", code: \"214028614\", hours: 3, expires: \"2026-12-31\",\n          categories: [\"Ethics & Standards of Conduct\"],\n          blurb: \"Review the REALTOR\\u00ae Code of Ethics and how it applies to everyday practice. Learn how ethical standards guide professional conduct, protect consumers, and resolve disputes.\" },\n        { type: \"PLE\", name: \"Deconstructing the New Home Contract\", code: \"214029017\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"New construction contracts differ significantly from resale agreements. This course breaks down key provisions, builder protections, and risks agents should understand before advising clients.\" },\n        { type: \"PLE\", name: \"Distressed Properties: Short Sales, Foreclosures\", code: \"214029019\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"Distressed sales present unique legal and logistical challenges. This course explores the processes behind short sales and foreclosures and how they affect real estate transactions.\" },\n        { type: \"PLE\", name: \"Fair Housing\", code: \"214030319\", hours: 2, expires: \"2028-12-31\",\n          categories: [\"Fair Housing, ADA and Civil Rights\"],\n          blurb: \"Understand the history, purpose, and requirements of Fair Housing laws. This course reviews protected classes, prohibited practices, and how agents can ensure compliance.\" },\n        { type: \"PLE\", name: \"General Principles of Escrow\", code: \"214028455\", hours: 3, expires: \"2026-12-31\",\n          categories: [\"Escrow Requirements\"],\n          blurb: \"Escrow plays a critical role in protecting funds and documents during a transaction. Learn how escrow works, who holds responsibility, and common issues that can arise.\" },\n        { type: \"PLE\", name: \"Home Inspection Contingency: A Refresher\", code: \"214029344\", hours: 2, expires: \"2027-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"The home inspection contingency is one of the most frequently negotiated contract provisions. This class reviews timelines, negotiation strategies, and common mistakes agents should avoid.\" },\n        { type: \"PLE\", name: \"Legislative Updates in Virginia Real Estate\", code: \"214031818\", hours: 2, expires: \"2029-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"Stay compliant with the latest industry shifts. Covers essential legislative updates and contract form changes effective this year.\" },\n        { type: \"PLE\", name: \"Mortgage Assumptions\", code: \"214028351\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Real Estate Related Finance\"],\n          blurb: \"With rising interest rates, mortgage assumptions are becoming more attractive to buyers. This course explains how assumptions work, lender requirements, and potential challenges.\" },\n        { type: \"PLE\", name: \"Resale Disclosure Act: Rules for Common Interest Communities\", code: \"214028770\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"Transactions involving HOAs, condos, and property owners associations have specific disclosure requirements. This class reviews the Virginia Resale Disclosure Act and key compliance issues.\" },\n        { type: \"PLE\", name: \"Spring Market Strategies\", code: \"214029346\", hours: 2, expires: \"2027-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"The spring market moves fast and competition can be fierce. Learn practical strategies to help buyers and sellers succeed in a high-activity real estate market.\" },\n        { type: \"PLE\", name: \"Successful Settlements\", code: \"214028458\", hours: 3, expires: \"2026-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"A smooth closing requires coordination between agents, lenders, and title professionals. This course walks through the settlement process and how agents can help ensure success.\" },\n        { type: \"PLE\", name: \"Top Preleasing Issues in Landlord Tenant Law\", code: \"214029021\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Current Industry Issues & Trends\"],\n          blurb: \"Before a lease is signed, legal issues can already arise. This course reviews common pre-leasing mistakes and the legal obligations of landlords and tenants.\" },\n        { type: \"PLE\", name: \"Understanding the Financing Addendum\", code: \"214028392\", hours: 2, expires: \"2026-12-31\",\n          categories: [\"Real Estate Related Finance\"],\n          blurb: \"Financing contingencies can make or break a deal. Learn how the financing addendum works, key deadlines, and how agents can protect their clients.\" },\n        { type: \"PLE\", name: \"Virginia Agency Law\", code: \"214029765\", hours: 3, expires: \"2027-12-31\",\n          categories: [\"Agency Law\"],\n          blurb: \"Agency relationships define how agents represent clients in real estate transactions. This course reviews the duties owed to clients and customers under Virginia law.\" }\n    ];\n\n    \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n       3. LICENSE REQUIREMENTS \u2014 update if the Board changes the rules.\n       \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n    const REQUIREMENTS = [\n        {\n            tab: 'Experienced salespersons', total: 16, label: 'hours of continuing education',\n            intro: 'Salespersons who have renewed a license at least once before need these hours to renew a license that expires on or after June 30, 2026. The same requirements apply to inactive licensees who apply to activate.',\n            rows: [\n                { hours: 3, subject: 'Ethics & Standards of Conduct' },\n                { hours: 2, subject: 'Fair Housing' },\n                { hours: 2, subject: 'Legal Updates and Emerging Trends' },\n                { hours: 2, subject: 'Real Estate Agency' },\n                { hours: 2, subject: 'Real Estate Contracts' },\n                { hours: 5, subject: 'Real Estate Related Subjects (electives), or more hours in the required subjects', open: true }\n            ]\n        },\n        {\n            tab: 'Brokers', total: 24, label: 'hours of continuing education',\n            intro: 'Brokers need these hours to renew a license that expires on or after June 30, 2026. The same requirements apply to inactive brokers who apply to activate.',\n            rows: [\n                { hours: 6, subject: 'Broker Management' },\n                { hours: 2, subject: 'Broker Supervision' },\n                { hours: 3, subject: 'Ethics & Standards of Conduct' },\n                { hours: 2, subject: 'Fair Housing' },\n                { hours: 2, subject: 'Legal Updates and Emerging Trends' },\n                { hours: 2, subject: 'Real Estate Agency' },\n                { hours: 2, subject: 'Real Estate Contracts' },\n                { hours: 5, subject: 'Real Estate Related Subjects (electives), or more hours in the required subjects', open: true }\n            ]\n        },\n        {\n            tab: 'Newly licensed salespersons', total: 30, label: 'hours of post-license education',\n            intro: 'Active new salespersons must complete the 30-hour PLE requirement within one calendar year of initial licensure, or they are placed on inactive status. They do not need the 16-hour continuing education curriculum.',\n            rows: [\n                { hours: 2, subject: 'Fair Housing, ADA and Civil Rights' },\n                { hours: 6, subject: 'Real Estate Law and Regulations' },\n                { hours: 3, subject: 'Ethics and Standards of Conduct' },\n                { hours: 2, subject: 'Current Industry Issues and Trends' },\n                { hours: 3, subject: 'Agency Law' },\n                { hours: 6, subject: 'Contract Writing' },\n                { hours: 3, subject: 'Risk Management' },\n                { hours: 3, subject: 'Escrow Requirements' },\n                { hours: 2, subject: 'Real Estate-Related Finance' }\n            ]\n        }\n    ];\n\n    \/* \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\n       Everything below is page logic. You shouldn't need to touch it.\n       \u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550 *\/\n    const root = document.querySelector('.ice');\n    const $ = (selector) => root.querySelector(selector);\n    const escapeHtml = (value) => String(value).replace(\/[&<>\"']\/g, (ch) => (\n        { '&': '&amp;', '<': '&lt;', '>': '&gt;', '\"': '&quot;', \"'\": '&#39;' }[ch]\n    ));\n    const plural = (n, word) => `${n} ${word}${n === 1 ? '' : 's'}`;\n    const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n    const coursesByCode = new Map(COURSES.map((course) => [course.code, course]));\n    const typeTag = (type) => `<span class=\"ice-type${type === 'PLE' ? ' is-ple' : ''}\">${type}<\/span>`;\n\n    \/* \u2500\u2500 Upcoming classes \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n    function parseDate(iso) {\n        const [year, month, day] = iso.split('-').map(Number);\n        return new Date(year, month - 1, day);\n    }\n\n    function upcomingEvents() {\n        const today = new Date();\n        today.setHours(0, 0, 0, 0);\n        return EVENTS\n            .map((event) => {\n                const codes = event.codes || [event.code];\n                const courses = codes.map((code) => coursesByCode.get(code)).filter(Boolean);\n                return { ...event, codes, courses, when: parseDate(event.date) };\n            })\n            .filter((event) => event.when >= today)\n            .sort((a, b) => a.when - b.when);\n    }\n\n    function renderEvents() {\n        const upcoming = upcomingEvents().slice(0, EVENTS_SHOWN);\n        $('#iceAllEvents').href = EVENTS_PAGE_URL;\n\n        if (!upcoming.length) {\n            $('#iceEvents').outerHTML =\n                '<div class=\"ice-empty\">No classes are scheduled right now. Check back soon, or email <a href=\"mailto:ice@ekkotitle.com\">ice@ekkotitle.com<\/a> with questions.<\/div>';\n            return;\n        }\n\n        $('#iceEvents').innerHTML = upcoming.map((event) => {\n            const courses = event.courses;\n            const single = courses.length === 1 ? courses[0] : null;\n            const title = event.title || (single && single.name) || 'Class';\n            const hours = event.hours ?? courses.reduce((sum, c) => sum + c.hours, 0);\n            const types = [...new Set(courses.map((c) => c.type))];\n            const categories = [...new Set(courses.flatMap((c) => c.categories))];\n            const description = event.description || (single && single.blurb) || '';\n            const weekday = event.when.toLocaleDateString('en-US', { weekday: 'long' });\n\n            const who = [\n                event.instructor && `with ${event.instructor}`,\n                event.host && `Hosted by ${event.host}`\n            ].filter(Boolean).join('. ');\n            const where = [event.venue, event.address].filter(Boolean).join(', ');\n            const credit = [\n                hours ? plural(hours, 'credit hour') : '',\n                categories.length ? `Counts as ${categories.join(', ')}` : '',\n                event.cost\n            ].filter(Boolean).join('. ');\n\n            return `\n                <li class=\"ice-event\">\n                    <div class=\"ice-date\">\n                        <span class=\"ice-date-month\">${event.when.toLocaleDateString('en-US', { month: 'short' })}<\/span>\n                        <span class=\"ice-date-day\">${event.when.getDate()}<\/span>\n                    <\/div>\n                    <div>\n                        <h3>${escapeHtml(title)}${event.featured ? '<span class=\"ice-featured\">Featured<\/span>' : ''}${event.sample ? '<span class=\"ice-sample\">Sample<\/span>' : ''}<\/h3>\n                        <div class=\"ice-event-lines\">\n                            ${who ? `<p>${escapeHtml(who)}.<\/p>` : ''}\n                            <p>${escapeHtml(`${weekday}, ${event.time}`)}${where ? '. ' + escapeHtml(where) : ''}.<\/p>\n                        <\/div>\n                        <p class=\"ice-event-credit\">${types.map(typeTag).join('')}<span>${escapeHtml(credit)}.<\/span><\/p>\n                        ${courses.length > 1 ? `<ul class=\"ice-event-sessions\">${courses.map((c) => `<li>${escapeHtml(c.name)} (${plural(c.hours, 'credit hour')})<\/li>`).join('')}<\/ul>` : ''}\n                        ${description ? `<p class=\"ice-event-desc\">${escapeHtml(description)}<\/p>` : ''}\n                    <\/div>\n                    <a class=\"ice-btn is-small\" href=\"${escapeHtml(event.url)}\" target=\"_blank\" rel=\"noopener\" aria-label=\"${escapeHtml((event.cta || 'RSVP') + ' for ' + title)}\">${escapeHtml(event.cta || 'RSVP')}<\/a>\n                <\/li>`;\n        }).join('');\n    }\n\n    \/* \u2500\u2500 Course finder \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n    const state = { type: 'all', category: '', search: '', selected: null };\n\n    const matches = (course) =>\n        (state.type === 'all' || course.type === state.type) &&\n        (!state.category || course.categories.includes(state.category)) &&\n        (!state.search || course.name.toLowerCase().includes(state.search.toLowerCase()));\n\n    function renderRequirementOptions() {\n        const categories = [...new Set(COURSES.flatMap((course) => course.categories))].sort();\n        $('#iceRequirement').innerHTML =\n            '<option value=\"\">Any requirement<\/option>' +\n            categories.map((cat) => `<option value=\"${escapeHtml(cat)}\">${escapeHtml(cat)}<\/option>`).join('');\n    }\n\n    function renderList() {\n        const list = COURSES.filter(matches);\n        if (state.selected && !list.some((course) => course.code === state.selected)) state.selected = null;\n\n        $('#iceCount').textContent = `Showing ${list.length} of ${COURSES.length} courses`;\n        $('#iceList').innerHTML = list.length\n            ? list.map((course) => `\n                <li>\n                    <button type=\"button\" class=\"ice-row\" data-code=\"${escapeHtml(course.code)}\" aria-current=\"${course.code === state.selected}\">\n                        <span class=\"ice-row-name\">${escapeHtml(course.name)}<\/span>\n                        <span class=\"ice-row-meta\">${typeTag(course.type)}<span>${plural(course.hours, 'credit hour')}<\/span><\/span>\n                    <\/button>\n                <\/li>`).join('')\n            : '<li class=\"ice-no-results\">No courses match. Clear a filter or try a different search.<\/li>';\n        $('#iceList').scrollTop = 0;\n        renderDetail();\n    }\n\n    function renderDetail() {\n        const detail = $('#iceDetail');\n        const course = coursesByCode.get(state.selected);\n        detail.classList.toggle('is-empty', !course);\n\n        if (!course) {\n            detail.innerHTML = '<p class=\"ice-detail-prompt\">Select a course to see what it covers, its course code, and when it\\'s next offered.<\/p>';\n            return;\n        }\n\n        const next = upcomingEvents().find((event) => event.codes.includes(course.code));\n        const nextBlock = next\n            ? `<div class=\"ice-next\">\n                    <strong>Next class<\/strong>\n                    <p>${escapeHtml(next.when.toLocaleDateString('en-US', { weekday: 'long', month: 'long', day: 'numeric' }))}, ${escapeHtml(next.time)}${next.venue ? ' at ' + escapeHtml(next.venue) : ''}.<\/p>\n                    <a class=\"ice-btn is-small\" href=\"${escapeHtml(next.url)}\" target=\"_blank\" rel=\"noopener\">${escapeHtml(next.cta || 'RSVP')}<\/a>\n               <\/div>`\n            : `<div class=\"ice-next\">\n                    <strong>No public class scheduled right now<\/strong>\n                    <p>Want this for your team? <a href=\"${OFFICE_CLASS_URL}\" target=\"_blank\" rel=\"noopener\">Request an office class<\/a>.<\/p>\n               <\/div>`;\n\n        detail.innerHTML = `\n            <div class=\"ice-detail-top\">${typeTag(course.type)}<span>${plural(course.hours, 'credit hour')}<\/span>${course.pending ? '<span class=\"ice-pending\">Pending approval<\/span>' : ''}<\/div>\n            <h3>${escapeHtml(course.name)}<\/h3>\n            <p class=\"ice-detail-meta\">Course code ${escapeHtml(course.code)}. Counts as ${escapeHtml(course.categories.join(', '))}.<\/p>\n            <p class=\"ice-detail-blurb\">${escapeHtml(course.blurb)}<\/p>\n            ${nextBlock}`;\n    }\n\n    $('#iceSearch').addEventListener('input', (event) => {\n        state.search = event.target.value.trim();\n        renderList();\n    });\n\n    $('#iceType').addEventListener('click', (event) => {\n        const button = event.target.closest('button');\n        if (!button) return;\n        state.type = button.dataset.type;\n        $('#iceType').querySelectorAll('button').forEach((b) => b.setAttribute('aria-pressed', String(b === button)));\n        renderList();\n    });\n\n    $('#iceRequirement').addEventListener('change', (event) => {\n        state.category = event.target.value;\n        renderList();\n    });\n\n    $('#iceList').addEventListener('click', (event) => {\n        const row = event.target.closest('.ice-row');\n        if (!row) return;\n        state.selected = row.dataset.code;\n        $('#iceList').querySelectorAll('.ice-row').forEach((r) => r.setAttribute('aria-current', String(r === row)));\n        renderDetail();\n        \/\/ On phones the detail sits above the list, so bring it into view.\n        if (window.matchMedia('(max-width: 720px)').matches) {\n            $('#iceDetail').scrollIntoView({ behavior: reduceMotion ? 'auto' : 'smooth', block: 'nearest' });\n        }\n    });\n\n    $('#iceFullCatalogue').href = CATALOGUE_URL;\n\n    \/* \u2500\u2500 Requirements \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n    function renderRequirements() {\n        $('#iceTabs').innerHTML = REQUIREMENTS.map((req, i) =>\n            `<button type=\"button\" class=\"ice-tab\" role=\"tab\" id=\"iceTab${i}\" aria-controls=\"icePanel${i}\" aria-selected=\"${i === 0}\" tabindex=\"${i === 0 ? 0 : -1}\">${escapeHtml(req.tab)}<\/button>`\n        ).join('');\n\n        $('#icePanels').innerHTML = REQUIREMENTS.map((req, i) => `\n            <div class=\"ice-panel\" role=\"tabpanel\" id=\"icePanel${i}\" aria-labelledby=\"iceTab${i}\" ${i === 0 ? '' : 'hidden'}>\n                <div class=\"ice-panel-head\">\n                    <span class=\"ice-total\">${req.total}<\/span>\n                    <p><strong>${escapeHtml(req.label)}.<\/strong> ${escapeHtml(req.intro)}<\/p>\n                <\/div>\n                <ul class=\"ice-ledger\">\n                    ${req.rows.map((row) => `\n                        <li>\n                            <span class=\"ice-hrs\">${row.hours}<\/span>\n                            <span class=\"ice-ticks${row.open ? ' is-open' : ''}\" aria-hidden=\"true\">${'<i><\/i>'.repeat(row.hours)}<\/span>\n                            <span>${escapeHtml(row.subject)}<span class=\"ice-sr\"> (${plural(row.hours, 'hour')})<\/span><\/span>\n                        <\/li>`).join('')}\n                <\/ul>\n            <\/div>`).join('');\n    }\n\n    function selectTab(index) {\n        root.querySelectorAll('.ice-tab').forEach((tab, i) => {\n            tab.setAttribute('aria-selected', String(i === index));\n            tab.tabIndex = i === index ? 0 : -1;\n        });\n        root.querySelectorAll('.ice-panel').forEach((panel, i) => { panel.hidden = i !== index; });\n    }\n\n    $('#iceTabs').addEventListener('click', (event) => {\n        const tab = event.target.closest('.ice-tab');\n        if (tab) selectTab([...$('#iceTabs').children].indexOf(tab));\n    });\n\n    $('#iceTabs').addEventListener('keydown', (event) => {\n        const tabs = [...$('#iceTabs').children];\n        const current = tabs.indexOf(document.activeElement);\n        if (current < 0 || !['ArrowRight', 'ArrowLeft'].includes(event.key)) return;\n        const next = (current + (event.key === 'ArrowRight' ? 1 : -1) + tabs.length) % tabs.length;\n        selectTab(next);\n        tabs[next].focus();\n    });\n\n    \/* \u2500\u2500 Smooth scroll for the two hero buttons \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n    root.querySelectorAll('[data-scroll]').forEach((link) => {\n        link.addEventListener('click', (event) => {\n            const target = document.getElementById(link.getAttribute('href').slice(1));\n            if (!target) return;\n            event.preventDefault();\n            target.scrollIntoView({ behavior: reduceMotion ? 'auto' : 'smooth' });\n        });\n    });\n\n    \/* \u2500\u2500 Init \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 *\/\n    renderEvents();\n    renderRequirementOptions();\n    renderList();\n    renderRequirements();\n})();\n<\/script>\n","protected":false},"excerpt":{"rendered":"<p>EKKO Title&#8217;s Institute of Continuing Education Board-approved continuing education and post-license courses for Virginia real estate professionals. Every ICE course counts toward the hours you need to renew your license. See upcoming classes Find a course Upcoming classes See the full class schedule Want a class just for your office? We&#8217;ll come to you. If &hellip;<\/p>\n<p class=\"read-more\"> <a class=\"\" href=\"https:\/\/www.ekkotitle.com\/index.php\/ce\/\"> <span class=\"screen-reader-text\">Continuing Education<\/span> Read More &raquo;<\/a><\/p>\n","protected":false},"author":3,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_monsterinsights_skip_tracking":false,"footnotes":""},"class_list":["post-1979","page","type-page","status-publish","hentry"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.ekkotitle.com\/index.php\/wp-json\/wp\/v2\/pages\/1979","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ekkotitle.com\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.ekkotitle.com\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.ekkotitle.com\/index.php\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ekkotitle.com\/index.php\/wp-json\/wp\/v2\/comments?post=1979"}],"version-history":[{"count":22,"href":"https:\/\/www.ekkotitle.com\/index.php\/wp-json\/wp\/v2\/pages\/1979\/revisions"}],"predecessor-version":[{"id":2102,"href":"https:\/\/www.ekkotitle.com\/index.php\/wp-json\/wp\/v2\/pages\/1979\/revisions\/2102"}],"wp:attachment":[{"href":"https:\/\/www.ekkotitle.com\/index.php\/wp-json\/wp\/v2\/media?parent=1979"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}