{"id":9,"date":"2026-04-18T03:44:14","date_gmt":"2026-04-18T03:44:14","guid":{"rendered":"https:\/\/fieldtrip.kiddypedia-inc.com\/?page_id=9"},"modified":"2026-04-18T03:46:56","modified_gmt":"2026-04-18T03:46:56","slug":"main-page","status":"publish","type":"page","link":"https:\/\/fieldtrip.kiddypedia-inc.com\/?page_id=9","title":{"rendered":"Main Page"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"9\" class=\"elementor elementor-9\" data-elementor-post-type=\"page\">\n\t\t\t\t<div class=\"elementor-element elementor-element-22a43e4 e-flex e-con-boxed e-con e-parent\" data-id=\"22a43e4\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-9dd840c elementor-widget elementor-widget-html\" data-id=\"9dd840c\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n<meta charset=\"UTF-8\" \/>\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" \/>\n<title>Kiddypedia \u2014 Field Trip Attractions Catalogue<\/title>\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Fraunces:ital,opsz,wght@0,9..144,400;0,9..144,600;0,9..144,800;1,9..144,400&family=IBM+Plex+Mono:wght@400;500&family=DM+Sans:wght@400;500;600;700&display=swap\" rel=\"stylesheet\">\n<style>\n  :root {\n    --white: #ffffff;\n    --off-white: #fafbfc;\n    --ink: #0a1628;\n    --ink-soft: #4a5568;\n    --ink-mute: #8a94a6;\n    --line: #e5e8ec;\n\n    --blue: #1e5fd9;\n    --blue-dark: #0d3b96;\n    --blue-soft: #dae7fc;\n\n    --yellow: #ffd23f;\n    --yellow-dark: #f0b800;\n    --yellow-soft: #fff4c4;\n\n    --accent: var(--blue);\n  }\n\n  * { box-sizing: border-box; margin: 0; padding: 0; }\n\n  html { scroll-behavior: smooth; }\n\n  body {\n    background: var(--white);\n    color: var(--ink);\n    font-family: 'DM Sans', sans-serif;\n    font-size: 16px;\n    line-height: 1.6;\n    overflow-x: hidden;\n  }\n\n  \/* ========== HEADER ========== *\/\n  header {\n    position: sticky;\n    top: 0;\n    z-index: 100;\n    background: rgba(255, 255, 255, 0.95);\n    backdrop-filter: blur(12px);\n    border-bottom: 1px solid var(--line);\n    padding: 16px 48px;\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n  }\n\n  .brand {\n    font-family: 'Fraunces', serif;\n    font-weight: 800;\n    font-size: 26px;\n    letter-spacing: -0.02em;\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    color: var(--ink);\n  }\n\n  .brand-mark {\n    width: 36px;\n    height: 36px;\n    border-radius: 10px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    font-size: 20px;\n    background: var(--blue);\n    color: var(--yellow);\n    font-weight: 800;\n    transform: rotate(-4deg);\n    box-shadow: 3px 3px 0 var(--yellow);\n  }\n\n  nav {\n    display: flex;\n    gap: 36px;\n    align-items: center;\n    font-family: 'DM Sans', sans-serif;\n    font-size: 14px;\n    font-weight: 500;\n  }\n\n  nav a {\n    color: var(--ink);\n    text-decoration: none;\n    position: relative;\n    padding-bottom: 2px;\n    transition: color 0.2s;\n  }\n\n  nav a::after {\n    content: '';\n    position: absolute;\n    bottom: -2px;\n    left: 0;\n    width: 0;\n    height: 2px;\n    background: var(--yellow);\n    transition: width 0.3s;\n  }\n\n  nav a:hover { color: var(--blue); }\n  nav a:hover::after { width: 100%; }\n\n  .wa-btn {\n    background: var(--blue);\n    color: var(--white) !important;\n    padding: 10px 18px;\n    border-radius: 999px;\n    display: inline-flex;\n    align-items: center;\n    gap: 8px;\n    font-weight: 600;\n    transition: all 0.2s;\n  }\n\n  .wa-btn::after { display: none; }\n  .wa-btn:hover {\n    background: var(--yellow);\n    color: var(--ink) !important;\n    transform: translateY(-1px);\n  }\n\n  \/* ========== HERO ========== *\/\n  .hero {\n    padding: 90px 48px 70px;\n    position: relative;\n    overflow: hidden;\n    background: var(--white);\n  }\n\n  .hero-inner {\n    max-width: 1400px;\n    margin: 0 auto;\n    position: relative;\n  }\n\n  .hero-kicker {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 12px;\n    letter-spacing: 0.2em;\n    text-transform: uppercase;\n    color: var(--blue);\n    margin-bottom: 24px;\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    font-weight: 500;\n  }\n\n  .hero-kicker::before {\n    content: '';\n    width: 40px;\n    height: 2px;\n    background: var(--yellow);\n  }\n\n  .hero h1 {\n    font-family: 'Fraunces', serif;\n    font-size: clamp(44px, 7.5vw, 104px);\n    line-height: 1;\n    font-weight: 400;\n    letter-spacing: -0.03em;\n    margin-bottom: 32px;\n    max-width: 1100px;\n    color: var(--ink);\n  }\n\n  .hero h1 em {\n    font-style: italic;\n    font-weight: 600;\n    color: var(--blue);\n  }\n\n  .hero h1 .underline {\n    position: relative;\n    display: inline-block;\n  }\n\n  .hero h1 .underline::after {\n    content: '';\n    position: absolute;\n    left: -4px;\n    right: -4px;\n    bottom: 8px;\n    height: 14px;\n    background: var(--yellow);\n    z-index: -1;\n    transform: skew(-2deg);\n  }\n\n  .hero-sub {\n    font-size: 18px;\n    max-width: 620px;\n    color: var(--ink-soft);\n    margin-bottom: 0;\n  }\n\n  .hero-stamp {\n    position: absolute;\n    top: 30px;\n    right: 30px;\n    width: 130px;\n    height: 130px;\n    border: 2px dashed var(--blue);\n    border-radius: 50%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    transform: rotate(10deg);\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 10px;\n    text-transform: uppercase;\n    letter-spacing: 0.15em;\n    color: var(--blue);\n    text-align: center;\n    line-height: 1.5;\n    padding: 16px;\n    animation: spin 40s linear infinite;\n    background: var(--yellow-soft);\n  }\n\n  @keyframes spin { from { transform: rotate(10deg); } to { transform: rotate(370deg); } }\n\n  \/* Decorative blob *\/\n  .hero::before {\n    content: '';\n    position: absolute;\n    top: -100px;\n    right: -100px;\n    width: 400px;\n    height: 400px;\n    background: radial-gradient(circle, var(--yellow-soft) 0%, transparent 70%);\n    z-index: 0;\n  }\n\n  \/* ========== SECTION HEADERS ========== *\/\n  .section-title {\n    display: flex;\n    align-items: baseline;\n    gap: 20px;\n    margin-bottom: 36px;\n    padding-bottom: 20px;\n    border-bottom: 2px solid var(--ink);\n  }\n\n  .section-title h2 {\n    font-family: 'Fraunces', serif;\n    font-size: clamp(36px, 5vw, 60px);\n    font-weight: 400;\n    letter-spacing: -0.02em;\n    font-style: italic;\n  }\n\n  .section-title .count {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 13px;\n    color: var(--ink-mute);\n    letter-spacing: 0.1em;\n  }\n\n  \/* ========== ATTRACTIONS ========== *\/\n  .attractions-section {\n    padding: 60px 48px 100px;\n    max-width: 1400px;\n    margin: 0 auto;\n    position: relative;\n    z-index: 2;\n  }\n\n  .layout {\n    display: grid;\n    grid-template-columns: 260px 1fr;\n    gap: 48px;\n  }\n\n  \/* Sidebar filters *\/\n  .filters {\n    position: sticky;\n    top: 100px;\n    align-self: start;\n    padding-right: 24px;\n    border-right: 1px solid var(--line);\n  }\n\n  .filter-group {\n    margin-bottom: 36px;\n  }\n\n  .filter-label {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    text-transform: uppercase;\n    letter-spacing: 0.15em;\n    color: var(--ink-mute);\n    margin-bottom: 14px;\n    display: block;\n    font-weight: 500;\n  }\n\n  .filter-options {\n    display: flex;\n    flex-direction: column;\n    gap: 2px;\n  }\n\n  .filter-chip {\n    display: flex;\n    align-items: center;\n    justify-content: space-between;\n    padding: 8px 10px;\n    font-family: 'DM Sans', sans-serif;\n    font-size: 15px;\n    cursor: pointer;\n    background: none;\n    border: none;\n    color: var(--ink-soft);\n    text-align: left;\n    font-weight: 500;\n    border-radius: 6px;\n    transition: all 0.2s;\n  }\n\n  .filter-chip:hover {\n    background: var(--blue-soft);\n    color: var(--blue-dark);\n  }\n\n  .filter-chip.active {\n    background: var(--blue);\n    color: var(--white);\n    font-weight: 600;\n  }\n\n  .filter-chip.active .num { color: var(--yellow); }\n\n  .filter-chip .num {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    color: var(--ink-mute);\n  }\n\n  .clear-filters {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    text-transform: uppercase;\n    letter-spacing: 0.1em;\n    color: var(--blue);\n    background: none;\n    border: none;\n    cursor: pointer;\n    padding: 0;\n    margin-top: 8px;\n    text-decoration: underline;\n    text-underline-offset: 3px;\n    font-weight: 600;\n  }\n\n  \/* Cards grid *\/\n  .grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));\n    gap: 24px;\n  }\n\n  .card {\n    background: var(--blue);\n    border: 2px solid var(--ink);\n    border-radius: 16px;\n    position: relative;\n    overflow: hidden;\n    transition: transform 0.3s cubic-bezier(0.19, 1, 0.22, 1), box-shadow 0.3s;\n    cursor: pointer;\n    display: flex;\n    flex-direction: column;\n    box-shadow: 4px 4px 0 var(--ink);\n  }\n\n  .card:hover {\n    transform: translate(-3px, -3px);\n    box-shadow: 7px 7px 0 var(--yellow), 7px 7px 0 1px var(--ink);\n  }\n\n  .card-img {\n    width: 100%;\n    height: 200px;\n    background: var(--blue-dark);\n    position: relative;\n    overflow: hidden;\n    border-bottom: 2px solid var(--ink);\n  }\n\n  .card-img img {\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n    display: block;\n    transition: transform 0.6s;\n  }\n\n  .card:hover .card-img img { transform: scale(1.05); }\n\n  .card-img-placeholder {\n    width: 100%;\n    height: 100%;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    background: linear-gradient(135deg, var(--yellow) 0%, var(--yellow-dark) 100%);\n    color: var(--blue-dark);\n    font-family: 'Fraunces', serif;\n    font-size: 64px;\n    font-style: italic;\n    font-weight: 800;\n  }\n\n  .card-tag {\n    position: absolute;\n    top: 12px;\n    left: 12px;\n    background: var(--yellow);\n    border: 1.5px solid var(--ink);\n    border-radius: 999px;\n    padding: 4px 12px;\n    font-family: 'DM Sans', sans-serif;\n    font-size: 11px;\n    text-transform: uppercase;\n    letter-spacing: 0.08em;\n    color: var(--ink);\n    font-weight: 700;\n  }\n\n  .card-body {\n    padding: 22px 24px 22px;\n    flex: 1;\n    display: flex;\n    flex-direction: column;\n    background: var(--blue);\n    color: var(--white);\n  }\n\n  .card-location {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    text-transform: uppercase;\n    letter-spacing: 0.12em;\n    color: var(--yellow);\n    margin-bottom: 10px;\n    display: flex;\n    align-items: center;\n    gap: 8px;\n    font-weight: 500;\n  }\n\n  .card-location::before {\n    content: '\u25c9';\n    font-size: 10px;\n  }\n\n  .card h3 {\n    font-family: 'Fraunces', serif;\n    font-size: 24px;\n    font-weight: 600;\n    line-height: 1.2;\n    letter-spacing: -0.01em;\n    margin-bottom: 12px;\n    color: var(--white);\n  }\n\n  .card p {\n    font-size: 14px;\n    color: rgba(255, 255, 255, 0.85);\n    line-height: 1.55;\n    flex: 1;\n  }\n\n  .card-foot {\n    margin-top: 18px;\n    padding-top: 14px;\n    border-top: 1px dashed rgba(255, 255, 255, 0.3);\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    color: var(--yellow);\n    font-weight: 500;\n    text-transform: uppercase;\n    letter-spacing: 0.08em;\n  }\n\n  .card-foot .delete-btn {\n    background: rgba(255, 255, 255, 0.15);\n    border: 1px solid rgba(255, 255, 255, 0.3);\n    color: var(--white);\n    cursor: pointer;\n    font-family: inherit;\n    font-size: 10px;\n    text-transform: uppercase;\n    letter-spacing: 0.1em;\n    padding: 4px 10px;\n    border-radius: 4px;\n    display: none;\n    transition: all 0.2s;\n  }\n\n  .card-foot .delete-btn:hover {\n    background: var(--yellow);\n    color: var(--ink);\n    border-color: var(--yellow);\n  }\n\n  body.editor-mode .card-foot .delete-btn { display: inline; }\n\n  .empty {\n    grid-column: 1 \/ -1;\n    text-align: center;\n    padding: 80px 20px;\n    border: 2px dashed var(--line);\n    background: var(--off-white);\n    border-radius: 16px;\n  }\n\n  .empty h3 {\n    font-family: 'Fraunces', serif;\n    font-size: 28px;\n    font-style: italic;\n    margin-bottom: 8px;\n    color: var(--ink);\n  }\n\n  .empty p { color: var(--ink-mute); }\n\n  \/* ========== UPDATES ========== *\/\n  .updates-section {\n    padding: 80px 48px;\n    background: var(--yellow-soft);\n    position: relative;\n    z-index: 2;\n    border-top: 2px solid var(--ink);\n    border-bottom: 2px solid var(--ink);\n  }\n\n  .updates-inner {\n    max-width: 1400px;\n    margin: 0 auto;\n  }\n\n  .updates-grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));\n    gap: 24px;\n  }\n\n  .update-card {\n    background: var(--white);\n    color: var(--ink);\n    padding: 28px;\n    border: 2px solid var(--ink);\n    border-radius: 12px;\n    position: relative;\n    transition: transform 0.3s, box-shadow 0.3s;\n    box-shadow: 3px 3px 0 var(--ink);\n  }\n\n  .update-card:hover {\n    transform: translate(-2px, -2px);\n    box-shadow: 5px 5px 0 var(--blue);\n  }\n\n  .update-date {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    text-transform: uppercase;\n    letter-spacing: 0.15em;\n    color: var(--blue);\n    margin-bottom: 12px;\n    font-weight: 600;\n  }\n\n  .update-card h3 {\n    font-family: 'Fraunces', serif;\n    font-size: 24px;\n    font-weight: 600;\n    line-height: 1.2;\n    margin-bottom: 12px;\n    letter-spacing: -0.01em;\n  }\n\n  .update-card p {\n    font-size: 14px;\n    color: var(--ink-soft);\n    margin-bottom: 16px;\n  }\n\n  .update-media {\n    margin-top: 16px;\n    border: 1.5px solid var(--ink);\n    border-radius: 8px;\n    overflow: hidden;\n  }\n\n  .update-media img, .update-media video {\n    width: 100%;\n    display: block;\n  }\n\n  .update-link {\n    display: inline-block;\n    margin-top: 12px;\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 12px;\n    color: var(--blue);\n    text-decoration: underline;\n    text-underline-offset: 3px;\n    word-break: break-all;\n    font-weight: 500;\n  }\n\n  .update-delete {\n    position: absolute;\n    top: 12px;\n    right: 12px;\n    background: var(--white);\n    border: 1px solid var(--ink);\n    color: var(--ink);\n    padding: 4px 8px;\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 10px;\n    cursor: pointer;\n    display: none;\n    border-radius: 4px;\n    text-transform: uppercase;\n    letter-spacing: 0.1em;\n    font-weight: 500;\n  }\n\n  .update-delete:hover { background: var(--ink); color: var(--white); }\n\n  body.editor-mode .update-delete { display: block; }\n\n  \/* ========== CLIENTELE ========== *\/\n  .clientele-section {\n    padding: 80px 48px;\n    max-width: 1400px;\n    margin: 0 auto;\n    position: relative;\n    z-index: 2;\n  }\n\n  .client-grid {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));\n    gap: 24px;\n    align-items: center;\n  }\n\n  .client-logo {\n    aspect-ratio: 3 \/ 2;\n    background: var(--white);\n    border: 1.5px solid var(--line);\n    border-radius: 12px;\n    display: flex;\n    align-items: center;\n    justify-content: center;\n    padding: 20px;\n    position: relative;\n    transition: all 0.3s;\n  }\n\n  .client-logo:hover {\n    transform: translateY(-2px);\n    border-color: var(--blue);\n    box-shadow: 3px 3px 0 var(--yellow);\n  }\n\n  .client-logo img {\n    max-width: 100%;\n    max-height: 100%;\n    object-fit: contain;\n    filter: grayscale(0.2);\n    transition: filter 0.3s;\n  }\n\n  .client-logo:hover img { filter: grayscale(0); }\n\n  .client-delete {\n    position: absolute;\n    top: 6px;\n    right: 6px;\n    background: var(--blue);\n    color: var(--white);\n    border: none;\n    width: 24px;\n    height: 24px;\n    border-radius: 50%;\n    cursor: pointer;\n    font-size: 14px;\n    line-height: 1;\n    display: none;\n    font-weight: bold;\n  }\n\n  body.editor-mode .client-delete {\n    display: flex;\n    align-items: center;\n    justify-content: center;\n  }\n\n  \/* ========== FOOTER ========== *\/\n  footer {\n    background: var(--ink);\n    color: var(--white);\n    padding: 70px 48px 30px;\n    position: relative;\n    z-index: 2;\n  }\n\n  .footer-inner {\n    max-width: 1400px;\n    margin: 0 auto;\n    display: flex;\n    justify-content: space-between;\n    align-items: end;\n    gap: 40px;\n    flex-wrap: wrap;\n  }\n\n  .footer-brand {\n    font-family: 'Fraunces', serif;\n    font-style: italic;\n    font-size: 48px;\n    font-weight: 400;\n    line-height: 1.05;\n    letter-spacing: -0.02em;\n    max-width: 600px;\n    color: var(--white);\n  }\n\n  .footer-brand em { color: var(--yellow); font-weight: 600; }\n\n  .footer-meta {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    color: rgba(255, 255, 255, 0.6);\n    text-transform: uppercase;\n    letter-spacing: 0.1em;\n    text-align: right;\n    line-height: 1.8;\n  }\n\n  .footer-meta a { color: var(--yellow); text-decoration: none; }\n  .footer-meta a:hover { text-decoration: underline; }\n\n  .secret-btn {\n    background: none;\n    border: none;\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 10px;\n    color: rgba(255, 255, 255, 0.25);\n    cursor: pointer;\n    padding: 8px;\n    margin-top: 16px;\n    letter-spacing: 0.1em;\n    transition: color 0.2s;\n  }\n\n  .secret-btn:hover { color: var(--yellow); }\n\n  \/* ========== EDITOR BAR ========== *\/\n  .editor-bar {\n    position: fixed;\n    bottom: 0;\n    left: 0;\n    right: 0;\n    background: var(--ink);\n    color: var(--white);\n    padding: 14px 48px;\n    display: none;\n    align-items: center;\n    justify-content: space-between;\n    gap: 20px;\n    z-index: 90;\n    border-top: 3px solid var(--yellow);\n    transform: translateY(100%);\n    transition: transform 0.4s cubic-bezier(0.19, 1, 0.22, 1);\n  }\n\n  body.editor-mode .editor-bar {\n    display: flex;\n    transform: translateY(0);\n  }\n\n  .editor-bar-left {\n    display: flex;\n    align-items: center;\n    gap: 12px;\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 12px;\n    text-transform: uppercase;\n    letter-spacing: 0.1em;\n    font-weight: 500;\n  }\n\n  .editor-bar-left::before {\n    content: '\u25cf';\n    color: var(--yellow);\n    animation: pulse 2s infinite;\n  }\n\n  @keyframes pulse { 50% { opacity: 0.3; } }\n\n  .editor-bar-actions {\n    display: flex;\n    gap: 10px;\n    flex-wrap: wrap;\n  }\n\n  .btn {\n    font-family: 'DM Sans', sans-serif;\n    font-size: 13px;\n    padding: 10px 16px;\n    border: 1.5px solid var(--yellow);\n    background: var(--yellow);\n    color: var(--ink);\n    cursor: pointer;\n    transition: all 0.2s;\n    border-radius: 6px;\n    font-weight: 600;\n  }\n\n  .btn:hover { background: var(--white); border-color: var(--white); }\n\n  .btn.btn-ghost {\n    background: transparent;\n    border-color: rgba(255, 255, 255, 0.3);\n    color: var(--white);\n  }\n\n  .btn.btn-ghost:hover { background: rgba(255, 255, 255, 0.1); border-color: var(--white); }\n\n  \/* ========== MODAL ========== *\/\n  .modal-overlay {\n    position: fixed;\n    inset: 0;\n    background: rgba(10, 22, 40, 0.6);\n    backdrop-filter: blur(6px);\n    display: none;\n    align-items: center;\n    justify-content: center;\n    z-index: 200;\n    padding: 20px;\n    animation: fadeIn 0.2s;\n  }\n\n  @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }\n\n  .modal-overlay.active { display: flex; }\n\n  .modal {\n    background: var(--white);\n    border: 2px solid var(--ink);\n    border-radius: 16px;\n    max-width: 560px;\n    width: 100%;\n    max-height: 90vh;\n    overflow-y: auto;\n    box-shadow: 8px 8px 0 var(--blue);\n    animation: slideUp 0.3s cubic-bezier(0.19, 1, 0.22, 1);\n  }\n\n  @keyframes slideUp { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } }\n\n  .modal-head {\n    padding: 22px 28px;\n    border-bottom: 1px solid var(--line);\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n  }\n\n  .modal-head h2 {\n    font-family: 'Fraunces', serif;\n    font-size: 26px;\n    font-weight: 600;\n    font-style: italic;\n    letter-spacing: -0.01em;\n  }\n\n  .modal-close {\n    background: var(--off-white);\n    border: 1px solid var(--line);\n    border-radius: 50%;\n    width: 32px;\n    height: 32px;\n    font-size: 18px;\n    cursor: pointer;\n    color: var(--ink);\n    line-height: 1;\n    transition: all 0.2s;\n  }\n\n  .modal-close:hover { background: var(--yellow); border-color: var(--ink); }\n\n  .modal-body {\n    padding: 24px 28px;\n  }\n\n  .field {\n    margin-bottom: 20px;\n  }\n\n  .field label {\n    display: block;\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    text-transform: uppercase;\n    letter-spacing: 0.1em;\n    margin-bottom: 8px;\n    color: var(--ink-soft);\n    font-weight: 500;\n  }\n\n  .field input, .field textarea, .field select {\n    width: 100%;\n    padding: 12px 14px;\n    border: 1.5px solid var(--line);\n    background: var(--white);\n    font-family: 'DM Sans', sans-serif;\n    font-size: 15px;\n    color: var(--ink);\n    border-radius: 8px;\n    transition: all 0.2s;\n  }\n\n  .field textarea { resize: vertical; min-height: 80px; font-family: 'DM Sans', sans-serif; }\n\n  .field input:focus, .field textarea:focus, .field select:focus {\n    outline: none;\n    border-color: var(--blue);\n    box-shadow: 0 0 0 3px var(--blue-soft);\n  }\n\n  .field-hint {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 10px;\n    color: var(--ink-mute);\n    margin-top: 6px;\n    letter-spacing: 0.05em;\n  }\n\n  .field-row {\n    display: flex;\n    gap: 8px;\n    align-items: stretch;\n  }\n\n  .field-row select { flex: 1; }\n\n  .add-cat-btn {\n    padding: 0 14px;\n    background: var(--yellow);\n    border: 1.5px solid var(--ink);\n    border-radius: 8px;\n    cursor: pointer;\n    font-family: 'DM Sans', sans-serif;\n    font-weight: 700;\n    font-size: 14px;\n    color: var(--ink);\n    transition: all 0.2s;\n    white-space: nowrap;\n  }\n\n  .add-cat-btn:hover { background: var(--ink); color: var(--yellow); }\n\n  .img-preview {\n    display: flex;\n    gap: 8px;\n    flex-wrap: wrap;\n    margin-top: 10px;\n  }\n\n  .img-preview > div {\n    position: relative;\n    width: 64px;\n    height: 64px;\n    border: 1.5px solid var(--ink);\n    border-radius: 8px;\n    overflow: hidden;\n  }\n\n  .img-preview img {\n    width: 100%;\n    height: 100%;\n    object-fit: cover;\n  }\n\n  .img-preview button {\n    position: absolute;\n    top: -6px;\n    right: -6px;\n    background: var(--blue);\n    color: var(--white);\n    border: 2px solid var(--white);\n    width: 20px;\n    height: 20px;\n    border-radius: 50%;\n    cursor: pointer;\n    font-size: 10px;\n    line-height: 1;\n    font-weight: bold;\n  }\n\n  .modal-foot {\n    padding: 18px 28px;\n    border-top: 1px solid var(--line);\n    display: flex;\n    justify-content: flex-end;\n    gap: 10px;\n  }\n\n  .btn-light {\n    font-family: 'DM Sans', sans-serif;\n    font-size: 14px;\n    padding: 10px 20px;\n    border: 1.5px solid var(--line);\n    background: var(--white);\n    color: var(--ink);\n    cursor: pointer;\n    transition: all 0.2s;\n    border-radius: 8px;\n    font-weight: 500;\n  }\n\n  .btn-light:hover { border-color: var(--ink); }\n\n  .btn-light.primary {\n    background: var(--blue);\n    color: var(--white);\n    border-color: var(--blue);\n    font-weight: 600;\n  }\n\n  .btn-light.primary:hover {\n    background: var(--ink);\n    border-color: var(--ink);\n  }\n\n  \/* Category management list *\/\n  .cat-manage-list {\n    border: 1.5px solid var(--line);\n    border-radius: 8px;\n    max-height: 200px;\n    overflow-y: auto;\n    margin-top: 6px;\n  }\n\n  .cat-row {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 10px 14px;\n    border-bottom: 1px solid var(--line);\n    font-size: 14px;\n  }\n\n  .cat-row:last-child { border-bottom: none; }\n\n  .cat-row button {\n    background: none;\n    border: 1px solid var(--line);\n    color: var(--ink-soft);\n    padding: 4px 10px;\n    font-size: 11px;\n    border-radius: 4px;\n    cursor: pointer;\n    font-family: 'IBM Plex Mono', monospace;\n    text-transform: uppercase;\n    letter-spacing: 0.08em;\n    transition: all 0.2s;\n  }\n\n  .cat-row button:hover { background: var(--blue); color: var(--white); border-color: var(--blue); }\n\n  .cat-count {\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    color: var(--ink-mute);\n  }\n\n  \/* ========== TOAST ========== *\/\n  .toast {\n    position: fixed;\n    bottom: 90px;\n    left: 50%;\n    transform: translateX(-50%) translateY(20px);\n    background: var(--ink);\n    color: var(--white);\n    padding: 12px 24px;\n    font-family: 'DM Sans', sans-serif;\n    font-size: 14px;\n    font-weight: 500;\n    border: 2px solid var(--yellow);\n    border-radius: 999px;\n    opacity: 0;\n    transition: all 0.3s;\n    z-index: 300;\n    pointer-events: none;\n  }\n\n  .toast.show {\n    opacity: 1;\n    transform: translateX(-50%) translateY(0);\n  }\n\n  \/* ========== DETAIL MODAL ========== *\/\n  .detail-modal {\n    max-width: 760px;\n  }\n\n  .detail-hero {\n    height: 320px;\n    background: var(--blue-dark);\n    position: relative;\n    overflow: hidden;\n    border-bottom: 2px solid var(--ink);\n  }\n\n  .detail-hero img { width: 100%; height: 100%; object-fit: cover; }\n\n  .detail-hero .card-img-placeholder {\n    border-radius: 0;\n  }\n\n  .detail-thumbs {\n    display: flex;\n    gap: 8px;\n    padding: 12px 28px;\n    border-bottom: 1px solid var(--line);\n    overflow-x: auto;\n  }\n\n  .detail-thumbs img {\n    width: 70px;\n    height: 70px;\n    object-fit: cover;\n    border: 1.5px solid var(--line);\n    border-radius: 6px;\n    cursor: pointer;\n    opacity: 0.6;\n    transition: all 0.2s;\n  }\n\n  .detail-thumbs img:hover, .detail-thumbs img.active {\n    opacity: 1;\n    border-color: var(--blue);\n  }\n\n  .detail-meta {\n    display: flex;\n    gap: 20px;\n    font-family: 'IBM Plex Mono', monospace;\n    font-size: 11px;\n    text-transform: uppercase;\n    letter-spacing: 0.1em;\n    color: var(--ink-mute);\n    margin-bottom: 16px;\n    font-weight: 500;\n  }\n\n  .detail-meta span::before {\n    content: '\u25c9 ';\n    color: var(--blue);\n  }\n\n  .detail-title {\n    font-family: 'Fraunces', serif;\n    font-size: 40px;\n    font-weight: 600;\n    line-height: 1.1;\n    letter-spacing: -0.02em;\n    margin-bottom: 16px;\n  }\n\n  .detail-desc {\n    font-size: 16px;\n    color: var(--ink-soft);\n    line-height: 1.7;\n  }\n\n  \/* ========== RESPONSIVE ========== *\/\n  @media (max-width: 900px) {\n    header { padding: 12px 20px; }\n    .brand { font-size: 20px; }\n    nav { gap: 16px; font-size: 13px; }\n    .hero { padding: 50px 20px 40px; }\n    .hero-stamp { display: none; }\n    .attractions-section, .clientele-section { padding: 40px 20px; }\n    .updates-section { padding: 50px 20px; }\n    footer { padding: 40px 20px 20px; }\n    .layout { grid-template-columns: 1fr; gap: 30px; }\n    .filters {\n      position: static;\n      border-right: none;\n      border-bottom: 1px solid var(--line);\n      padding-bottom: 20px;\n    }\n    .filter-options {\n      flex-direction: row;\n      flex-wrap: wrap;\n      gap: 6px;\n    }\n    .filter-chip {\n      padding: 6px 14px;\n      border: 1.5px solid var(--line);\n      font-size: 13px;\n      border-radius: 999px;\n    }\n    .filter-chip .num { display: none; }\n    .editor-bar { padding: 12px 16px; flex-direction: column; gap: 10px; }\n    .footer-brand { font-size: 32px; }\n    .footer-meta { text-align: left; }\n    nav a.hide-mobile { display: none; }\n    .detail-title { font-size: 30px; }\n  }\n<\/style>\n<\/head>\n<body>\n\n<!-- HEADER -->\n<header>\n  <div class=\"brand\">\n    <div class=\"brand-mark\">K<\/div>\n    Kiddypedia\n  <\/div>\n  <nav>\n    <a href=\"#home\" class=\"hide-mobile\">Home<\/a>\n    <a href=\"#attractions\">Attractions<\/a>\n    <a href=\"#updates\">Updates<\/a>\n    <a href=\"https:\/\/wa.me\/0125586015\" target=\"_blank\" class=\"wa-btn\">\n      <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M17.5 14.4l-2.5-1.2c-.3-.1-.7 0-.9.2l-1.1 1.1c-.2.2-.5.3-.8.1-1.3-.7-2.4-1.8-3.1-3.1-.1-.3-.1-.6.1-.8l1.1-1.1c.2-.2.3-.6.2-.9L9.3 6.2c-.2-.4-.7-.6-1.1-.4l-1.6.5C6 6.5 5.5 7.1 5.5 7.8c0 5.3 4.4 9.7 9.7 9.7.7 0 1.3-.5 1.5-1.1l.5-1.6c.1-.4-.1-.9-.5-1.1z\"\/><path d=\"M12 2C6.5 2 2 6.5 2 12c0 1.8.5 3.5 1.3 5L2 22l5.1-1.3c1.4.8 3.1 1.3 4.9 1.3 5.5 0 10-4.5 10-10S17.5 2 12 2zm0 18c-1.6 0-3.1-.4-4.4-1.2l-.3-.2-3.1.8.8-3-.2-.3C4 14.9 3.5 13.5 3.5 12c0-4.7 3.8-8.5 8.5-8.5s8.5 3.8 8.5 8.5-3.8 8.5-8.5 8.5z\"\/><\/svg>\n      Make Enquiry\n    <\/a>\n  <\/nav>\n<\/header>\n\n<!-- HERO -->\n<section class=\"hero\" id=\"home\">\n  <div class=\"hero-inner\">\n    <div class=\"hero-kicker\">Field trip catalogue \u00b7 Malaysia edition<\/div>\n    <h1>A living atlas of <em>places worth<\/em><br>learning from, curated for <span class=\"underline\">curious classrooms<\/span>.<\/h1>\n    <p class=\"hero-sub\">Browse museums, workshops, farms, and studios across every Malaysian state. Built for principals and teachers planning trips that stick.<\/p>\n    <div class=\"hero-stamp\">Verified<br><strong style=\"font-size:14px;\">Educators<\/strong><br>since '24<\/div>\n  <\/div>\n<\/section>\n\n<!-- ATTRACTIONS -->\n<section class=\"attractions-section\" id=\"attractions\">\n  <div class=\"section-title\">\n    <h2>The Catalogue<\/h2>\n    <span class=\"count\" id=\"countLabel\">\u2014 loading<\/span>\n  <\/div>\n\n  <div class=\"layout\">\n    <aside class=\"filters\">\n      <div class=\"filter-group\">\n        <span class=\"filter-label\">Category<\/span>\n        <div class=\"filter-options\" id=\"categoryFilters\"><\/div>\n      <\/div>\n      <div class=\"filter-group\">\n        <span class=\"filter-label\">Location<\/span>\n        <div class=\"filter-options\" id=\"locationFilters\"><\/div>\n      <\/div>\n      <button class=\"clear-filters\" onclick=\"clearFilters()\">Clear all filters<\/button>\n    <\/aside>\n    <div class=\"grid\" id=\"attractionsGrid\"><\/div>\n  <\/div>\n<\/section>\n\n<!-- UPDATES -->\n<section class=\"updates-section\" id=\"updates\">\n  <div class=\"updates-inner\">\n    <div class=\"section-title\">\n      <h2>Updates &amp; Announcements<\/h2>\n      <span class=\"count\" id=\"updatesCount\">\u2014<\/span>\n    <\/div>\n    <div class=\"updates-grid\" id=\"updatesGrid\"><\/div>\n  <\/div>\n<\/section>\n\n<!-- CLIENTELE -->\n<section class=\"clientele-section\" id=\"clientele\">\n  <div class=\"section-title\">\n    <h2>Our Clientele<\/h2>\n    <span class=\"count\" id=\"clientsCount\">\u2014<\/span>\n  <\/div>\n  <div class=\"client-grid\" id=\"clientsGrid\"><\/div>\n<\/section>\n\n<!-- FOOTER -->\n<footer>\n  <div class=\"footer-inner\">\n    <div class=\"footer-brand\">Planning a trip?<br>Let's make it <em>unforgettable<\/em>.<\/div>\n    <div class=\"footer-meta\">\n      \u00a9 2026 Kiddypedia<br>\n      A catalogue for curious classrooms<br>\n      <a href=\"https:\/\/wa.me\/0125586015\">+60 12-558 6015<\/a>\n      <br>\n      <button class=\"secret-btn\" onclick=\"openPasscode()\">\u00b7 editor access \u00b7<\/button>\n    <\/div>\n  <\/div>\n<\/footer>\n\n<!-- EDITOR BAR -->\n<div class=\"editor-bar\">\n  <div class=\"editor-bar-left\">Editor mode active<\/div>\n  <div class=\"editor-bar-actions\">\n    <button class=\"btn\" onclick=\"openAttractionModal()\">+ Attraction<\/button>\n    <button class=\"btn\" onclick=\"openUpdateModal()\">+ Update<\/button>\n    <button class=\"btn\" onclick=\"openClientModal()\">+ Clientele<\/button>\n    <button class=\"btn\" onclick=\"openCategoryManager()\">Manage Categories<\/button>\n    <button class=\"btn btn-ghost\" onclick=\"exitEditor()\">Exit<\/button>\n  <\/div>\n<\/div>\n\n<!-- PASSCODE MODAL -->\n<div class=\"modal-overlay\" id=\"passcodeModal\">\n  <div class=\"modal\">\n    <div class=\"modal-head\">\n      <h2>Editor Access<\/h2>\n      <button class=\"modal-close\" onclick=\"closeModal('passcodeModal')\">\u00d7<\/button>\n    <\/div>\n    <div class=\"modal-body\">\n      <div class=\"field\">\n        <label>Passcode<\/label>\n        <input type=\"password\" id=\"passcodeInput\" placeholder=\"Enter passcode\" autocomplete=\"off\" \/>\n        <div class=\"field-hint\">Required for content editing<\/div>\n      <\/div>\n    <\/div>\n    <div class=\"modal-foot\">\n      <button class=\"btn-light\" onclick=\"closeModal('passcodeModal')\">Cancel<\/button>\n      <button class=\"btn-light primary\" onclick=\"verifyPasscode()\">Enter<\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<!-- ATTRACTION MODAL -->\n<div class=\"modal-overlay\" id=\"attractionModal\">\n  <div class=\"modal\">\n    <div class=\"modal-head\">\n      <h2>New Attraction<\/h2>\n      <button class=\"modal-close\" onclick=\"closeModal('attractionModal')\">\u00d7<\/button>\n    <\/div>\n    <div class=\"modal-body\">\n      <div class=\"field\">\n        <label>Name<\/label>\n        <input type=\"text\" id=\"attrName\" placeholder=\"e.g. Petrosains Discovery Centre\" \/>\n      <\/div>\n      <div class=\"field\">\n        <label>Short Description<\/label>\n        <textarea id=\"attrDesc\" placeholder=\"A sentence or two describing the experience...\"><\/textarea>\n      <\/div>\n      <div class=\"field\">\n        <label>Category<\/label>\n        <div class=\"field-row\">\n          <select id=\"attrCategory\"><\/select>\n          <button type=\"button\" class=\"add-cat-btn\" onclick=\"promptNewCategory()\">+ New<\/button>\n        <\/div>\n      <\/div>\n      <div class=\"field\">\n        <label>Location (State)<\/label>\n        <select id=\"attrLocation\">\n          <option>Johor<\/option>\n          <option>Kedah<\/option>\n          <option>Kelantan<\/option>\n          <option>Kuala Lumpur<\/option>\n          <option>Labuan<\/option>\n          <option>Malacca<\/option>\n          <option>Negeri Sembilan<\/option>\n          <option>Pahang<\/option>\n          <option>Penang<\/option>\n          <option>Perak<\/option>\n          <option>Perlis<\/option>\n          <option>Putrajaya<\/option>\n          <option>Sabah<\/option>\n          <option>Sarawak<\/option>\n          <option>Selangor<\/option>\n          <option>Terengganu<\/option>\n        <\/select>\n      <\/div>\n      <div class=\"field\">\n        <label>Images<\/label>\n        <input type=\"file\" id=\"attrImages\" accept=\"image\/*\" multiple \/>\n        <div class=\"field-hint\">You can upload multiple images<\/div>\n        <div class=\"img-preview\" id=\"attrImgPreview\"><\/div>\n      <\/div>\n    <\/div>\n    <div class=\"modal-foot\">\n      <button class=\"btn-light\" onclick=\"closeModal('attractionModal')\">Cancel<\/button>\n      <button class=\"btn-light primary\" onclick=\"saveAttraction()\">Save Attraction<\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<!-- CATEGORY MANAGER MODAL -->\n<div class=\"modal-overlay\" id=\"categoryModal\">\n  <div class=\"modal\">\n    <div class=\"modal-head\">\n      <h2>Manage Categories<\/h2>\n      <button class=\"modal-close\" onclick=\"closeModal('categoryModal')\">\u00d7<\/button>\n    <\/div>\n    <div class=\"modal-body\">\n      <div class=\"field\">\n        <label>Add New Category<\/label>\n        <div class=\"field-row\">\n          <input type=\"text\" id=\"newCatInput\" placeholder=\"e.g. Planetarium, Farm Stay, Aquarium\" \/>\n          <button type=\"button\" class=\"add-cat-btn\" onclick=\"addCategoryFromManager()\">Add<\/button>\n        <\/div>\n        <div class=\"field-hint\">New categories become available for attractions<\/div>\n      <\/div>\n      <div class=\"field\">\n        <label>Existing Categories<\/label>\n        <div class=\"cat-manage-list\" id=\"catManageList\"><\/div>\n      <\/div>\n    <\/div>\n    <div class=\"modal-foot\">\n      <button class=\"btn-light primary\" onclick=\"closeModal('categoryModal')\">Done<\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<!-- UPDATE MODAL -->\n<div class=\"modal-overlay\" id=\"updateModal\">\n  <div class=\"modal\">\n    <div class=\"modal-head\">\n      <h2>New Update<\/h2>\n      <button class=\"modal-close\" onclick=\"closeModal('updateModal')\">\u00d7<\/button>\n    <\/div>\n    <div class=\"modal-body\">\n      <div class=\"field\">\n        <label>Title<\/label>\n        <input type=\"text\" id=\"updTitle\" placeholder=\"e.g. New attraction added!\" \/>\n      <\/div>\n      <div class=\"field\">\n        <label>Announcement<\/label>\n        <textarea id=\"updBody\" placeholder=\"Write your announcement...\"><\/textarea>\n      <\/div>\n      <div class=\"field\">\n        <label>Picture \/ Video (optional)<\/label>\n        <input type=\"file\" id=\"updMedia\" accept=\"image\/*,video\/*\" \/>\n        <div class=\"field-hint\">Upload an image or video<\/div>\n        <div class=\"img-preview\" id=\"updMediaPreview\"><\/div>\n      <\/div>\n      <div class=\"field\">\n        <label>Link (optional)<\/label>\n        <input type=\"url\" id=\"updLink\" placeholder=\"https:\/\/...\" \/>\n      <\/div>\n    <\/div>\n    <div class=\"modal-foot\">\n      <button class=\"btn-light\" onclick=\"closeModal('updateModal')\">Cancel<\/button>\n      <button class=\"btn-light primary\" onclick=\"saveUpdate()\">Post Update<\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<!-- CLIENT MODAL -->\n<div class=\"modal-overlay\" id=\"clientModal\">\n  <div class=\"modal\">\n    <div class=\"modal-head\">\n      <h2>Add Clientele<\/h2>\n      <button class=\"modal-close\" onclick=\"closeModal('clientModal')\">\u00d7<\/button>\n    <\/div>\n    <div class=\"modal-body\">\n      <div class=\"field\">\n        <label>Logo<\/label>\n        <input type=\"file\" id=\"clientLogo\" accept=\"image\/*\" \/>\n        <div class=\"field-hint\">Upload a logo image (PNG with transparency works best)<\/div>\n        <div class=\"img-preview\" id=\"clientLogoPreview\"><\/div>\n      <\/div>\n    <\/div>\n    <div class=\"modal-foot\">\n      <button class=\"btn-light\" onclick=\"closeModal('clientModal')\">Cancel<\/button>\n      <button class=\"btn-light primary\" onclick=\"saveClient()\">Save Logo<\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<!-- DETAIL MODAL -->\n<div class=\"modal-overlay\" id=\"detailModal\">\n  <div class=\"modal detail-modal\">\n    <div class=\"detail-hero\" id=\"detailHero\"><\/div>\n    <div class=\"detail-thumbs\" id=\"detailThumbs\"><\/div>\n    <div class=\"modal-body\">\n      <div class=\"detail-meta\" id=\"detailMeta\"><\/div>\n      <h1 class=\"detail-title\" id=\"detailTitle\"><\/h1>\n      <p class=\"detail-desc\" id=\"detailDesc\"><\/p>\n    <\/div>\n    <div class=\"modal-foot\">\n      <a href=\"https:\/\/wa.me\/0125586015\" target=\"_blank\" class=\"btn-light primary\" style=\"text-decoration:none;\">Enquire via WhatsApp<\/a>\n      <button class=\"btn-light\" onclick=\"closeModal('detailModal')\">Close<\/button>\n    <\/div>\n  <\/div>\n<\/div>\n\n<!-- TOAST -->\n<div class=\"toast\" id=\"toast\"><\/div>\n\n<script>\n  \/\/ ========== STATE ==========\n  const STORAGE_KEY = 'kiddypedia_data_v2';\n  let data = {\n    attractions: [],\n    updates: [],\n    clients: [],\n    categories: []\n  };\n\n  const DEFAULT_CATEGORIES = [\n    'Museum', 'Workshop', 'Nature & Farm', 'Science Centre',\n    'Cultural Heritage', 'Art Studio', 'Theme Park', 'Factory Tour'\n  ];\n\n  const SEED = {\n    categories: DEFAULT_CATEGORIES.slice(),\n    attractions: [\n      { id: 1, name: 'Petrosains Discovery Centre', desc: 'Hands-on science exhibits exploring the story of petroleum and its place in everyday life. Interactive, noisy, and thoroughly engaging for primary and secondary students.', category: 'Science Centre', location: 'Kuala Lumpur', images: [] },\n      { id: 2, name: 'Rimbun Dahan Art Residency', desc: 'A working garden estate hosting resident artists. Perfect for older students studying visual arts, sustainability, and traditional Malay architecture.', category: 'Art Studio', location: 'Selangor', images: [] },\n      { id: 3, name: 'Penang Batik Workshop', desc: 'Learn the wax-and-dye craft from master artisans. Each student walks home with their own hand-drawn piece of fabric.', category: 'Workshop', location: 'Penang', images: [] },\n      { id: 4, name: 'Kinabalu National Park', desc: 'A UNESCO World Heritage Site and one of the world\\'s most biodiverse spots. Guided nature walks for students of all ages.', category: 'Nature & Farm', location: 'Sabah', images: [] },\n      { id: 5, name: 'Melaka Maritime Museum', desc: 'Housed inside a replica Portuguese galleon, this museum walks visitors through five centuries of Melaka\\'s trading history.', category: 'Museum', location: 'Malacca', images: [] },\n      { id: 6, name: 'Sarawak Cultural Village', desc: 'A \"living museum\" with full-scale replicas of traditional longhouses from all major ethnic groups, complete with cultural performances.', category: 'Cultural Heritage', location: 'Sarawak', images: [] }\n    ],\n    updates: [\n      { id: 1, title: '10 new Sabah attractions added', body: 'We\\'ve expanded our East Malaysia catalogue with ten new verified destinations including marine parks, orangutan sanctuaries, and heritage sites.', media: null, mediaType: null, link: '', date: 'April 2026' },\n      { id: 2, title: 'School holiday workshop season opens', body: 'Many partner workshops are now taking bookings for June term break. Enquire early \u2014 popular slots fill up fast.', media: null, mediaType: null, link: '', date: 'March 2026' }\n    ],\n    clients: []\n  };\n\n  function load() {\n    const saved = localStorage.getItem(STORAGE_KEY);\n    if (saved) {\n      try {\n        data = JSON.parse(saved);\n        \/\/ Migration: ensure categories array exists\n        if (!data.categories || !Array.isArray(data.categories)) {\n          const existingCats = [...new Set(data.attractions.map(a => a.category))];\n          data.categories = [...new Set([...DEFAULT_CATEGORIES, ...existingCats])];\n        }\n      }\n      catch(e) { data = JSON.parse(JSON.stringify(SEED)); }\n    } else {\n      data = JSON.parse(JSON.stringify(SEED));\n      save();\n    }\n  }\n\n  function save() {\n    try {\n      localStorage.setItem(STORAGE_KEY, JSON.stringify(data));\n    } catch(e) {\n      toast('Storage full \u2014 try removing images');\n    }\n  }\n\n  \/\/ ========== FILTERS ==========\n  let filters = { category: null, location: null };\n\n  function renderFilters() {\n    \/\/ Show ALL categories from data.categories, with counts\n    const catCounts = {};\n    const locCounts = {};\n    data.attractions.forEach(a => {\n      catCounts[a.category] = (catCounts[a.category] || 0) + 1;\n      locCounts[a.location] = (locCounts[a.location] || 0) + 1;\n    });\n\n    const catEl = document.getElementById('categoryFilters');\n    const locEl = document.getElementById('locationFilters');\n\n    \/\/ Show categories that have at least one attraction\n    const visibleCats = data.categories.filter(c => catCounts[c]).sort();\n\n    catEl.innerHTML = visibleCats.map(c =>\n      `<button class=\"filter-chip ${filters.category === c ? 'active' : ''}\" onclick=\"toggleFilter('category', '${escapeAttr(c)}')\">\n        <span>${escapeHtml(c)}<\/span><span class=\"num\">${catCounts[c]}<\/span>\n      <\/button>`\n    ).join('') || '<span style=\"font-size:12px;color:var(--ink-mute);padding:8px 10px;\">No categories yet<\/span>';\n\n    locEl.innerHTML = Object.entries(locCounts).sort().map(([l, n]) =>\n      `<button class=\"filter-chip ${filters.location === l ? 'active' : ''}\" onclick=\"toggleFilter('location', '${escapeAttr(l)}')\">\n        <span>${escapeHtml(l)}<\/span><span class=\"num\">${n}<\/span>\n      <\/button>`\n    ).join('') || '<span style=\"font-size:12px;color:var(--ink-mute);padding:8px 10px;\">No locations yet<\/span>';\n  }\n\n  function toggleFilter(key, val) {\n    filters[key] = filters[key] === val ? null : val;\n    renderFilters();\n    renderAttractions();\n  }\n\n  function clearFilters() {\n    filters = { category: null, location: null };\n    renderFilters();\n    renderAttractions();\n  }\n\n  \/\/ ========== CATEGORY MANAGEMENT ==========\n  function populateCategorySelect() {\n    const sel = document.getElementById('attrCategory');\n    const current = sel.value;\n    sel.innerHTML = data.categories.map(c =>\n      `<option value=\"${escapeAttr(c)}\">${escapeHtml(c)}<\/option>`\n    ).join('');\n    if (current && data.categories.includes(current)) sel.value = current;\n  }\n\n  function promptNewCategory() {\n    const name = prompt('New category name:');\n    if (!name) return;\n    const trimmed = name.trim();\n    if (!trimmed) return;\n    if (data.categories.some(c => c.toLowerCase() === trimmed.toLowerCase())) {\n      toast('Category already exists');\n      return;\n    }\n    data.categories.push(trimmed);\n    data.categories.sort();\n    save();\n    populateCategorySelect();\n    document.getElementById('attrCategory').value = trimmed;\n    toast('Category added');\n  }\n\n  function openCategoryManager() {\n    openModal('categoryModal');\n    renderCatManageList();\n    document.getElementById('newCatInput').value = '';\n    setTimeout(() => document.getElementById('newCatInput').focus(), 100);\n  }\n\n  function addCategoryFromManager() {\n    const input = document.getElementById('newCatInput');\n    const val = input.value.trim();\n    if (!val) { toast('Enter a category name'); return; }\n    if (data.categories.some(c => c.toLowerCase() === val.toLowerCase())) {\n      toast('Category already exists');\n      return;\n    }\n    data.categories.push(val);\n    data.categories.sort();\n    save();\n    renderCatManageList();\n    populateCategorySelect();\n    renderFilters();\n    input.value = '';\n    input.focus();\n    toast('Category added');\n  }\n\n  function renderCatManageList() {\n    const counts = {};\n    data.attractions.forEach(a => {\n      counts[a.category] = (counts[a.category] || 0) + 1;\n    });\n\n    const list = document.getElementById('catManageList');\n    if (data.categories.length === 0) {\n      list.innerHTML = '<div style=\"padding:20px;text-align:center;color:var(--ink-mute);font-size:13px;\">No categories yet<\/div>';\n      return;\n    }\n\n    list.innerHTML = data.categories.map(c => {\n      const n = counts[c] || 0;\n      return `<div class=\"cat-row\">\n        <span>${escapeHtml(c)} <span class=\"cat-count\">\u2014 ${n} attraction${n === 1 ? '' : 's'}<\/span><\/span>\n        <button onclick=\"removeCategory('${escapeAttr(c)}')\">Remove<\/button>\n      <\/div>`;\n    }).join('');\n  }\n\n  function removeCategory(cat) {\n    const counts = data.attractions.filter(a => a.category === cat).length;\n    if (counts > 0) {\n      if (!confirm(`\"${cat}\" is used by ${counts} attraction${counts === 1 ? '' : 's'}. Remove anyway? (attractions will keep the category label)`)) return;\n    } else {\n      if (!confirm(`Remove category \"${cat}\"?`)) return;\n    }\n    data.categories = data.categories.filter(c => c !== cat);\n    save();\n    renderCatManageList();\n    populateCategorySelect();\n    renderFilters();\n    toast('Category removed');\n  }\n\n  \/\/ Enter key in new category input\n  document.getElementById('newCatInput').addEventListener('keydown', e => {\n    if (e.key === 'Enter') { e.preventDefault(); addCategoryFromManager(); }\n  });\n\n  \/\/ ========== RENDER ATTRACTIONS ==========\n  function renderAttractions() {\n    const filtered = data.attractions.filter(a =>\n      (!filters.category || a.category === filters.category) &&\n      (!filters.location || a.location === filters.location)\n    );\n\n    const grid = document.getElementById('attractionsGrid');\n    document.getElementById('countLabel').textContent = `\u2014 ${filtered.length} of ${data.attractions.length}`;\n\n    if (filtered.length === 0) {\n      grid.innerHTML = `<div class=\"empty\">\n        <h3>Nothing here yet<\/h3>\n        <p>${data.attractions.length === 0 ? 'Add your first attraction using editor mode.' : 'Try clearing the filters.'}<\/p>\n      <\/div>`;\n      return;\n    }\n\n    grid.innerHTML = filtered.map(a => {\n      const imgHtml = a.images && a.images[0]\n        ? `<img decoding=\"async\" src=\"${a.images[0]}\" alt=\"${escapeAttr(a.name)}\" \/>`\n        : `<div class=\"card-img-placeholder\">${escapeHtml(a.name[0] || '\u2726')}<\/div>`;\n\n      return `<div class=\"card\" onclick=\"showDetail(${a.id})\">\n        <div class=\"card-img\">\n          ${imgHtml}\n          <div class=\"card-tag\">${escapeHtml(a.category)}<\/div>\n        <\/div>\n        <div class=\"card-body\">\n          <div class=\"card-location\">${escapeHtml(a.location)}<\/div>\n          <h3>${escapeHtml(a.name)}<\/h3>\n          <p>${escapeHtml(truncate(a.desc, 140))}<\/p>\n          <div class=\"card-foot\">\n            <span>View details \u2192<\/span>\n            <button class=\"delete-btn\" onclick=\"event.stopPropagation(); deleteAttraction(${a.id})\">Remove<\/button>\n          <\/div>\n        <\/div>\n      <\/div>`;\n    }).join('');\n  }\n\n  function showDetail(id) {\n    const a = data.attractions.find(x => x.id === id);\n    if (!a) return;\n    document.getElementById('detailTitle').textContent = a.name;\n    document.getElementById('detailDesc').textContent = a.desc;\n    document.getElementById('detailMeta').innerHTML =\n      `<span>${escapeHtml(a.category)}<\/span><span>${escapeHtml(a.location)}<\/span>`;\n\n    const hero = document.getElementById('detailHero');\n    const thumbs = document.getElementById('detailThumbs');\n\n    if (a.images && a.images.length) {\n      hero.innerHTML = `<img decoding=\"async\" src=\"${a.images[0]}\" alt=\"${escapeAttr(a.name)}\" id=\"heroImg\" \/>`;\n      thumbs.style.display = a.images.length > 1 ? 'flex' : 'none';\n      thumbs.innerHTML = a.images.map((src, i) =>\n        `<img decoding=\"async\" src=\"${src}\" class=\"${i === 0 ? 'active' : ''}\" onclick=\"swapHero('${src}', this)\" \/>`\n      ).join('');\n    } else {\n      hero.innerHTML = `<div class=\"card-img-placeholder\">${escapeHtml(a.name[0] || '\u2726')}<\/div>`;\n      thumbs.style.display = 'none';\n    }\n    openModal('detailModal');\n  }\n\n  function swapHero(src, el) {\n    document.getElementById('heroImg').src = src;\n    document.querySelectorAll('#detailThumbs img').forEach(i => i.classList.remove('active'));\n    el.classList.add('active');\n  }\n\n  \/\/ ========== RENDER UPDATES ==========\n  function renderUpdates() {\n    const grid = document.getElementById('updatesGrid');\n    document.getElementById('updatesCount').textContent = `\u2014 ${data.updates.length}`;\n\n    if (data.updates.length === 0) {\n      grid.innerHTML = `<div class=\"empty\" style=\"background:var(--white);\">\n        <h3>No updates yet<\/h3>\n        <p>Announcements will appear here.<\/p>\n      <\/div>`;\n      return;\n    }\n\n    grid.innerHTML = data.updates.map(u => {\n      let mediaHtml = '';\n      if (u.media && u.mediaType === 'image') {\n        mediaHtml = `<div class=\"update-media\"><img decoding=\"async\" src=\"${u.media}\" alt=\"\" \/><\/div>`;\n      } else if (u.media && u.mediaType === 'video') {\n        mediaHtml = `<div class=\"update-media\"><video src=\"${u.media}\" controls><\/video><\/div>`;\n      }\n      const linkHtml = u.link ? `<a href=\"${escapeAttr(u.link)}\" target=\"_blank\" class=\"update-link\">${escapeHtml(u.link)}<\/a>` : '';\n\n      return `<div class=\"update-card\">\n        <button class=\"update-delete\" onclick=\"deleteUpdate(${u.id})\">Remove<\/button>\n        <div class=\"update-date\">${escapeHtml(u.date)}<\/div>\n        <h3>${escapeHtml(u.title)}<\/h3>\n        <p>${escapeHtml(u.body)}<\/p>\n        ${mediaHtml}\n        ${linkHtml}\n      <\/div>`;\n    }).join('');\n  }\n\n  \/\/ ========== RENDER CLIENTELE ==========\n  function renderClients() {\n    const grid = document.getElementById('clientsGrid');\n    document.getElementById('clientsCount').textContent = `\u2014 ${data.clients.length}`;\n\n    if (data.clients.length === 0) {\n      grid.innerHTML = `<div class=\"empty\" style=\"grid-column: 1 \/ -1;\">\n        <h3>No clients yet<\/h3>\n        <p>Client logos will appear here.<\/p>\n      <\/div>`;\n      return;\n    }\n\n    grid.innerHTML = data.clients.map(c =>\n      `<div class=\"client-logo\">\n        <button class=\"client-delete\" onclick=\"deleteClient(${c.id})\">\u00d7<\/button>\n        <img decoding=\"async\" src=\"${c.logo}\" alt=\"Client logo\" \/>\n      <\/div>`\n    ).join('');\n  }\n\n  \/\/ ========== EDITOR ==========\n  function openPasscode() {\n    openModal('passcodeModal');\n    setTimeout(() => document.getElementById('passcodeInput').focus(), 100);\n  }\n\n  function verifyPasscode() {\n    const val = document.getElementById('passcodeInput').value;\n    if (val === 'zzzxxx') {\n      document.body.classList.add('editor-mode');\n      closeModal('passcodeModal');\n      document.getElementById('passcodeInput').value = '';\n      toast('Editor mode unlocked');\n    } else {\n      toast('Wrong passcode');\n      document.getElementById('passcodeInput').value = '';\n    }\n  }\n\n  function exitEditor() {\n    document.body.classList.remove('editor-mode');\n    toast('Exited editor mode');\n  }\n\n  document.getElementById('passcodeInput').addEventListener('keydown', e => {\n    if (e.key === 'Enter') verifyPasscode();\n  });\n\n  \/\/ ========== ATTRACTION FORM ==========\n  let pendingImages = [];\n\n  function openAttractionModal() {\n    document.getElementById('attrName').value = '';\n    document.getElementById('attrDesc').value = '';\n    document.getElementById('attrImages').value = '';\n    pendingImages = [];\n    document.getElementById('attrImgPreview').innerHTML = '';\n    populateCategorySelect();\n    openModal('attractionModal');\n  }\n\n  document.getElementById('attrImages').addEventListener('change', async e => {\n    const files = Array.from(e.target.files);\n    for (const f of files) {\n      const b64 = await fileToBase64(f);\n      pendingImages.push(b64);\n    }\n    renderAttrImgPreview();\n  });\n\n  function renderAttrImgPreview() {\n    document.getElementById('attrImgPreview').innerHTML = pendingImages.map((src, i) =>\n      `<div><img decoding=\"async\" src=\"${src}\" \/><button onclick=\"removePendingImg(${i})\">\u00d7<\/button><\/div>`\n    ).join('');\n  }\n\n  function removePendingImg(i) {\n    pendingImages.splice(i, 1);\n    renderAttrImgPreview();\n  }\n\n  function saveAttraction() {\n    const name = document.getElementById('attrName').value.trim();\n    const desc = document.getElementById('attrDesc').value.trim();\n    if (!name || !desc) { toast('Name and description required'); return; }\n\n    const cat = document.getElementById('attrCategory').value;\n    if (!cat) { toast('Select a category'); return; }\n\n    data.attractions.unshift({\n      id: Date.now(),\n      name,\n      desc,\n      category: cat,\n      location: document.getElementById('attrLocation').value,\n      images: pendingImages.slice()\n    });\n    save();\n    renderFilters();\n    renderAttractions();\n    closeModal('attractionModal');\n    toast('Attraction added');\n  }\n\n  function deleteAttraction(id) {\n    if (!confirm('Remove this attraction?')) return;\n    data.attractions = data.attractions.filter(a => a.id !== id);\n    save();\n    renderFilters();\n    renderAttractions();\n    toast('Removed');\n  }\n\n  \/\/ ========== UPDATE FORM ==========\n  let pendingMedia = null;\n  let pendingMediaType = null;\n\n  function openUpdateModal() {\n    document.getElementById('updTitle').value = '';\n    document.getElementById('updBody').value = '';\n    document.getElementById('updMedia').value = '';\n    document.getElementById('updLink').value = '';\n    pendingMedia = null;\n    pendingMediaType = null;\n    document.getElementById('updMediaPreview').innerHTML = '';\n    openModal('updateModal');\n  }\n\n  document.getElementById('updMedia').addEventListener('change', async e => {\n    const f = e.target.files[0];\n    if (!f) return;\n    pendingMedia = await fileToBase64(f);\n    pendingMediaType = f.type.startsWith('video') ? 'video' : 'image';\n    const preview = document.getElementById('updMediaPreview');\n    preview.innerHTML = pendingMediaType === 'video'\n      ? `<div style=\"width:100px;height:64px;\"><video src=\"${pendingMedia}\" style=\"width:100%;height:100%;object-fit:cover;\"><\/video><button onclick=\"pendingMedia=null;pendingMediaType=null;document.getElementById('updMediaPreview').innerHTML='';document.getElementById('updMedia').value='';\">\u00d7<\/button><\/div>`\n      : `<div><img decoding=\"async\" src=\"${pendingMedia}\" \/><button onclick=\"pendingMedia=null;pendingMediaType=null;document.getElementById('updMediaPreview').innerHTML='';document.getElementById('updMedia').value='';\">\u00d7<\/button><\/div>`;\n  });\n\n  function saveUpdate() {\n    const title = document.getElementById('updTitle').value.trim();\n    const body = document.getElementById('updBody').value.trim();\n    if (!title || !body) { toast('Title and body required'); return; }\n\n    const now = new Date();\n    const months = ['January','February','March','April','May','June','July','August','September','October','November','December'];\n    data.updates.unshift({\n      id: Date.now(),\n      title,\n      body,\n      media: pendingMedia,\n      mediaType: pendingMediaType,\n      link: document.getElementById('updLink').value.trim(),\n      date: `${months[now.getMonth()]} ${now.getFullYear()}`\n    });\n    save();\n    renderUpdates();\n    closeModal('updateModal');\n    toast('Update posted');\n  }\n\n  function deleteUpdate(id) {\n    if (!confirm('Remove this update?')) return;\n    data.updates = data.updates.filter(u => u.id !== id);\n    save();\n    renderUpdates();\n    toast('Removed');\n  }\n\n  \/\/ ========== CLIENT FORM ==========\n  let pendingLogo = null;\n\n  function openClientModal() {\n    document.getElementById('clientLogo').value = '';\n    pendingLogo = null;\n    document.getElementById('clientLogoPreview').innerHTML = '';\n    openModal('clientModal');\n  }\n\n  document.getElementById('clientLogo').addEventListener('change', async e => {\n    const f = e.target.files[0];\n    if (!f) return;\n    pendingLogo = await fileToBase64(f);\n    document.getElementById('clientLogoPreview').innerHTML =\n      `<div><img decoding=\"async\" src=\"${pendingLogo}\" \/><button onclick=\"pendingLogo=null;document.getElementById('clientLogoPreview').innerHTML='';document.getElementById('clientLogo').value='';\">\u00d7<\/button><\/div>`;\n  });\n\n  function saveClient() {\n    if (!pendingLogo) { toast('Upload a logo first'); return; }\n    data.clients.unshift({ id: Date.now(), logo: pendingLogo });\n    save();\n    renderClients();\n    closeModal('clientModal');\n    toast('Logo added');\n  }\n\n  function deleteClient(id) {\n    if (!confirm('Remove this logo?')) return;\n    data.clients = data.clients.filter(c => c.id !== id);\n    save();\n    renderClients();\n    toast('Removed');\n  }\n\n  \/\/ ========== UTILS ==========\n  function fileToBase64(file) {\n    return new Promise((resolve, reject) => {\n      if (file.type.startsWith('image')) {\n        const img = new Image();\n        const reader = new FileReader();\n        reader.onload = e => {\n          img.onload = () => {\n            const canvas = document.createElement('canvas');\n            const MAX = 1200;\n            let w = img.width, h = img.height;\n            if (w > MAX || h > MAX) {\n              if (w > h) { h = h * (MAX \/ w); w = MAX; }\n              else { w = w * (MAX \/ h); h = MAX; }\n            }\n            canvas.width = w; canvas.height = h;\n            canvas.getContext('2d').drawImage(img, 0, 0, w, h);\n            resolve(canvas.toDataURL('image\/jpeg', 0.82));\n          };\n          img.onerror = reject;\n          img.src = e.target.result;\n        };\n        reader.onerror = reject;\n        reader.readAsDataURL(file);\n      } else {\n        const reader = new FileReader();\n        reader.onload = e => resolve(e.target.result);\n        reader.onerror = reject;\n        reader.readAsDataURL(file);\n      }\n    });\n  }\n\n  function openModal(id) { document.getElementById(id).classList.add('active'); }\n  function closeModal(id) { document.getElementById(id).classList.remove('active'); }\n\n  function toast(msg) {\n    const t = document.getElementById('toast');\n    t.textContent = msg;\n    t.classList.add('show');\n    setTimeout(() => t.classList.remove('show'), 2200);\n  }\n\n  function escapeHtml(str) {\n    if (!str) return '';\n    return String(str).replace(\/[&<>\"']\/g, c => ({'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'})[c]);\n  }\n  function escapeAttr(str) { return escapeHtml(str).replace(\/`\/g, '&#96;'); }\n  function truncate(s, n) { return s.length > n ? s.slice(0, n) + '\u2026' : s; }\n\n  document.querySelectorAll('.modal-overlay').forEach(m => {\n    m.addEventListener('click', e => { if (e.target === m) m.classList.remove('active'); });\n  });\n\n  \/\/ ========== INIT ==========\n  load();\n  renderFilters();\n  renderAttractions();\n  renderUpdates();\n  renderClients();\n<\/script>\n\n<\/body>\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Kiddypedia \u2014 Field Trip Attractions Catalogue K Kiddypedia Home Attractions Updates Make Enquiry Field trip catalogue \u00b7 Malaysia edition A living atlas of places worthlearning from, curated for curious classrooms. Browse museums, workshops, farms, and studios across every Malaysian state. Built for principals and teachers planning trips that stick. VerifiedEducatorssince &#8217;24 The Catalogue \u2014 loading [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-9","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/fieldtrip.kiddypedia-inc.com\/index.php?rest_route=\/wp\/v2\/pages\/9","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/fieldtrip.kiddypedia-inc.com\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/fieldtrip.kiddypedia-inc.com\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/fieldtrip.kiddypedia-inc.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/fieldtrip.kiddypedia-inc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=9"}],"version-history":[{"count":4,"href":"https:\/\/fieldtrip.kiddypedia-inc.com\/index.php?rest_route=\/wp\/v2\/pages\/9\/revisions"}],"predecessor-version":[{"id":13,"href":"https:\/\/fieldtrip.kiddypedia-inc.com\/index.php?rest_route=\/wp\/v2\/pages\/9\/revisions\/13"}],"wp:attachment":[{"href":"https:\/\/fieldtrip.kiddypedia-inc.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=9"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}