<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Learning Archives - Tantrumming Trailblazers</title>
	<atom:link href="https://tantrummingtrailblazers.com/tag/learning/feed/" rel="self" type="application/rss+xml" />
	<link>https://tantrummingtrailblazers.com/tag/learning/</link>
	<description>Where Travel and Tantrums Collide in Epic Journeys!</description>
	<lastBuildDate>Sun, 03 May 2026 09:33:14 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://tantrummingtrailblazers.com/wp-content/uploads/2024/06/cropped-DALL·E-2024-06-22-12.00.14-A-minimalist-logo-featuring-three-lines-and-three-colors.-The-design-includes-one-diagonal-line-from-the-bottom-left-to-the-top-right-in-bright-orange-32x32.webp</url>
	<title>Learning Archives - Tantrumming Trailblazers</title>
	<link>https://tantrummingtrailblazers.com/tag/learning/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">229502464</site>	<item>
		<title>Digital Emoji Learning Tool SEN Visual Communication Cards </title>
		<link>https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/</link>
					<comments>https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Thu, 13 Mar 2025 16:19:03 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Customizable]]></category>
		<category><![CDATA[Educational]]></category>
		<category><![CDATA[Emoji-Based]]></category>
		<category><![CDATA[Interactive]]></category>
		<category><![CDATA[Learning]]></category>
		<category><![CDATA[Tool]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=4083</guid>

					<description><![CDATA[<p>Hello, after realising we don’t always have our cards with us I decided to create a SEN Digital Emoji PECs system using emojis that&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/">Digital Emoji Learning Tool SEN Visual Communication Cards </a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Fai%2Fsen-visual-communication-cards%2F&amp;linkname=Digital%20Emoji%20Learning%20Tool%20SEN%20Visual%20Communication%20Cards%C2%A0" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Fai%2Fsen-visual-communication-cards%2F&amp;linkname=Digital%20Emoji%20Learning%20Tool%20SEN%20Visual%20Communication%20Cards%C2%A0" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Fai%2Fsen-visual-communication-cards%2F&amp;linkname=Digital%20Emoji%20Learning%20Tool%20SEN%20Visual%20Communication%20Cards%C2%A0" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Ftantrummingtrailblazers.com%2Fai%2Fsen-visual-communication-cards%2F&#038;title=Digital%20Emoji%20Learning%20Tool%20SEN%20Visual%20Communication%20Cards%C2%A0" data-a2a-url="https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/" data-a2a-title="Digital Emoji Learning Tool SEN Visual Communication Cards "></a></p>
<p class="wp-block-paragraph">Hello, after realising we don’t always have our cards with us I decided to create a SEN Digital Emoji PECs system using emojis that brings interactive, emoji-based learning right to you. Designed with my daughter Dotty in mind, it lets you effortlessly create and manage custom categories and items with editable emojis and names. Whether you&#8217;re adding everyday items or creating a playful learning journey, everything is at your fingertips.</p>



<p class="wp-block-paragraph">P.S &#8211; if you sign up and login the edits you make will save and it syncs seamlessly across devices all for free, as Maui said, you’re welcome! Emoji Learning Tool<br><br>    <div id="dotty-app-root"></div>

    <script>
      window.DottyToolConfig = {
        ajaxUrl: "https:\/\/tantrummingtrailblazers.com\/wp-admin\/admin-ajax.php",
        nonce: "8b70245328",
        serviceWorkerUrl: "https:\/\/tantrummingtrailblazers.com\/wp-content\/plugins\/SEN TOOL\/service-worker.js",
        isLoggedIn: false,
        canUpload: false      };
    </script>

    <style>
      #dotty-app-root,
      #dotty-app-root * {
        box-sizing: border-box;
      }

      #dotty-app-root {
        --dt-bg: #f6f8fc;
        --dt-surface: #ffffff;
        --dt-surface-2: #eef4ff;
        --dt-border: #d9e2f1;
        --dt-text: #172033;
        --dt-muted: #5f6f8c;
        --dt-primary: #2f80ed;
        --dt-primary-2: #1f66c7;
        --dt-success: #16a34a;
        --dt-danger: #dc2626;
        --dt-warning: #f59e0b;
        --dt-purple: #8b5cf6;
        --dt-shadow: 0 12px 30px rgba(31, 60, 116, 0.12);
        --dt-gap: 14px;
        --dt-card-size: 190px;
        --dt-font: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
        font-family: var(--dt-font);
        color: var(--dt-text);
        margin: 18px 0;
      }

      #dotty-app-root.dt-high-contrast {
        --dt-bg: #000000;
        --dt-surface: #111111;
        --dt-surface-2: #1a1a1a;
        --dt-border: #ffffff;
        --dt-text: #ffffff;
        --dt-muted: #d4d4d4;
        --dt-primary: #00c2ff;
        --dt-primary-2: #00a3d6;
        --dt-success: #22c55e;
        --dt-danger: #ff4d4d;
        --dt-warning: #ffd166;
        --dt-shadow: none;
      }

      #dotty-app-root.dt-large-text {
        --dt-card-size: 205px;
      }

      .dt-shell {
        background: linear-gradient(180deg, var(--dt-bg) 0%, #ffffff 100%);
        border: 1px solid var(--dt-border);
        border-radius: 28px;
        overflow: hidden;
        box-shadow: var(--dt-shadow);
      }

      .dt-header {
        background: linear-gradient(135deg, var(--dt-primary), #6aa8f5);
        color: #fff;
        padding: 18px 18px 16px;
      }

      .dt-header-top {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        flex-wrap: wrap;
      }

      .dt-title-wrap h1 {
        margin: 0;
        font-size: 1.8rem;
        line-height: 1.1;
        font-weight: 800;
        letter-spacing: -0.02em;
      }

      .dt-title-wrap p {
        margin: 6px 0 0;
        opacity: 0.95;
        font-size: 0.98rem;
      }

      .dt-toolbar {
        display: flex;
        gap: 10px;
        flex-wrap: wrap;
      }

      .dt-btn {
        appearance: none;
        border: none;
        border-radius: 999px;
        padding: 11px 16px;
        cursor: pointer;
        font-weight: 700;
        font-size: 0.95rem;
        transition: transform .15s ease, filter .15s ease, background .15s ease;
        min-height: 44px;
      }

      .dt-btn:hover {
        transform: translateY(-1px);
        filter: brightness(.98);
      }

      .dt-btn:focus-visible,
      .dt-card:focus-visible,
      .dt-action-btn:focus-visible,
      .dt-input:focus-visible,
      .dt-select:focus-visible,
      .dt-range:focus-visible,
      .dt-chip:focus-visible,
      .dt-template-chip:focus-visible,
      .dt-mini-btn:focus-visible,
      .dt-routine-card-main:focus-visible,
      .dt-fav-card:focus-visible,
      .dt-sentence-token-remove:focus-visible {
        outline: 3px solid rgba(47, 128, 237, 0.35);
        outline-offset: 2px;
      }

      .dt-btn-primary { background: #fff; color: var(--dt-primary-2); }
      .dt-btn-warning { background: #fff3cd; color: #6a4c00; }
      .dt-btn-success { background: #dcfce7; color: #166534; }
      .dt-btn-danger  { background: #fee2e2; color: #991b1b; }
      .dt-btn-purple  { background: #f3e8ff; color: #6b21a8; }
      .dt-btn-ghost   { background: rgba(255,255,255,.15); color: #fff; border: 1px solid rgba(255,255,255,.25); }

      .dt-main {
        padding: 14px;
        background: var(--dt-bg);
      }

      .dt-topbar {
        display: grid;
        grid-template-columns: 1fr auto;
        gap: 14px;
        align-items: center;
        margin-bottom: 14px;
      }

      .dt-breadcrumbs {
        display: flex;
        gap: 8px;
        align-items: center;
        flex-wrap: wrap;
        color: var(--dt-muted);
        font-weight: 700;
      }

      .dt-chip {
        display: inline-flex;
        align-items: center;
        gap: 6px;
        background: var(--dt-surface);
        border: 1px solid var(--dt-border);
        border-radius: 999px;
        padding: 8px 12px;
        font-size: 0.95rem;
        cursor: pointer;
      }

      .dt-settings {
        display: flex;
        gap: 10px;
        flex-wrap: wrap;
        justify-content: flex-end;
      }

      .dt-switch {
        display: inline-flex;
        align-items: center;
        gap: 8px;
        background: var(--dt-surface);
        border: 1px solid var(--dt-border);
        border-radius: 999px;
        padding: 8px 12px;
        font-weight: 700;
        color: var(--dt-text);
      }

      .dt-switch input {
        width: 18px;
        height: 18px;
      }

      .dt-panel {
        background: var(--dt-surface);
        border: 1px solid var(--dt-border);
        border-radius: 20px;
        padding: 12px;
        margin-bottom: 12px;
      }

      .dt-panel-grid {
        display: grid;
        grid-template-columns: 1.2fr .8fr;
        gap: 14px;
      }

      .dt-search-wrap {
        display: flex;
        gap: 10px;
        align-items: center;
      }

      .dt-input,
      .dt-select,
      .dt-textarea {
        width: 100%;
        min-height: 46px;
        padding: 12px 14px;
        border: 1px solid var(--dt-border);
        border-radius: 14px;
        background: #fff;
        color: var(--dt-text);
        font-size: 1rem;
      }

      .dt-textarea {
        min-height: 96px;
        resize: vertical;
      }

      .dt-range {
        width: 100%;
      }

      .dt-grid {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(var(--dt-card-size), 1fr));
        gap: var(--dt-gap);
      }

      .dt-card {
        position: relative;
        min-height: var(--dt-card-size);
        border: 1px solid var(--dt-border);
        border-radius: 22px;
        background: linear-gradient(180deg, var(--dt-surface) 0%, var(--dt-surface-2) 100%);
        box-shadow: 0 8px 18px rgba(0,0,0,.06);
        padding: 12px;
        text-align: center;
        display: grid;
        grid-template-rows: 92px auto auto;
        align-content: start;
        gap: 6px;
        cursor: pointer;
        user-select: none;
        transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
        overflow: hidden;
      }

      .dt-card:hover {
        transform: translateY(-2px) scale(1.01);
        box-shadow: 0 12px 24px rgba(0,0,0,.1);
      }

      .dt-card.is-active {
        border-color: var(--dt-primary);
        box-shadow: 0 0 0 4px rgba(47,128,237,.14);
      }

      .dt-card.is-add {
        border-style: dashed;
        background: #f8fff9;
      }

      .dt-card.style-success { background: linear-gradient(180deg, #ecfdf3 0%, #d1fae5 100%); }
      .dt-card.style-danger  { background: linear-gradient(180deg, #fef2f2 0%, #fee2e2 100%); }
      .dt-card.style-warning { background: linear-gradient(180deg, #fffbeb 0%, #fde68a 100%); }
      .dt-card.style-primary { background: linear-gradient(180deg, #eff6ff 0%, #dbeafe 100%); }
      .dt-card.style-purple  { background: linear-gradient(180deg, #f5f3ff 0%, #ede9fe 100%); }
      .dt-card.style-muted   { background: linear-gradient(180deg, #f8fafc 0%, #e2e8f0 100%); }

      .dt-card-image-wrap {
        width: 100%;
        height: 92px;
        border-radius: 16px;
        overflow: hidden;
        background: #fff;
        border: 1px solid rgba(0,0,0,.05);
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .dt-card.has-image-only {
        grid-template-rows: 120px auto;
      }

      .dt-card.has-image-only .dt-card-image-wrap {
        height: 120px;
      }

      .dt-card-image {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
      }

      .dt-card-media {
        width: 100%;
        height: 72px !important;
        min-height: 72px !important;
        max-height: 72px !important;
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        position: relative !important;
        overflow: hidden !important;
        padding: 4px !important;
      }

      .dt-card-emoji {
        width: 100% !important;
        height: 100% !important;
        display: flex !important;
        align-items: center !important;
        justify-content: center !important;
        text-align: center !important;
        line-height: 1 !important;
        overflow: hidden !important;
        font-size: 2rem !important;
      }

      .dt-card-emoji img,
      .dt-card-emoji img.emoji,
      .dt-card-emoji svg,
      .dt-card-media img.emoji {
        width: 56px !important;
        height: 56px !important;
        max-width: 56px !important;
        max-height: 56px !important;
        object-fit: contain !important;
        display: block !important;
        margin: 0 auto !important;
      }

      .dt-card-emoji-badge {
        position: absolute;
        right: 10px;
        bottom: -8px;
        width: 38px;
        height: 38px;
        border-radius: 999px;
        background: rgba(255,255,255,.96);
        border: 1px solid var(--dt-border);
        display: flex;
        align-items: center;
        justify-content: center;
        font-size: 1rem;
        box-shadow: 0 6px 14px rgba(0,0,0,.1);
      }

      .dt-card-title {
        font-size: 1.08rem;
        font-weight: 800;
        line-height: 1.15;
        word-break: break-word;
        margin-top: 2px;
      }

      .dt-card-sub {
        font-size: .85rem;
        color: var(--dt-muted);
        font-weight: 700;
      }

      .dt-card-actions {
        position: absolute;
        inset: 8px 8px auto 8px;
        display: flex;
        justify-content: space-between;
        gap: 8px;
        pointer-events: none;
        z-index: 4;
      }

      .dt-card-actions-right,
      .dt-card-actions-left {
        display: flex;
        gap: 6px;
        pointer-events: auto;
      }

      .dt-action-btn {
        appearance: none;
        border: none;
        border-radius: 10px;
        padding: 7px 9px;
        cursor: pointer;
        font-weight: 800;
        min-height: 34px;
        min-width: 34px;
        display: inline-flex;
        align-items: center;
        justify-content: center;
      }

      .dt-action-edit   { background: #f3e8ff; color: #6b21a8; }
      .dt-action-delete { background: #fee2e2; color: #991b1b; }
      .dt-action-move   { background: #dbeafe; color: #1d4ed8; }
      .dt-action-star   { background: #fef3c7; color: #92400e; }

      .dt-title-row {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        margin-bottom: 10px;
        flex-wrap: wrap;
      }

      .dt-title-row h2 {
        margin: 0;
        font-size: 1.28rem;
        font-weight: 900;
        letter-spacing: -0.02em;
      }

      .dt-favourites-grid {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
        gap: 10px;
      }

      .dt-fav-card {
        position: relative;
        border: 1px solid var(--dt-border);
        background: linear-gradient(180deg, #fff 0%, #f8fbff 100%);
        border-radius: 16px;
        min-height: 98px;
        padding: 8px;
        display: grid;
        grid-template-rows: 42px auto;
        align-items: center;
        justify-items: center;
        text-align: center;
        cursor: pointer;
        font-weight: 800;
        transition: transform .15s ease, box-shadow .15s ease;
      }

      .dt-fav-card:hover {
        transform: translateY(-1px);
        box-shadow: 0 6px 14px rgba(0,0,0,.08);
      }

      .dt-fav-card .emoji {
        font-size: 1.7rem;
        line-height: 1;
      }

      .dt-fav-card .img-wrap {
        width: 42px;
        height: 42px;
        border-radius: 10px;
        overflow: hidden;
        border: 1px solid var(--dt-border);
        background: #fff;
      }

      .dt-fav-card .img-wrap img {
        width: 100%;
        height: 100%;
        object-fit: cover;
      }

      .dt-fav-card .label {
        font-size: .82rem;
        line-height: 1.05;
        word-break: break-word;
      }

      .dt-sentence {
        position: sticky;
        bottom: 0;
        z-index: 20;
        background: rgba(246, 248, 252, 0.96);
        backdrop-filter: blur(10px);
        border-top: 1px solid var(--dt-border);
        padding: 10px 14px 14px;
      }

      .dt-sentence-inner {
        background: var(--dt-surface);
        border: 1px solid var(--dt-border);
        border-radius: 20px;
        padding: 10px;
        box-shadow: var(--dt-shadow);
      }

      .dt-sentence-top {
        display: flex;
        align-items: center;
        justify-content: space-between;
        gap: 12px;
        margin-bottom: 10px;
        flex-wrap: wrap;
      }

      .dt-sentence-top h3 {
        margin: 0;
        font-size: 1.05rem;
      }

      .dt-sentence-actions {
        display: flex;
        gap: 8px;
        flex-wrap: wrap;
      }

      .dt-sentence-strip {
        display: flex;
        gap: 10px;
        overflow-x: auto;
        padding-bottom: 2px;
      }

      .dt-sentence-token {
        position: relative;
        min-width: 92px;
        background: linear-gradient(180deg, #fff 0%, #f6faff 100%);
        border: 1px solid var(--dt-border);
        border-radius: 18px;
        padding: 10px 8px;
        text-align: center;
        font-weight: 800;
        flex: 0 0 auto;
      }

      .dt-sentence-token .emoji {
        display: block;
        font-size: 2rem;
        line-height: 1;
        margin-bottom: 6px;
      }

      .dt-sentence-token .img-wrap {
        width: 56px;
        height: 56px;
        margin: 0 auto 6px;
        overflow: hidden;
        border-radius: 12px;
        background: #fff;
        border: 1px solid var(--dt-border);
      }

      .dt-sentence-token .img-wrap img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
      }

      .dt-sentence-token-remove {
        position: absolute;
        top: 6px;
        right: 6px;
        width: 24px;
        height: 24px;
        border-radius: 999px;
        border: none;
        background: #fee2e2;
        color: #991b1b;
        font-weight: 900;
        cursor: pointer;
        display: inline-flex;
        align-items: center;
        justify-content: center;
      }

      .dt-routine-list {
        display: flex;
        gap: 12px;
        flex-wrap: wrap;
      }

      .dt-routine-card {
        background: linear-gradient(180deg, #fff 0%, #f4f8ff 100%);
        border: 1px solid var(--dt-border);
        border-radius: 18px;
        padding: 12px;
        min-width: 150px;
        max-width: 220px;
      }

      .dt-routine-card-main {
        width: 100%;
        border: none;
        background: transparent;
        cursor: pointer;
        text-align: center;
        padding: 0;
      }

      .dt-routine-card-main .e {
        display: block;
        font-size: 2rem;
        margin-bottom: 4px;
      }

      .dt-routine-card-main .t {
        display: block;
        font-size: .92rem;
        font-weight: 800;
        line-height: 1.15;
      }

      .dt-routine-card-main .s {
        display: block;
        margin-top: 6px;
        font-size: .78rem;
        color: var(--dt-muted);
        font-weight: 700;
      }

      .dt-routine-actions {
        display: flex;
        gap: 6px;
        flex-wrap: wrap;
        justify-content: center;
        margin-top: 10px;
      }

      .dt-mini-btn {
        appearance: none;
        border: none;
        border-radius: 999px;
        min-height: 32px;
        padding: 6px 10px;
        font-size: .82rem;
        font-weight: 800;
        cursor: pointer;
      }

      .dt-mini-btn.edit   { background: #f3e8ff; color: #6b21a8; }
      .dt-mini-btn.delete { background: #fee2e2; color: #991b1b; }
      .dt-mini-btn.move   { background: #dbeafe; color: #1d4ed8; }
      .dt-mini-btn.copy   { background: #ecfdf3; color: #166534; }

      .dt-template-list {
        display: flex;
        gap: 10px;
        flex-wrap: wrap;
      }

      .dt-template-chip {
        border: 1px solid var(--dt-border);
        background: linear-gradient(180deg, #fff 0%, #f8fbff 100%);
        padding: 10px 12px;
        border-radius: 999px;
        font-weight: 800;
        cursor: pointer;
        font-size: .9rem;
        line-height: 1;
      }

      .dt-template-chip:hover {
        transform: translateY(-1px);
      }

      .dt-template-chip.is-disabled {
        opacity: .65;
      }

      .dt-empty {
        padding: 16px;
        text-align: center;
        color: var(--dt-muted);
        font-weight: 700;
        border: 2px dashed var(--dt-border);
        border-radius: 18px;
        background: #fff;
      }

      .dt-modal {
        border: none;
        border-radius: 20px;
        padding: 0;
        width: min(760px, calc(100vw - 20px));
        max-height: calc(100vh - 20px);
        overflow: hidden;
        background: transparent;
      }

      .dt-modal::backdrop {
        background: rgba(10, 20, 40, 0.45);
      }

      .dt-modal-card {
        background: var(--dt-surface);
        color: var(--dt-text);
        border-radius: 20px;
        overflow: hidden;
        border: 1px solid var(--dt-border);
      }

      .dt-modal-header {
        padding: 16px 18px;
        background: linear-gradient(135deg, var(--dt-primary), #6aa8f5);
        color: #fff;
      }

      .dt-modal-header h3 {
        margin: 0;
        font-size: 1.2rem;
      }

      .dt-modal-body {
        padding: 14px 16px 16px;
        max-height: 78vh;
        overflow: auto;
      }

      .dt-form-grid {
        display: grid;
        grid-template-columns: 1fr;
        gap: 12px;
      }

      .dt-form-group {
        margin-bottom: 12px;
      }

      .dt-form-group label {
        display: block;
        margin-bottom: 6px;
        font-weight: 800;
      }

      .dt-form-help {
        font-size: .9rem;
        color: var(--dt-muted);
      }

      .dt-emoji-search {
        margin-bottom: 14px;
      }

      .dt-emoji-grid {
        display: grid;
        grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
        gap: 8px;
        max-height: 240px;
        overflow-y: auto;
        padding-right: 4px;
      }

      .dt-emoji-option {
        border: 1px solid var(--dt-border);
        border-radius: 12px;
        background: #fff;
        padding: 8px 4px;
        text-align: center;
        cursor: pointer;
        min-height: 72px;
        transition: transform .15s ease, border-color .15s ease, background .15s ease;
      }

      .dt-emoji-option:hover {
        transform: translateY(-1px);
      }

      .dt-emoji-option.is-selected {
        border-color: var(--dt-primary);
        background: #eff6ff;
        box-shadow: 0 0 0 3px rgba(47,128,237,.14);
      }

      .dt-emoji-option .e {
        display: block;
        font-size: 1.7rem;
        line-height: 1;
        margin-bottom: 4px;
      }

      .dt-emoji-option .n {
        display: block;
        font-size: .68rem;
        color: var(--dt-muted);
        font-weight: 700;
        line-height: 1.1;
      }

      .dt-modal-actions {
        display: flex;
        justify-content: flex-end;
        gap: 10px;
        flex-wrap: wrap;
        margin-top: 14px;
      }

      .dt-image-preview-wrap {
        display: flex;
        align-items: center;
        gap: 12px;
        flex-wrap: wrap;
      }

      .dt-image-preview {
        width: 76px;
        height: 76px;
        border-radius: 14px;
        overflow: hidden;
        border: 1px solid var(--dt-border);
        background: #fff;
        display: flex;
        align-items: center;
        justify-content: center;
      }

      .dt-image-preview img {
        width: 100%;
        height: 100%;
        object-fit: cover;
        display: block;
      }

      .dt-image-preview-empty {
        font-size: .85rem;
        color: var(--dt-muted);
        font-weight: 700;
        padding: 8px;
        text-align: center;
      }

      .dt-hidden {
        display: none !important;
      }

      .dt-toast-wrap {
        position: fixed;
        right: 16px;
        bottom: 16px;
        z-index: 99999;
        display: flex;
        flex-direction: column;
        gap: 10px;
      }

      .dt-toast {
        background: #111827;
        color: #fff;
        padding: 12px 14px;
        border-radius: 14px;
        box-shadow: 0 12px 30px rgba(0,0,0,.25);
        min-width: 220px;
        max-width: 320px;
        font-weight: 700;
      }

      .dt-toast.success { background: #166534; }
      .dt-toast.error   { background: #991b1b; }
      .dt-toast.info    { background: #1f2937; }

      #dotty-app-root.dt-editing .dt-card {
        min-height: 160px;
        grid-template-rows: 70px auto auto;
        padding: 10px;
      }

      #dotty-app-root.dt-editing .dt-grid {
        grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
      }

      #dotty-app-root.dt-editing .dt-card-title {
        font-size: .98rem;
      }

      #dotty-app-root.dt-editing .dt-card-sub {
        font-size: .78rem;
      }

      #dotty-app-root.dt-editing .dt-panel {
        background: linear-gradient(180deg, #ffffff 0%, #f7faff 100%);
      }

      @media (min-width: 860px) {
        .dt-form-grid {
          grid-template-columns: 1fr 1fr;
          align-items: start;
        }
      }

      @media (max-width: 900px) {
        .dt-panel-grid,
        .dt-topbar {
          grid-template-columns: 1fr;
        }

        .dt-settings {
          justify-content: flex-start;
        }
      }

      @media (max-width: 640px) {
        #dotty-app-root {
          margin: 0;
        }

        .dt-shell {
          border-radius: 0;
          border-left: none;
          border-right: none;
        }

        .dt-header {
          padding: 16px 14px;
        }

        .dt-main {
          padding: 12px;
        }

        .dt-grid {
          grid-template-columns: repeat(2, minmax(0, 1fr));
          gap: 12px;
        }

        .dt-card {
          min-height: 154px;
          border-radius: 20px;
          grid-template-rows: 78px auto auto;
        }

        .dt-card-media,
        .dt-card-image-wrap {
          min-height: 70px !important;
          height: 70px !important;
          max-height: 70px !important;
        }

        .dt-card.has-image-only .dt-card-image-wrap {
          height: 100px;
        }

        .dt-card-title {
          font-size: 1rem;
        }

        .dt-sentence {
          padding: 10px 12px 12px;
        }

        .dt-card-emoji img,
        .dt-card-emoji img.emoji,
        .dt-card-emoji svg,
        .dt-card-media img.emoji {
          width: 46px !important;
          height: 46px !important;
          max-width: 46px !important;
          max-height: 46px !important;
        }

        .dt-favourites-grid {
          grid-template-columns: repeat(3, minmax(0, 1fr));
        }
      }

      #dotty-app-root.dt-fullscreen {
        position: fixed;
        inset: 0;
        z-index: 99998;
        background: var(--dt-bg);
        margin: 0;
      }

      #dotty-app-root.dt-fullscreen .dt-shell {
        height: 100vh;
        border-radius: 0;
      }
    </style>

    <script>
      (() => {
        const STORAGE_KEY = 'dotty_tool_data_v421';
        const ACTIVE_CLASS = 'is-active';

        const DEFAULT_DATA = {
          version: 421,
          updatedAt: Date.now(),
          settings: {
            autoSpeak: true,
            speechRate: 0.95,
            highContrast: false,
            largeText: true
          },
          ui: {
            showRoutines: false
          },
          sentence: [],
          routines: [
            { name: 'Morning', emoji: '&#x1f305;', items: [] },
            { name: 'Bedtime', emoji: '&#x1f319;', items: [] },
            { name: 'Toilet', emoji: '&#x1f6bd;', items: [] }
          ],
          categories: [
            {
              name: 'Food',
              emoji: '&#x1f34e;',
              imageId: 0,
              imageUrl: '',
              displayMode: 'emoji_text',
              items: [
                { name: 'Dinner', emoji: '&#x1f37d;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: false, style: '' },
                { name: 'Drink', emoji: '&#x1f964;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: false, style: '' },
                { name: 'Milk', emoji: '&#x1f95b;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: false, style: '' },
                { name: 'Juice', emoji: '&#x1f9c3;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: false, style: '' }
              ]
            },
            {
              name: 'Every Day',
              emoji: '&#x1f506;',
              imageId: 0,
              imageUrl: '',
              displayMode: 'emoji_text',
              items: [
                { name: 'Yes', emoji: '&#x2705;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: true, style: 'success' },
                { name: 'No', emoji: '&#x1f6ab;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: true, style: 'danger' },
                { name: 'Wait', emoji: '&#x1faf8;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: true, style: 'warning' },
                { name: 'Calm', emoji: '&#x1f92b;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: true, style: 'primary' },
                { name: 'Home', emoji: '&#x1f3e0;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: false, style: '' },
                { name: 'Sleep', emoji: '&#x1f6cc;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: false, style: '' },
                { name: 'Swim', emoji: '&#x1f3ca;&#x200d;&#x2642;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: false, style: '' },
                { name: 'Toilet', emoji: '&#x1f6bd;', imageId: 0, imageUrl: '', displayMode: 'emoji_text', favourite: true, style: 'warning' }
              ]
            }
          ]
        };

        const DISPLAY_MODES = [
          { value: 'emoji_text', label: 'Emoji + Text' },
          { value: 'image_text', label: 'Image + Text' },
          { value: 'image_only', label: 'Image Only' },
          { value: 'image_emoji_text', label: 'Image + Emoji + Text' }
        ];

        const TEMPLATE_PACKS = [
          {
            key: 'core',
            name: 'Core Needs',
            items: [
              { name: 'Yes', emoji: '&#x2705;', favourite: true, style: 'success' },
              { name: 'No', emoji: '&#x1f6ab;', favourite: true, style: 'danger' },
              { name: 'Wait', emoji: '&#x1faf8;', favourite: true, style: 'warning' },
              { name: 'Help', emoji: '&#x1f198;', favourite: true, style: 'danger' },
              { name: 'More', emoji: '&#x2795;', favourite: true, style: 'primary' },
              { name: 'Finished', emoji: '&#x274c;', favourite: true, style: 'muted' },
              { name: 'Stop', emoji: '&#x1f6d1;', favourite: true, style: 'danger' },
              { name: 'Go', emoji: '&#x25b6;', favourite: true, style: 'success' },
              { name: 'Again', emoji: '&#x1f501;', favourite: true, style: 'primary' },
              { name: 'Please', emoji: '&#x1f64f;', favourite: true, style: 'purple' }
            ]
          },
          {
            key: 'food',
            name: 'Food & Drink',
            items: [
              { name: 'Drink', emoji: '&#x1f964;' },
              { name: 'Water', emoji: '&#x1f4a7;' },
              { name: 'Juice', emoji: '&#x1f9c3;' },
              { name: 'Milk', emoji: '&#x1f95b;' },
              { name: 'Eat', emoji: '&#x1f374;' },
              { name: 'Snack', emoji: '&#x1f36a;' },
              { name: 'Apple', emoji: '&#x1f34e;' },
              { name: 'Banana', emoji: '&#x1f34c;' },
              { name: 'Toast', emoji: '&#x1f35e;' },
              { name: 'Dinner', emoji: '&#x1f37d;' }
            ]
          },
          {
            key: 'home',
            name: 'Home',
            items: [
              { name: 'Home', emoji: '&#x1f3e0;' },
              { name: 'Bed', emoji: '&#x1f6cf;' },
              { name: 'Bath', emoji: '&#x1f6c1;' },
              { name: 'Shower', emoji: '&#x1f6bf;' },
              { name: 'Toilet', emoji: '&#x1f6bd;', favourite: true, style: 'warning' },
              { name: 'Wash Hands', emoji: '&#x1f9fc;' },
              { name: 'Light', emoji: '&#x1f4a1;' },
              { name: 'Door', emoji: '&#x1f6aa;' },
              { name: 'Sofa', emoji: '&#x1f6cb;' },
              { name: 'Television', emoji: '&#x1f4fa;' }
            ]
          },
          {
            key: 'people',
            name: 'People',
            items: [
              { name: 'Mum', emoji: '&#x1f469;' },
              { name: 'Dad', emoji: '&#x1f468;' },
              { name: 'Sister', emoji: '&#x1f467;' },
              { name: 'Teacher', emoji: '&#x1f469;&#x200d;&#x1f3eb;' },
              { name: 'Friend', emoji: '&#x1f642;' },
              { name: 'Doctor', emoji: '&#x1f469;&#x200d;&#x2695;' },
              { name: 'Family', emoji: '&#x1f468;&#x200d;&#x1f469;&#x200d;&#x1f467;' },
              { name: 'Me', emoji: '&#x1f64b;' }
            ]
          },
          {
            key: 'feelings',
            name: 'Feelings',
            items: [
              { name: 'Happy', emoji: '&#x1f642;' },
              { name: 'Sad', emoji: '&#x1f622;' },
              { name: 'Cross', emoji: '&#x1f620;' },
              { name: 'Scared', emoji: '&#x1f628;' },
              { name: 'Tired', emoji: '&#x1f634;' },
              { name: 'Poorly', emoji: '&#x1f912;' },
              { name: 'Calm', emoji: '&#x1f92b;', favourite: true, style: 'primary' },
              { name: 'Excited', emoji: '&#x1f929;' }
            ]
          },
          {
            key: 'places',
            name: 'Places',
            items: [
              { name: 'Park', emoji: '&#x1f3de;' },
              { name: 'School', emoji: '&#x1f3eb;' },
              { name: 'Shop', emoji: '&#x1f3ea;' },
              { name: 'Hospital', emoji: '&#x1f3e5;' },
              { name: 'Beach', emoji: '&#x1f3d6;' },
              { name: 'Car', emoji: '&#x1f697;' },
              { name: 'Garden', emoji: '&#x1f333;' },
              { name: 'Playground', emoji: '&#x1f6dd;' }
            ]
          },
          {
            key: 'actions',
            name: 'Actions',
            items: [
              { name: 'Go', emoji: '&#x25b6;' },
              { name: 'Come', emoji: '&#x1f44b;' },
              { name: 'Sit', emoji: '&#x1fa91;' },
              { name: 'Sleep', emoji: '&#x1f6cc;' },
              { name: 'Eat', emoji: '&#x1f374;' },
              { name: 'Drink', emoji: '&#x1f964;' },
              { name: 'Open', emoji: '&#x1f4c2;' },
              { name: 'Close', emoji: '&#x1f4d5;' },
              { name: 'Play', emoji: '&#x1f389;' },
              { name: 'Read', emoji: '&#x1f4d6;' }
            ]
          },
          {
            key: 'transport',
            name: 'Transport',
            items: [
              { name: 'Car', emoji: '&#x1f697;' },
              { name: 'Bus', emoji: '&#x1f68c;' },
              { name: 'Train', emoji: '&#x1f686;' },
              { name: 'Bike', emoji: '&#x1f6b2;' },
              { name: 'Taxi', emoji: '&#x1f695;' },
              { name: 'Boat', emoji: '&#x26f5;' },
              { name: 'Airplane', emoji: '&#x2708;' },
              { name: 'Walk', emoji: '&#x1f6b6;' }
            ]
          },
          {
            key: 'play',
            name: 'Play',
            items: [
              { name: 'Ball', emoji: '&#x26bd;' },
              { name: 'Toy', emoji: '&#x1f9f8;' },
              { name: 'Music', emoji: '&#x1f3b5;' },
              { name: 'Tablet', emoji: '&#x1f4f1;' },
              { name: 'Television', emoji: '&#x1f4fa;' },
              { name: 'Puzzle', emoji: '&#x1f9e9;' },
              { name: 'Game', emoji: '&#x1f3ae;' },
              { name: 'Slide', emoji: '&#x1f6dd;' }
            ]
          },
          {
            key: 'routine',
            name: 'Routine',
            items: [
              { name: 'Morning', emoji: '&#x1f305;' },
              { name: 'Breakfast', emoji: '&#x1f963;' },
              { name: 'School Time', emoji: '&#x1f3eb;' },
              { name: 'Lunch', emoji: '&#x1f37d;' },
              { name: 'Bath Time', emoji: '&#x1f6c1;' },
              { name: 'Bedtime', emoji: '&#x1f319;' },
              { name: 'Toilet Time', emoji: '&#x1f6bd;' },
              { name: 'Quiet Time', emoji: '&#x1f92b;' }
            ]
          },
          {
            key: 'health',
            name: 'Health',
            items: [
              { name: 'Doctor', emoji: '&#x1f469;&#x200d;&#x2695;' },
              { name: 'Medicine', emoji: '&#x1f48a;' },
              { name: 'Pain', emoji: '&#x1f623;' },
              { name: 'Head', emoji: '&#x1f915;' },
              { name: 'Tummy', emoji: '&#x1f922;' },
              { name: 'Hot', emoji: '&#x1f975;' },
              { name: 'Cold', emoji: '&#x1f976;' },
              { name: 'Rest', emoji: '&#x1f6cc;' }
            ]
          }
        ];

        const EMOJIS = [
          ['&#x1f34e;','Apple'],['&#x1f350;','Pear'],['&#x1f34c;','Banana'],['&#x1f353;','Strawberry'],['&#x1f347;','Grapes'],['&#x1f349;','Watermelon'],
          ['&#x1f34a;','Orange'],['&#x1f95d;','Kiwi'],['&#x1f34d;','Pineapple'],['&#x1f96d;','Mango'],['&#x1f352;','Cherry'],['&#x1f351;','Peach'],
          ['&#x1f955;','Carrot'],['&#x1f966;','Broccoli'],['&#x1f33d;','Corn'],['&#x1f345;','Tomato'],['&#x1f954;','Potato'],['&#x1f952;','Cucumber'],
          ['&#x1f35e;','Bread'],['&#x1f9c0;','Cheese'],['&#x1f95a;','Egg'],['&#x1f357;','Chicken'],['&#x1f35f;','Chips'],['&#x1f36a;','Biscuit'],
          ['&#x1f370;','Cake'],['&#x1f37f;','Popcorn'],['&#x1f37d;','Dinner'],['&#x1f964;','Drink'],['&#x1f95b;','Milk'],['&#x1f9c3;','Juice'],
          ['&#x1f4a7;','Water'],['&#x1f374;','Eat'],['&#x1f96a;','Sandwich'],['&#x1f371;','Sushi'],['&#x1f35d;','Noodles'],['&#x1f963;','Cereal'],
          ['&#x1f3e0;','House'],['&#x1f3e1;','Home'],['&#x1f6cf;','Bed'],['&#x1f6cb;','Sofa'],['&#x1fa91;','Chair'],['&#x1f6aa;','Door'],
          ['&#x1fa9f;','Window'],['&#x1f4a1;','Light'],['&#x1f6bf;','Shower'],['&#x1f6c1;','Bath'],['&#x1f6bd;','Toilet'],['&#x1f9fc;','Soap'],
          ['&#x1f4fa;','Television'],['&#x1f4f1;','Phone'],['&#x1f4bb;','Computer'],['&#x1f697;','Car'],['&#x1f695;','Taxi'],['&#x1f69a;','Truck'],
          ['&#x1f68c;','Bus'],['&#x1f686;','Train'],['&#x1f6b2;','Bicycle'],['&#x1f3cd;','Motorbike'],['&#x2708;','Airplane'],['&#x26f5;','Boat'],
          ['&#x1f333;','Tree'],['&#x1f338;','Flower'],['&#x1f33f;','Grass'],['&#x2600;','Sun'],['&#x2601;','Cloud'],['&#x1f327;','Rain'],
          ['&#x2744;','Snow'],['&#x2b50;','Star'],['&#x1f319;','Moon'],['&#x1f3de;','Park'],['&#x1f3d6;','Beach'],['&#x1f30a;','Sea'],
          ['&#x2705;','Yes'],['&#x1f6ab;','No'],['&#x1f44c;','OK'],['&#x1f92b;','Calm'],['&#x1faf8;','Wait'],['&#x274c;','Finished'],
          ['&#x2764;','Love'],['&#x1f642;','Happy'],['&#x1f622;','Sad'],['&#x1f634;','Sleep'],['&#x1f912;','Poorly'],['&#x1f198;','Help'],
          ['&#x1f64f;','Please'],['&#x1f44f;','Hands'],['&#x1f389;','Fun'],['&#x1f3b5;','Music'],['&#x1f4d6;','Book'],['&#x270f;','Pencil'],
          ['&#x1f9e9;','Puzzle'],['&#x1f3ae;','Game'],['&#x1f9f8;','Teddy'],['&#x26bd;','Ball'],['&#x1f3ca;&#x200d;&#x2642;','Swim'],
          ['&#x1f436;','Dog'],['&#x1f431;','Cat'],['&#x1f430;','Rabbit'],['&#x1f43b;','Bear'],['&#x1f438;','Frog'],['&#x1f422;','Turtle'],
          ['&#x1f981;','Lion'],['&#x1f42f;','Tiger'],['&#x1f435;','Monkey'],['&#x1f418;','Elephant'],['&#x1f992;','Giraffe'],['&#x1f993;','Zebra'],
          ['&#x1f3eb;','School'],['&#x1f3e5;','Hospital'],['&#x1f3ea;','Shop'],['&#x1f6d5;','Temple'],['&#x1f6e3;','Road'],['&#x1f309;','Bridge'],
          ['&#x1f511;','Key'],['&#x1f392;','Bag'],['&#x1f4e6;','Box'],['&#x1f381;','Gift'],['&#x1f4f7;','Camera'],['&#x1f57a;','Blippi'],
          ['&#x1f469;','Mum'],['&#x1f468;','Dad'],['&#x1f467;','Girl'],['&#x1f9d2;','Child'],['&#x1f469;&#x200d;&#x1f3eb;','Teacher']
        ].map(([emoji, name]) => ({ emoji, name }));

        class DottyTool {
          constructor(root) {
            this.root = root;
            this.state = this.normaliseData(this.clone(DEFAULT_DATA));
            this.currentView = 'main';
            this.currentCategoryIndex = null;
            this.editMode = false;
            this.searchTerm = '';
            this.emojiSearchTerm = '';
            this.editingContext = null;
            this.editingRoutineIndex = null;
            this.pendingDelete = null;
            this.saveTimer = null;
            this.parentCode = sessionStorage.getItem('dotty_parent_code') || null;
            this.elements = {};
            this.showRoutines = false;
          }

          init() {
            this.loadLocal();
            this.showRoutines = !!this.state.ui?.showRoutines;
            this.renderShell();
            this.bindGlobalEvents();
            this.applySettingsClasses();
            this.render();
            this.loadRemote();
            this.registerServiceWorker();
          }

          clone(obj) {
            return JSON.parse(JSON.stringify(obj));
          }

          decodePossiblyEscapedUnicode(str) {
            if (typeof str !== 'string') return '';
            if (!/\\u[0-9a-fA-F]{4}/.test(str)) return str;
            try {
              return JSON.parse('"' + str.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"');
            } catch (e) {
              return str;
            }
          }

          cleanText(str, fallback = 'Untitled') {
            let value = String(str ?? '');
            value = this.decodePossiblyEscapedUnicode(value);
            value = value.replace(/\s+/g, ' ').trim();
            return value.slice(0, 40) || fallback;
          }

          cleanEmoji(str, fallback = '&#x2753;') {
            let value = String(str ?? '');
            value = this.decodePossiblyEscapedUnicode(value).trim();

            if (/^u[dD][0-9a-fA-F]{3,}/.test(value)) {
              value = '';
            }

            return value.slice(0, 16) || fallback;
          }

          cleanImageUrl(str) {
            return String(str ?? '').trim().slice(0, 500);
          }

          cleanDisplayMode(str) {
            const allowed = ['emoji_text', 'image_text', 'image_only', 'image_emoji_text'];
            return allowed.includes(str) ? str : 'emoji_text';
          }

          clamp(n, min, max) {
            return Math.min(max, Math.max(min, n));
          }

          touchUpdatedAt() {
            this.state.updatedAt = Date.now();
            this.state.ui = this.state.ui || {};
            this.state.ui.showRoutines = !!this.showRoutines;
          }

          normaliseItem(item) {
            return {
              name: this.cleanText(item?.name || 'Item', 'Item'),
              emoji: this.cleanEmoji(item?.emoji || '&#x2753;', '&#x2753;'),
              imageId: Number(item?.imageId || 0) || 0,
              imageUrl: this.cleanImageUrl(item?.imageUrl || ''),
              displayMode: this.cleanDisplayMode(item?.displayMode || 'emoji_text'),
              favourite: !!item?.favourite,
              style: String(item?.style || '').slice(0, 20)
            };
          }

          normaliseCategory(cat) {
            return {
              name: this.cleanText(cat?.name || 'Untitled'),
              emoji: this.cleanEmoji(cat?.emoji || '&#x1f4c1;', '&#x1f4c1;'),
              imageId: Number(cat?.imageId || 0) || 0,
              imageUrl: this.cleanImageUrl(cat?.imageUrl || ''),
              displayMode: this.cleanDisplayMode(cat?.displayMode || 'emoji_text'),
              items: Array.isArray(cat?.items) ? cat.items.slice(0, 120).map(item => this.normaliseItem(item)) : []
            };
          }

          normaliseRoutine(routine) {
            return {
              name: this.cleanText(routine?.name || 'Routine'),
              emoji: this.cleanEmoji(routine?.emoji || '&#x2b50;', '&#x2b50;'),
              items: Array.isArray(routine?.items) ? routine.items.slice(0, 30).map(item => this.normaliseItem(item)) : []
            };
          }

          dedupeFavouriteItems() {
            const seen = new Set();

            this.state.categories.forEach(cat => {
              cat.items.forEach(item => {
                if (!item.favourite) return;

                const key = `${item.name.toLowerCase()}|${item.emoji}|${item.imageUrl}`;
                if (seen.has(key)) {
                  item.favourite = false;
                } else {
                  seen.add(key);
                }
              });
            });
          }

          normaliseData(data) {
            const safe = this.clone(DEFAULT_DATA);
            const input = data && typeof data === 'object' ? data : {};

            safe.version = 421;
            safe.updatedAt = Number(input.updatedAt || safe.updatedAt || Date.now()) || Date.now();
            safe.settings.autoSpeak = input.settings?.autoSpeak === false ? false : true;
            safe.settings.speechRate = this.clamp(Number(input.settings?.speechRate ?? safe.settings.speechRate), 0.6, 1.3);
            safe.settings.highContrast = !!(input.settings && input.settings.highContrast);
            safe.settings.largeText = input.settings?.largeText === false ? false : true;
            safe.ui.showRoutines = !!input.ui?.showRoutines;

            safe.sentence = Array.isArray(input.sentence)
              ? input.sentence.slice(0, 20).map(x => this.normaliseItem(x))
              : [];

            safe.routines = Array.isArray(input.routines)
              ? input.routines.slice(0, 20).map(r => this.normaliseRoutine(r))
              : safe.routines;

            const cats = Array.isArray(input.categories) ? input.categories : safe.categories;
            safe.categories = cats.slice(0, 30).map(cat => this.normaliseCategory(cat));

            this.state = safe;
            this.dedupeFavouriteItems();

            return this.state;
          }

          loadLocal() {
            try {
              const raw = localStorage.getItem(STORAGE_KEY);
              if (!raw) return;
              this.state = this.normaliseData(JSON.parse(raw));
            } catch (e) {
              console.warn('Dotty local load failed', e);
            }
          }

          async loadRemote() {
            if (!window.DottyToolConfig?.isLoggedIn) return;

            try {
              const body = new URLSearchParams({
                action: 'load_dotty_data',
                nonce: window.DottyToolConfig.nonce
              });

              const res = await fetch(window.DottyToolConfig.ajaxUrl, {
                method: 'POST',
                credentials: 'same-origin',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
                body
              });

              const json = await res.json();

              if (json?.success && json?.data) {
                const remoteState = this.normaliseData(json.data);
                const localUpdated = Number(this.state.updatedAt || 0);
                const remoteUpdated = Number(remoteState.updatedAt || 0);

                if (remoteUpdated > localUpdated) {
                  this.state = remoteState;
                  this.showRoutines = !!this.state.ui?.showRoutines;
                  this.applySettingsClasses();
                  this.render();
                  this.toast('Cloud data loaded', 'success');
                }
              }
            } catch (e) {
              console.warn('Dotty remote load failed', e);
            }
          }

          saveLocal() {
            localStorage.setItem(STORAGE_KEY, JSON.stringify(this.state));
          }

          queueSave() {
            this.dedupeFavouriteItems();
            this.touchUpdatedAt();
            this.saveLocal();
            clearTimeout(this.saveTimer);
            this.saveTimer = setTimeout(() => this.saveRemote(), 450);
          }

          async saveRemote() {
            if (!window.DottyToolConfig?.isLoggedIn) return;

            try {
              const body = new URLSearchParams({
                action: 'save_dotty_data',
                nonce: window.DottyToolConfig.nonce,
                data: JSON.stringify(this.state)
              });

              const res = await fetch(window.DottyToolConfig.ajaxUrl, {
                method: 'POST',
                credentials: 'same-origin',
                headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
                body
              });

              const json = await res.json();
              if (!json?.success) {
                console.warn('Dotty remote save failed', json);
              }
            } catch (e) {
              console.warn('Dotty remote save failed', e);
            }
          }

          registerServiceWorker() {
            if (!('serviceWorker' in navigator)) return;
            if (!window.DottyToolConfig?.serviceWorkerUrl) return;
            navigator.serviceWorker.register(window.DottyToolConfig.serviceWorkerUrl).catch(() => {});
          }

          renderShell() {
            this.root.innerHTML = `
              <div class="dt-shell" aria-live="polite">
                <div class="dt-header">
                  <div class="dt-header-top">
                    <div class="dt-title-wrap">
                      <h1>Emoji & Image PECs Tool</h1>
                      <p>Offline-friendly communication and learning support for Dotty</p>
                    </div>
                    <div class="dt-toolbar">
                      <button class="dt-btn dt-btn-ghost" id="dtHomeBtn" type="button">Home</button>
                      <button class="dt-btn dt-btn-ghost" id="dtEditBtn" type="button">Edit</button>
                      <button class="dt-btn dt-btn-ghost" id="dtFullBtn" type="button">Full Screen</button>
                      <button class="dt-btn dt-btn-ghost dt-hidden" id="dtExportBtn" type="button">Save JSON</button>
                      <button class="dt-btn dt-btn-ghost dt-hidden" id="dtImportBtn" type="button">Load JSON</button>
                    </div>
                  </div>
                </div>

                <div class="dt-main">
                  <div class="dt-topbar">
                    <div class="dt-breadcrumbs" id="dtBreadcrumbs"></div>
                    <div class="dt-settings">
                      <label class="dt-switch"><input type="checkbox" id="dtAutoSpeak"> Auto Speak</label>
                      <label class="dt-switch"><input type="checkbox" id="dtLargeText"> Large Text</label>
                      <label class="dt-switch"><input type="checkbox" id="dtHighContrast"> High Contrast</label>
                    </div>
                  </div>

                  <div class="dt-panel">
                    <div class="dt-panel-grid">
                      <div>
                        <div class="dt-form-group">
                          <label for="dtSearch">Search tiles</label>
                          <div class="dt-search-wrap">
                            <input class="dt-input" id="dtSearch" type="search" placeholder="Search categories or items">
                            <button class="dt-btn dt-btn-warning" id="dtBackBtn" type="button">Back</button>
                          </div>
                        </div>
                      </div>
                      <div>
                        <div class="dt-form-group">
                          <label for="dtSpeechRate">Speech speed</label>
                          <input class="dt-range" id="dtSpeechRate" type="range" min="0.6" max="1.3" step="0.05">
                          <div class="dt-form-help" id="dtSpeechRateValue"></div>
                        </div>
                      </div>
                    </div>
                  </div>

                  <div id="dtView"></div>
                </div>

                <div class="dt-sentence">
                  <div class="dt-sentence-inner">
                    <div class="dt-sentence-top">
                      <h3>Sentence Builder</h3>
                      <div class="dt-sentence-actions">
                        <button class="dt-btn dt-btn-primary" id="dtSpeakSentenceBtn" type="button">Speak</button>
                        <button class="dt-btn dt-btn-warning" id="dtUndoSentenceBtn" type="button">Backspace</button>
                        <button class="dt-btn dt-btn-danger" id="dtClearSentenceBtn" type="button">Clear</button>
                        <button class="dt-btn dt-btn-purple" id="dtSaveRoutineBtn" type="button">Save as Routine</button>
                      </div>
                    </div>
                    <div id="dtSentenceStrip"></div>
                  </div>
                </div>
              </div>

              <dialog class="dt-modal" id="dtTileModal">
                <div class="dt-modal-card">
                  <div class="dt-modal-header">
                    <h3 id="dtTileModalTitle">Edit Tile</h3>
                  </div>
                  <div class="dt-modal-body">
                    <div class="dt-form-grid">
                      <div>
                        <div class="dt-form-group">
                          <label for="dtTileName">Name</label>
                          <input class="dt-input" id="dtTileName" type="text" maxlength="40" placeholder="Enter a name">
                        </div>

                        <div class="dt-form-group">
                          <label for="dtTileDisplayMode">Display Mode</label>
                          <select class="dt-select" id="dtTileDisplayMode">
                            ${DISPLAY_MODES.map(mode => `<option value="${mode.value}">${mode.label}</option>`).join('')}
                          </select>
                        </div>

                        <div class="dt-form-group" id="dtFavouriteWrap">
                          <label class="dt-switch"><input type="checkbox" id="dtTileFavourite"> Favourite</label>
                        </div>

                        <div class="dt-form-group">
                          <label>Image</label>
                          <div class="dt-image-preview-wrap">
                            <div class="dt-image-preview" id="dtImagePreview">
                              <div class="dt-image-preview-empty">No image</div>
                            </div>
                            <div style="display:flex;gap:10px;flex-wrap:wrap;">
                              <button class="dt-btn dt-btn-primary" id="dtPickImageBtn" type="button">Choose Image</button>
                              <button class="dt-btn dt-btn-success" id="dtCameraImageBtn" type="button">Use Camera</button>
                              <button class="dt-btn dt-btn-danger" id="dtRemoveImageBtn" type="button">Remove Image</button>
                            </div>
                          </div>
                          <div class="dt-form-help">Great for real cups, beds, snacks, toys, people, and routines.</div>
                        </div>

                        <div class="dt-form-group">
                          <label for="dtEmojiSearch">Find an emoji</label>
                          <input class="dt-input dt-emoji-search" id="dtEmojiSearch" type="search" placeholder="Search emoji names">
                        </div>

                        <div class="dt-form-group">
                          <label for="dtTileEmoji">Or type your own emoji</label>
                          <input class="dt-input" id="dtTileEmoji" type="text" maxlength="16" placeholder="&#x1f600;">
                        </div>
                      </div>

                      <div>
                        <div class="dt-form-group">
                          <label>Choose emoji</label>
                          <div class="dt-emoji-grid" id="dtEmojiGrid"></div>
                        </div>
                      </div>
                    </div>

                    <div class="dt-modal-actions">
                      <button class="dt-btn dt-btn-warning" id="dtTileCancelBtn" type="button">Cancel</button>
                      <button class="dt-btn dt-btn-success" id="dtTileSaveBtn" type="button">Save</button>
                    </div>
                  </div>
                </div>
              </dialog>

              <dialog class="dt-modal" id="dtRoutineModal">
                <div class="dt-modal-card">
                  <div class="dt-modal-header">
                    <h3>Save Sentence as Routine</h3>
                  </div>
                  <div class="dt-modal-body">
                    <div class="dt-form-group">
                      <label for="dtRoutineName">Routine name</label>
                      <input class="dt-input" id="dtRoutineName" type="text" maxlength="40" placeholder="e.g. Bedtime">
                    </div>
                    <div class="dt-form-group">
                      <label for="dtRoutineEmoji">Routine emoji</label>
                      <input class="dt-input" id="dtRoutineEmoji" type="text" maxlength="16" placeholder="&#x1f319;">
                    </div>
                    <div class="dt-form-group">
                      <label>Preview</label>
                      <div id="dtRoutinePreview" class="dt-empty"></div>
                    </div>
                    <div class="dt-modal-actions">
                      <button class="dt-btn dt-btn-warning" id="dtRoutineCancelBtn" type="button">Cancel</button>
                      <button class="dt-btn dt-btn-success" id="dtRoutineSaveBtn" type="button">Save Routine</button>
                    </div>
                  </div>
                </div>
              </dialog>

              <dialog class="dt-modal" id="dtRoutineEditModal">
                <div class="dt-modal-card">
                  <div class="dt-modal-header">
                    <h3 id="dtRoutineEditTitle">Edit Routine</h3>
                  </div>
                  <div class="dt-modal-body">
                    <div class="dt-form-group">
                      <label for="dtRoutineEditName">Routine name</label>
                      <input class="dt-input" id="dtRoutineEditName" type="text" maxlength="40" placeholder="Routine name">
                    </div>

                    <div class="dt-form-group">
                      <label for="dtRoutineEditEmoji">Routine emoji</label>
                      <input class="dt-input" id="dtRoutineEditEmoji" type="text" maxlength="16" placeholder="&#x2b50;">
                    </div>

                    <div class="dt-form-group">
                      <label>Routine items</label>
                      <div id="dtRoutineEditPreview" class="dt-empty"></div>
                    </div>

                    <div class="dt-modal-actions">
                      <button class="dt-btn dt-btn-warning" id="dtRoutineEditCancelBtn" type="button">Cancel</button>
                      <button class="dt-btn dt-btn-purple" id="dtRoutineOverwriteBtn" type="button">Replace with Current Sentence</button>
                      <button class="dt-btn dt-btn-success" id="dtRoutineEditSaveBtn" type="button">Save Changes</button>
                    </div>
                  </div>
                </div>
              </dialog>

              <dialog class="dt-modal" id="dtConfirmModal">
                <div class="dt-modal-card">
                  <div class="dt-modal-header">
                    <h3>Are you sure?</h3>
                  </div>
                  <div class="dt-modal-body">
                    <p id="dtConfirmText" style="margin:0 0 14px;font-weight:700;"></p>
                    <div class="dt-modal-actions">
                      <button class="dt-btn dt-btn-warning" id="dtConfirmCancelBtn" type="button">Cancel</button>
                      <button class="dt-btn dt-btn-danger" id="dtConfirmOkBtn" type="button">Delete</button>
                    </div>
                  </div>
                </div>
              </dialog>

              <input type="file" id="dtImportFile" accept="application/json,.json" class="dt-hidden">
              <input type="file" id="dtCameraFile" accept="image/*" capture="environment" class="dt-hidden">

              <div class="dt-toast-wrap" id="dtToastWrap" aria-live="polite" aria-atomic="true"></div>
            `;

            this.elements = {
              homeBtn: this.root.querySelector('#dtHomeBtn'),
              editBtn: this.root.querySelector('#dtEditBtn'),
              fullBtn: this.root.querySelector('#dtFullBtn'),
              exportBtn: this.root.querySelector('#dtExportBtn'),
              importBtn: this.root.querySelector('#dtImportBtn'),
              backBtn: this.root.querySelector('#dtBackBtn'),
              breadcrumbs: this.root.querySelector('#dtBreadcrumbs'),
              search: this.root.querySelector('#dtSearch'),
              speechRate: this.root.querySelector('#dtSpeechRate'),
              speechRateValue: this.root.querySelector('#dtSpeechRateValue'),
              autoSpeak: this.root.querySelector('#dtAutoSpeak'),
              largeText: this.root.querySelector('#dtLargeText'),
              highContrast: this.root.querySelector('#dtHighContrast'),
              view: this.root.querySelector('#dtView'),
              sentenceStrip: this.root.querySelector('#dtSentenceStrip'),
              speakSentenceBtn: this.root.querySelector('#dtSpeakSentenceBtn'),
              undoSentenceBtn: this.root.querySelector('#dtUndoSentenceBtn'),
              clearSentenceBtn: this.root.querySelector('#dtClearSentenceBtn'),
              saveRoutineBtn: this.root.querySelector('#dtSaveRoutineBtn'),

              tileModal: this.root.querySelector('#dtTileModal'),
              tileModalTitle: this.root.querySelector('#dtTileModalTitle'),
              tileName: this.root.querySelector('#dtTileName'),
              tileEmoji: this.root.querySelector('#dtTileEmoji'),
              tileDisplayMode: this.root.querySelector('#dtTileDisplayMode'),
              tileFavourite: this.root.querySelector('#dtTileFavourite'),
              favouriteWrap: this.root.querySelector('#dtFavouriteWrap'),
              emojiSearch: this.root.querySelector('#dtEmojiSearch'),
              emojiGrid: this.root.querySelector('#dtEmojiGrid'),
              imagePreview: this.root.querySelector('#dtImagePreview'),
              pickImageBtn: this.root.querySelector('#dtPickImageBtn'),
              cameraImageBtn: this.root.querySelector('#dtCameraImageBtn'),
              removeImageBtn: this.root.querySelector('#dtRemoveImageBtn'),
              tileCancelBtn: this.root.querySelector('#dtTileCancelBtn'),
              tileSaveBtn: this.root.querySelector('#dtTileSaveBtn'),

              routineModal: this.root.querySelector('#dtRoutineModal'),
              routineName: this.root.querySelector('#dtRoutineName'),
              routineEmoji: this.root.querySelector('#dtRoutineEmoji'),
              routinePreview: this.root.querySelector('#dtRoutinePreview'),
              routineCancelBtn: this.root.querySelector('#dtRoutineCancelBtn'),
              routineSaveBtn: this.root.querySelector('#dtRoutineSaveBtn'),

              routineEditModal: this.root.querySelector('#dtRoutineEditModal'),
              routineEditTitle: this.root.querySelector('#dtRoutineEditTitle'),
              routineEditName: this.root.querySelector('#dtRoutineEditName'),
              routineEditEmoji: this.root.querySelector('#dtRoutineEditEmoji'),
              routineEditPreview: this.root.querySelector('#dtRoutineEditPreview'),
              routineEditCancelBtn: this.root.querySelector('#dtRoutineEditCancelBtn'),
              routineOverwriteBtn: this.root.querySelector('#dtRoutineOverwriteBtn'),
              routineEditSaveBtn: this.root.querySelector('#dtRoutineEditSaveBtn'),

              confirmModal: this.root.querySelector('#dtConfirmModal'),
              confirmText: this.root.querySelector('#dtConfirmText'),
              confirmCancelBtn: this.root.querySelector('#dtConfirmCancelBtn'),
              confirmOkBtn: this.root.querySelector('#dtConfirmOkBtn'),

              importFile: this.root.querySelector('#dtImportFile'),
              cameraFile: this.root.querySelector('#dtCameraFile'),
              toastWrap: this.root.querySelector('#dtToastWrap')
            };
          }

          bindGlobalEvents() {
            this.elements.homeBtn.addEventListener('click', () => this.goHome());
            this.elements.editBtn.addEventListener('click', () => this.toggleEditMode());
            this.elements.fullBtn.addEventListener('click', () => this.toggleFullscreen());
            this.elements.exportBtn.addEventListener('click', () => this.exportJsonFile());
            this.elements.importBtn.addEventListener('click', () => this.elements.importFile.click());
            this.elements.importFile.addEventListener('change', (e) => this.importJsonFile(e));
            this.elements.backBtn.addEventListener('click', () => this.goBack());

            this.elements.search.addEventListener('input', (e) => {
              this.searchTerm = e.target.value.trim().toLowerCase();
              this.render();
            });

            this.elements.autoSpeak.addEventListener('change', (e) => {
              this.state.settings.autoSpeak = !!e.target.checked;
              this.queueSave();
            });

            this.elements.largeText.addEventListener('change', (e) => {
              this.state.settings.largeText = !!e.target.checked;
              this.applySettingsClasses();
              this.queueSave();
            });

            this.elements.highContrast.addEventListener('change', (e) => {
              this.state.settings.highContrast = !!e.target.checked;
              this.applySettingsClasses();
              this.queueSave();
            });

            this.elements.speechRate.addEventListener('input', (e) => {
              this.state.settings.speechRate = this.clamp(Number(e.target.value), 0.6, 1.3);
              this.updateSpeechRateText();
              this.queueSave();
            });

            this.elements.speakSentenceBtn.addEventListener('click', () => this.speakSentence());
            this.elements.undoSentenceBtn.addEventListener('click', () => this.undoSentence());
            this.elements.clearSentenceBtn.addEventListener('click', () => this.clearSentence());
            this.elements.saveRoutineBtn.addEventListener('click', () => this.openRoutineModal());

            this.elements.tileCancelBtn.addEventListener('click', () => this.closeTileModal());
            this.elements.tileSaveBtn.addEventListener('click', () => this.saveTileModal());

            this.elements.emojiSearch.addEventListener('input', (e) => {
              this.emojiSearchTerm = e.target.value.trim().toLowerCase();
              this.renderEmojiPicker();
            });

            this.elements.tileEmoji.addEventListener('input', () => this.renderEmojiPicker());
            this.elements.pickImageBtn.addEventListener('click', () => this.pickImage());
            this.elements.cameraImageBtn.addEventListener('click', () => this.pickCameraImage());
            this.elements.cameraFile.addEventListener('change', (e) => this.handleCameraUpload(e));
            this.elements.removeImageBtn.addEventListener('click', () => this.removeModalImage());

            this.elements.routineCancelBtn.addEventListener('click', () => this.elements.routineModal.close());
            this.elements.routineSaveBtn.addEventListener('click', () => this.saveRoutine());

            this.elements.routineEditCancelBtn.addEventListener('click', () => this.closeRoutineEditModal());
            this.elements.routineEditSaveBtn.addEventListener('click', () => this.saveRoutineEdit());
            this.elements.routineOverwriteBtn.addEventListener('click', () => this.overwriteRoutineFromSentence());

            this.elements.confirmCancelBtn.addEventListener('click', () => {
              this.pendingDelete = null;
              this.elements.confirmModal.close();
            });
            this.elements.confirmOkBtn.addEventListener('click', () => this.confirmDelete());

            this.elements.view.addEventListener('click', (e) => this.handleViewClick(e));

            this.elements.sentenceStrip.addEventListener('click', (e) => {
              const removeBtn = e.target.closest('[data-remove-sentence-index]');
              if (removeBtn) {
                const index = Number(removeBtn.dataset.removeSentenceIndex);
                this.removeSentenceToken(index);
              }
            });

            [this.elements.tileModal, this.elements.routineModal, this.elements.routineEditModal, this.elements.confirmModal].forEach(dialog => {
              if (!dialog) return;
              dialog.addEventListener('click', (e) => {
                const rect = dialog.getBoundingClientRect();
                const clickedInDialog =
                  e.clientX >= rect.left &&
                  e.clientX <= rect.right &&
                  e.clientY >= rect.top &&
                  e.clientY <= rect.bottom;
                if (!clickedInDialog) {
                  dialog.close();
                }
              });
            });

            document.addEventListener('fullscreenchange', () => {
              if (!document.fullscreenElement) {
                this.root.classList.remove('dt-fullscreen');
              }
            });
          }

          applySettingsClasses() {
            this.root.classList.toggle('dt-high-contrast', !!this.state.settings.highContrast);
            this.root.classList.toggle('dt-large-text', !!this.state.settings.largeText);

            if (this.elements.autoSpeak) this.elements.autoSpeak.checked = !!this.state.settings.autoSpeak;
            if (this.elements.largeText) this.elements.largeText.checked = !!this.state.settings.largeText;
            if (this.elements.highContrast) this.elements.highContrast.checked = !!this.state.settings.highContrast;
            if (this.elements.speechRate) this.elements.speechRate.value = String(this.state.settings.speechRate);
            this.updateSpeechRateText();
          }

          updateSpeechRateText() {
            if (this.elements.speechRateValue) {
              this.elements.speechRateValue.textContent = `Current speed: ${Number(this.state.settings.speechRate).toFixed(2)}x`;
            }
          }

          toast(message, type = 'info') {
            const item = document.createElement('div');
            item.className = `dt-toast ${type}`;
            item.textContent = message;
            this.elements.toastWrap.appendChild(item);
            setTimeout(() => item.remove(), 2600);
          }

          getFavouriteItems() {
            const out = [];
            const seen = new Set();

            this.state.categories.forEach((cat, catIndex) => {
              cat.items.forEach((item, itemIndex) => {
                if (!item.favourite) return;
                const key = `${item.name.toLowerCase()}|${item.emoji}|${item.imageUrl}`;
                if (seen.has(key)) return;
                seen.add(key);
                out.push({ item, catIndex, itemIndex });
              });
            });

            return out;
          }

          goHome() {
            this.currentView = 'main';
            this.currentCategoryIndex = null;
            this.searchTerm = '';
            if (this.elements.search) {
              this.elements.search.value = '';
            }
            this.render();
          }

          goBack() {
            this.goHome();
          }

          render() {
            this.root.classList.toggle('dt-editing', this.editMode);
            this.renderBreadcrumbs();
            this.renderView();
            this.renderSentence();
            this.elements.exportBtn.classList.toggle('dt-hidden', !this.editMode);
            this.elements.importBtn.classList.toggle('dt-hidden', !this.editMode);
            this.elements.backBtn.classList.toggle('dt-hidden', this.currentView === 'main');
            this.elements.editBtn.textContent = this.editMode ? 'Edit Mode On' : 'Edit';
          }

          renderBreadcrumbs() {
            let html = `<button class="dt-chip" type="button" id="dtBreadcrumbHome">&#x1f3e0; Home</button>`;

            if (this.currentView === 'category' && this.state.categories[this.currentCategoryIndex]) {
              const cat = this.state.categories[this.currentCategoryIndex];
              html += `<span>›</span><span class="dt-chip">${this.escape(cat.emoji)} ${this.escape(cat.name)}</span>`;
            }

            this.elements.breadcrumbs.innerHTML = html;

            const homeChip = this.root.querySelector('#dtBreadcrumbHome');
            if (homeChip) {
              homeChip.addEventListener('click', () => this.goHome());
            }
          }

          renderView() {
            if (this.currentView === 'main') {
              this.renderMain();
            } else {
              this.renderCategory();
            }
          }

          renderFavouriteCard(entry) {
            const item = entry.item;
            return `
              <button
                class="dt-fav-card"
                type="button"
                data-card-type="item"
                data-index="${entry.itemIndex}"
                data-category-index="${entry.catIndex}"
              >
                ${
                  item.imageUrl
                    ? `<div class="img-wrap"><img decoding="async" src="${this.escape(item.imageUrl)}" alt="${this.escape(item.name)}"></div>`
                    : `<div class="emoji">${this.escape(item.emoji)}</div>`
                }
                <div class="label">${this.escape(item.name)}</div>
              </button>
            `;
          }

          renderMain() {
            const filtered = this.state.categories.filter(cat => {
              if (!this.searchTerm) return true;
              if (cat.name.toLowerCase().includes(this.searchTerm)) return true;
              return cat.items.some(item => item.name.toLowerCase().includes(this.searchTerm));
            });

            const favourites = this.getFavouriteItems();

            let html = '';

            html += `
              <div class="dt-panel">
                <div class="dt-title-row">
                  <h2>Favourites</h2>
                  <div class="dt-form-help">${favourites.length} quick button${favourites.length === 1 ? '' : 's'}</div>
                </div>
            `;

            if (!favourites.length) {
              html += `<div class="dt-empty">Mark favourite items with the star button in edit mode.</div>`;
            } else {
              html += `<div class="dt-favourites-grid">`;
              favourites.forEach(entry => {
                html += this.renderFavouriteCard(entry);
              });
              html += `</div>`;
            }

            html += `</div>`;

            html += `
              <div class="dt-panel">
                <div class="dt-title-row">
                  <h2>Quick Access</h2>
                  <div class="dt-form-help">Fast actions for routines and everyday use</div>
                </div>
                <div style="display:flex;gap:10px;flex-wrap:wrap;">
                  <button class="dt-btn dt-btn-primary" type="button" data-toggle-routines="1">
                    ${this.showRoutines ? 'Hide Routines' : 'Show Routines'}
                  </button>
                </div>
              </div>
            `;

            if (this.showRoutines) {
              html += `
                <div class="dt-panel">
                  <div class="dt-title-row">
                    <h2>Routines</h2>
                    <div class="dt-form-help">${this.state.routines.length} saved</div>
                  </div>
              `;

              if (!this.state.routines.length) {
                html += `<div class="dt-empty">Build a sentence, then save it as a routine.</div>`;
              } else {
                html += `<div class="dt-routine-list">`;
                this.state.routines.forEach((routine, index) => {
                  html += `
                    <div class="dt-routine-card">
                      <button class="dt-routine-card-main" type="button" data-routine-index="${index}">
                        <span class="e">${this.escape(routine.emoji)}</span>
                        <span class="t">${this.escape(routine.name)}</span>
                        <span class="s">${routine.items.length} step${routine.items.length === 1 ? '' : 's'}</span>
                      </button>

                      ${this.editMode ? `
                        <div class="dt-routine-actions">
                          <button class="dt-mini-btn move" type="button" data-routine-move="-1" data-routine-index="${index}">←</button>
                          <button class="dt-mini-btn move" type="button" data-routine-move="1" data-routine-index="${index}">→</button>
                          <button class="dt-mini-btn edit" type="button" data-edit-routine="${index}">Edit</button>
                          <button class="dt-mini-btn copy" type="button" data-duplicate-routine="${index}">Copy</button>
                          <button class="dt-mini-btn delete" type="button" data-delete-routine="${index}">Delete</button>
                        </div>
                      ` : ''}
                    </div>
                  `;
                });
                html += `</div>`;
              }

              html += `</div>`;
            }

            html += `
              <div class="dt-panel">
                <div class="dt-title-row">
                  <h2>Quick Add Templates</h2>
                  <div class="dt-form-help">${this.editMode ? 'Tap a pack to add ready-made buttons' : 'Turn on edit mode to add packs'}</div>
                </div>
                <div class="dt-template-list">
                  ${TEMPLATE_PACKS.map((pack, idx) => `
                    <button class="dt-template-chip ${!this.editMode ? 'is-disabled' : ''}" type="button" data-template-pack="${idx}">
                      ${this.escape(pack.name)} (${pack.items.length})
                    </button>
                  `).join('')}
                </div>
              </div>
            `;

            html += `
              <div class="dt-panel">
                <div class="dt-title-row">
                  <h2>Categories</h2>
                  <div class="dt-form-help">${filtered.length} shown</div>
                </div>
            `;

            html += `<div class="dt-grid">`;

            if (!filtered.length) {
              html += `<div class="dt-empty" style="grid-column:1/-1;">No categories match your search.</div>`;
            } else {
              filtered.forEach(cat => {
                const actualIndex = this.state.categories.indexOf(cat);
                html += this.cardHtml({
                  type: 'category',
                  index: actualIndex,
                  data: cat,
                  subtitle: `${cat.items.length} item${cat.items.length === 1 ? '' : 's'}`
                });
              });
            }

            if (this.editMode) {
              html += this.addCardHtml('category');
            }

            html += `</div>`;
            html += `</div>`;

            this.elements.view.innerHTML = html;
          }

          renderCategory() {
            const category = this.state.categories[this.currentCategoryIndex];
            if (!category) {
              this.currentView = 'main';
              this.currentCategoryIndex = null;
              return this.render();
            }

            const filteredItems = category.items.filter(item => {
              if (!this.searchTerm) return true;
              return item.name.toLowerCase().includes(this.searchTerm);
            });

            let html = `
              <div class="dt-panel">
                <div class="dt-title-row">
                  <h2>${this.escape(category.emoji)} ${this.escape(category.name)}</h2>
                  <div class="dt-form-help">${filteredItems.length} shown</div>
                </div>
            `;

            html += `<div class="dt-grid">`;

            if (!filteredItems.length) {
              html += `<div class="dt-empty" style="grid-column:1/-1;">No items match your search in this category.</div>`;
            } else {
              filteredItems.forEach(item => {
                const actualIndex = category.items.indexOf(item);
                html += this.cardHtml({
                  type: 'item',
                  index: actualIndex,
                  categoryIndex: this.currentCategoryIndex,
                  data: item,
                  subtitle: 'Tap to say and add'
                });
              });
            }

            if (this.editMode) {
              html += this.addCardHtml('item', this.currentCategoryIndex);
            }

            html += `</div>`;
            html += `</div>`;
            this.elements.view.innerHTML = html;
          }

          renderSentence() {
            const sentence = this.state.sentence;
            if (!sentence.length) {
              this.elements.sentenceStrip.innerHTML = `<div class="dt-empty">Tap tiles to build a sentence.</div>`;
              return;
            }

            this.elements.sentenceStrip.innerHTML = `
              <div class="dt-sentence-strip">
                ${sentence.map((item, i) => `
                  <div class="dt-sentence-token" data-sentence-index="${i}">
                    <button class="dt-sentence-token-remove" type="button" data-remove-sentence-index="${i}" aria-label="Remove ${this.escape(item.name)}">×</button>
                    ${item.imageUrl ? `<div class="img-wrap"><img decoding="async" src="${this.escape(item.imageUrl)}" alt="${this.escape(item.name)}"></div>` : `<span class="emoji">${this.escape(item.emoji)}</span>`}
                    <span>${this.escape(item.name)}</span>
                  </div>
                `).join('')}
              </div>
            `;
          }

          cardHtml({ type, index, categoryIndex = '', data, subtitle }) {
            const display = this.getDisplayParts(data);
            const classes = ['dt-card'];

            if (data.style) {
              classes.push(`style-${this.escape(data.style)}`);
            }

            if (display.showImage && !display.showText && !display.showEmojiBadge && !display.showEmojiMain) {
              classes.push('has-image-only');
            }

            return `
              <button
                class="${classes.join(' ')}"
                type="button"
                data-card-type="${this.escape(type)}"
                data-index="${index}"
                ${type === 'item' ? `data-category-index="${categoryIndex}"` : ''}
              >
                ${this.editMode ? this.cardActionsHtml(type, index, categoryIndex, data) : ''}
                <div class="dt-card-media">
                  ${display.mediaHtml}
                  ${display.showEmojiBadge ? `<div class="dt-card-emoji-badge">${this.escape(data.emoji)}</div>` : ''}
                </div>
                ${display.showText ? `<div class="dt-card-title">${this.escape(type === 'category' ? data.name.toUpperCase() : data.name)}</div>` : `<div></div>`}
                ${display.showText && subtitle ? `<div class="dt-card-sub">${this.escape(subtitle)}</div>` : `<div></div>`}
              </button>
            `;
          }

          getDisplayParts(data) {
            const hasImage = !!data.imageUrl;
            let mode = data.displayMode || 'emoji_text';

            if (hasImage && mode === 'emoji_text') {
              mode = 'image_text';
            }

            let showImage = false;
            let showEmojiMain = false;
            let showEmojiBadge = false;
            let showText = true;

            if (mode === 'emoji_text') {
              showEmojiMain = true;
              showText = true;
            } else if (mode === 'image_text') {
              showImage = hasImage;
              showText = true;
              if (!hasImage) showEmojiMain = true;
            } else if (mode === 'image_only') {
              showImage = hasImage;
              showText = false;
              if (!hasImage) {
                showEmojiMain = true;
                showText = true;
              }
            } else if (mode === 'image_emoji_text') {
              showImage = hasImage;
              showText = true;
              if (hasImage) {
                showEmojiBadge = true;
              } else {
                showEmojiMain = true;
              }
            }

            let mediaHtml = '';

            if (showImage && hasImage) {
              mediaHtml += `
                <div class="dt-card-image-wrap">
                  <img decoding="async" class="dt-card-image" src="${this.escape(data.imageUrl)}" alt="${this.escape(data.name)}">
                </div>
              `;
            } else if (showEmojiMain || (!hasImage && !showImage)) {
              mediaHtml += `<div class="dt-card-emoji">${this.escape(data.emoji || '&#x2753;')}</div>`;
            }

            return { mediaHtml, showImage, showEmojiMain, showEmojiBadge, showText };
          }

          addCardHtml(type, categoryIndex = '') {
            return `
              <button
                class="dt-card is-add"
                type="button"
                data-action="add"
                data-add-type="${this.escape(type)}"
                ${type === 'item' ? `data-category-index="${categoryIndex}"` : ''}
              >
                <div class="dt-card-media">
                  <div class="dt-card-emoji">&#x2795;</div>
                </div>
                <div class="dt-card-title">${type === 'category' ? 'ADD CATEGORY' : 'ADD ITEM'}</div>
                <div class="dt-card-sub">Create a new tile</div>
              </button>
            `;
          }

          cardActionsHtml(type, index, categoryIndex, data) {
            return `
              <div class="dt-card-actions">
                <div class="dt-card-actions-left">
                  <button class="dt-action-btn dt-action-move" type="button" data-action="move-left" data-card-type="${this.escape(type)}" data-index="${index}" ${type === 'item' ? `data-category-index="${categoryIndex}"` : ''}>←</button>
                  <button class="dt-action-btn dt-action-move" type="button" data-action="move-right" data-card-type="${this.escape(type)}" data-index="${index}" ${type === 'item' ? `data-category-index="${categoryIndex}"` : ''}>→</button>
                </div>
                <div class="dt-card-actions-right">
                  ${type === 'item' ? `<button class="dt-action-btn dt-action-star" type="button" data-action="toggle-favourite" data-card-type="${this.escape(type)}" data-index="${index}" data-category-index="${categoryIndex}">${data.favourite ? '★' : '☆'}</button>` : ''}
                  <button class="dt-action-btn dt-action-edit" type="button" data-action="edit" data-card-type="${this.escape(type)}" data-index="${index}" ${type === 'item' ? `data-category-index="${categoryIndex}"` : ''}>Edit</button>
                  <button class="dt-action-btn dt-action-delete" type="button" data-action="delete" data-card-type="${this.escape(type)}" data-index="${index}" ${type === 'item' ? `data-category-index="${categoryIndex}"` : ''}>✕</button>
                </div>
              </div>
            `;
          }

          handleViewClick(e) {
            const toggleRoutinesBtn = e.target.closest('[data-toggle-routines]');
            if (toggleRoutinesBtn) {
              e.preventDefault();
              e.stopPropagation();
              this.showRoutines = !this.showRoutines;
              this.queueSave();
              this.render();
              return;
            }

            const templateBtn = e.target.closest('[data-template-pack]');
            if (templateBtn) {
              e.preventDefault();
              e.stopPropagation();
              this.applyTemplatePack(Number(templateBtn.dataset.templatePack));
              return;
            }

            const routineMove = e.target.closest('[data-routine-move]');
            if (routineMove) {
              e.preventDefault();
              e.stopPropagation();
              this.moveRoutine(
                Number(routineMove.dataset.routineIndex),
                Number(routineMove.dataset.routineMove)
              );
              return;
            }

            const routineEdit = e.target.closest('[data-edit-routine]');
            if (routineEdit) {
              e.preventDefault();
              e.stopPropagation();
              this.openRoutineEditModal(Number(routineEdit.dataset.editRoutine));
              return;
            }

            const routineDuplicate = e.target.closest('[data-duplicate-routine]');
            if (routineDuplicate) {
              e.preventDefault();
              e.stopPropagation();
              this.duplicateRoutine(Number(routineDuplicate.dataset.duplicateRoutine));
              return;
            }

            const routineDelete = e.target.closest('[data-delete-routine]');
            if (routineDelete) {
              e.preventDefault();
              e.stopPropagation();
              this.askDelete({ type: 'routine', index: Number(routineDelete.dataset.deleteRoutine) });
              return;
            }

            const routineBtn = e.target.closest('[data-routine-index]');
            if (routineBtn) {
              e.preventDefault();
              e.stopPropagation();
              this.applyRoutine(Number(routineBtn.dataset.routineIndex));
              return;
            }

            const actionBtn = e.target.closest('[data-action]');
            if (actionBtn) {
              e.preventDefault();
              e.stopPropagation();

              const action = actionBtn.dataset.action;
              const type = actionBtn.dataset.cardType;
              const index = Number(actionBtn.dataset.index);
              const categoryIndex = actionBtn.dataset.categoryIndex !== undefined ? Number(actionBtn.dataset.categoryIndex) : null;

              if (action === 'add') {
                const addType = actionBtn.dataset.addType;
                const addCatIndex = actionBtn.dataset.categoryIndex !== undefined ? Number(actionBtn.dataset.categoryIndex) : null;
                this.openTileModal({ mode: 'add', type: addType, categoryIndex: addCatIndex });
                return;
              }

              if (action === 'edit') {
                this.openTileModal({ mode: 'edit', type, index, categoryIndex });
                return;
              }

              if (action === 'delete') {
                this.askDelete({ type, index, categoryIndex });
                return;
              }

              if (action === 'move-left' || action === 'move-right') {
                this.moveTile({ type, index, categoryIndex, direction: action === 'move-left' ? -1 : 1 });
                return;
              }

              if (action === 'toggle-favourite') {
                this.toggleFavourite(categoryIndex, index);
                return;
              }
            }

            const card = e.target.closest('.dt-card, .dt-fav-card');
            if (!card) return;

            const addType = card.dataset.addType;
            if (addType) {
              const addCatIndex = card.dataset.categoryIndex !== undefined ? Number(card.dataset.categoryIndex) : null;
              this.openTileModal({ mode: 'add', type: addType, categoryIndex: addCatIndex });
              return;
            }

            const type = card.dataset.cardType;
            const index = Number(card.dataset.index);
            const categoryIndex = card.dataset.categoryIndex !== undefined ? Number(card.dataset.categoryIndex) : null;

            this.root.querySelectorAll('.dt-card, .dt-fav-card').forEach(el => el.classList.remove(ACTIVE_CLASS));
            card.classList.add(ACTIVE_CLASS);
            setTimeout(() => card.classList.remove(ACTIVE_CLASS), 700);

            if (type === 'category') {
              this.currentView = 'category';
              this.currentCategoryIndex = index;
              this.render();
              return;
            }

            if (type === 'item' && categoryIndex !== null) {
              const item = this.state.categories[categoryIndex]?.items[index];
              if (!item) return;
              this.state.sentence.push(this.normaliseItem(item));
              this.state.sentence = this.state.sentence.slice(-20);
              this.renderSentence();
              this.queueSave();

              if (this.state.settings.autoSpeak) {
                this.speak(item.name);
              }
            }
          }

          applyTemplatePack(packIndex) {
            if (!this.editMode) {
              this.toast('Turn on edit mode to add template buttons', 'info');
              return;
            }

            const pack = TEMPLATE_PACKS[packIndex];
            if (!pack) return;

            let targetCategoryIndex = this.currentCategoryIndex;

            if (targetCategoryIndex === null || targetCategoryIndex === undefined) {
              const existingIndex = this.state.categories.findIndex(
                c => c.name.toLowerCase() === pack.name.toLowerCase()
              );

              if (existingIndex >= 0) {
                targetCategoryIndex = existingIndex;
              } else {
                this.state.categories.push({
                  name: pack.name,
                  emoji: pack.items[0]?.emoji || '&#x1f4c1;',
                  imageId: 0,
                  imageUrl: '',
                  displayMode: 'emoji_text',
                  items: []
                });
                targetCategoryIndex = this.state.categories.length - 1;
              }
            }

            const targetCategory = this.state.categories[targetCategoryIndex];
            if (!targetCategory) return;

            let added = 0;

            pack.items.forEach(templateItem => {
              const exists = targetCategory.items.some(
                item => item.name.toLowerCase() === templateItem.name.toLowerCase()
              );

              if (!exists) {
                targetCategory.items.push(this.normaliseItem({
                  ...templateItem,
                  imageId: 0,
                  imageUrl: '',
                  displayMode: 'emoji_text'
                }));
                added++;
              }
            });

            this.currentView = 'category';
            this.currentCategoryIndex = targetCategoryIndex;
            this.queueSave();
            this.render();
            this.toast(`${added} template buttons added to ${targetCategory.name}`, 'success');
          }

          toggleFullscreen() {
            const onMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent);

            if (onMobile) {
              this.root.classList.toggle('dt-fullscreen');
              return;
            }

            if (!document.fullscreenElement) {
              this.root.classList.add('dt-fullscreen');
              this.root.requestFullscreen?.().catch(() => {
                this.root.classList.remove('dt-fullscreen');
              });
            } else {
              document.exitFullscreen?.();
            }
          }

          toggleEditMode() {
            if (this.editMode) {
              this.editMode = false;
              this.render();
              this.toast('Edit mode disabled', 'info');
              return;
            }

            if (!this.parentCode) {
              const code = window.prompt('Set a parent code for edit mode');
              if (!code || !String(code).trim()) return;
              this.parentCode = String(code).trim();
              sessionStorage.setItem('dotty_parent_code', this.parentCode);
            }

            const entered = window.prompt('Enter parent code');
            if (!entered) return;

            if (String(entered).trim() !== this.parentCode) {
              this.toast('Wrong parent code', 'error');
              return;
            }

            this.editMode = true;
            this.render();
            this.toast('Edit mode enabled', 'success');
          }

          openTileModal(context) {
            this.editingContext = {
              ...context,
              tempImageId: 0,
              tempImageUrl: ''
            };

            this.emojiSearchTerm = '';
            this.elements.emojiSearch.value = '';

            let current = null;

            if (context.mode === 'edit') {
              if (context.type === 'category') {
                current = this.state.categories[context.index];
              } else {
                current = this.state.categories[context.categoryIndex]?.items[context.index];
              }
            }

            this.elements.tileModalTitle.textContent =
              context.mode === 'add'
                ? (context.type === 'category' ? 'Add Category' : 'Add Item')
                : (context.type === 'category' ? 'Edit Category' : 'Edit Item');

            this.elements.tileName.value = current?.name || '';
            this.elements.tileEmoji.value = current?.emoji || '';
            this.elements.tileDisplayMode.value = current?.displayMode || 'emoji_text';
            this.elements.tileFavourite.checked = !!current?.favourite;
            this.elements.favouriteWrap.classList.toggle('dt-hidden', context.type === 'category');

            this.editingContext.tempImageId = Number(current?.imageId || 0) || 0;
            this.editingContext.tempImageUrl = current?.imageUrl || '';

            this.renderEmojiPicker();
            this.renderImagePreview();
            this.updateImageButtons();

            this.elements.tileModal.showModal();
            setTimeout(() => this.elements.tileName.focus(), 30);
          }

          closeTileModal() {
            this.editingContext = null;
            this.elements.tileModal.close();
          }

          renderEmojiPicker() {
            const search = this.emojiSearchTerm;
            const currentEmoji = this.elements.tileEmoji.value.trim();

            const list = EMOJIS.filter(item => {
              if (!search) return true;
              return item.name.toLowerCase().includes(search);
            }).slice(0, 140);

            this.elements.emojiGrid.innerHTML = list.map(item => `
              <button class="dt-emoji-option ${currentEmoji === item.emoji ? 'is-selected' : ''}" type="button" data-pick-emoji="${this.escape(item.emoji)}" data-pick-name="${this.escape(item.name)}">
                <span class="e">${this.escape(item.emoji)}</span>
                <span class="n">${this.escape(item.name)}</span>
              </button>
            `).join('');

            this.elements.emojiGrid.querySelectorAll('[data-pick-emoji]').forEach(btn => {
              btn.addEventListener('click', () => {
                const emoji = btn.dataset.pickEmoji;
                const name = btn.dataset.pickName;
                this.elements.tileEmoji.value = emoji;
                if (!this.elements.tileName.value.trim()) {
                  this.elements.tileName.value = name;
                }
                this.renderEmojiPicker();
              });
            });
          }

          renderImagePreview() {
            const url = this.editingContext?.tempImageUrl || '';
            if (!url) {
              this.elements.imagePreview.innerHTML = `<div class="dt-image-preview-empty">No image</div>`;
              return;
            }
            this.elements.imagePreview.innerHTML = `<img decoding="async" src="${this.escape(url)}" alt="Selected image">`;
          }

          updateImageButtons() {
            const canUpload = !!window.DottyToolConfig?.canUpload && !!window.wp?.media;
            this.elements.pickImageBtn.disabled = !canUpload;
            this.elements.cameraImageBtn.disabled = !window.DottyToolConfig?.canUpload;
            this.elements.pickImageBtn.textContent = canUpload ? 'Choose Image' : 'Upload unavailable';
          }

          pickImage() {
            if (!window.DottyToolConfig?.canUpload || !window.wp?.media) {
              this.toast('Image upload needs a logged-in user with media access', 'error');
              return;
            }

            const frame = window.wp.media({
              title: 'Choose tile image',
              button: { text: 'Use this image' },
              multiple: false,
              library: { type: ['image'] }
            });

            frame.on('select', () => {
              const attachment = frame.state().get('selection').first().toJSON();
              this.editingContext.tempImageId = Number(attachment.id || 0);
              this.editingContext.tempImageUrl = attachment.sizes?.medium?.url || attachment.url || '';
              this.renderImagePreview();

              if (this.elements.tileDisplayMode.value === 'emoji_text') {
                this.elements.tileDisplayMode.value = 'image_text';
              }
            });

            frame.open();
          }

          pickCameraImage() {
            if (!window.DottyToolConfig?.canUpload) {
              this.toast('Camera upload needs a logged-in user with upload access', 'error');
              return;
            }
            this.elements.cameraFile.click();
          }

          async handleCameraUpload(e) {
            const file = e.target.files?.[0];
            if (!file) return;

            const formData = new FormData();
            formData.append('action', 'dotty_upload_image');
            formData.append('nonce', window.DottyToolConfig.nonce);
            formData.append('image', file);

            try {
              const res = await fetch(window.DottyToolConfig.ajaxUrl, {
                method: 'POST',
                credentials: 'same-origin',
                body: formData
              });

              const json = await res.json();

              if (json?.success && json?.data) {
                this.editingContext.tempImageId = Number(json.data.id || 0);
                this.editingContext.tempImageUrl = json.data.url || '';
                this.renderImagePreview();
                if (this.elements.tileDisplayMode.value === 'emoji_text') {
                  this.elements.tileDisplayMode.value = 'image_text';
                }
                this.toast('Camera image uploaded', 'success');
              } else {
                this.toast(json?.data?.message || 'Upload failed', 'error');
              }
            } catch (err) {
              this.toast('Upload failed', 'error');
            } finally {
              e.target.value = '';
            }
          }

          removeModalImage() {
            if (!this.editingContext) return;
            this.editingContext.tempImageId = 0;
            this.editingContext.tempImageUrl = '';
            this.renderImagePreview();

            if (this.elements.tileDisplayMode.value !== 'emoji_text') {
              this.elements.tileDisplayMode.value = 'emoji_text';
            }
          }

          saveTileModal() {
            if (!this.editingContext) return;

            const name = this.cleanText(this.elements.tileName.value, 'Untitled');
            const emoji = this.cleanEmoji(this.elements.tileEmoji.value, '&#x2753;');
            const displayMode = this.cleanDisplayMode(this.elements.tileDisplayMode.value);
            const imageId = Number(this.editingContext.tempImageId || 0) || 0;
            const imageUrl = this.cleanImageUrl(this.editingContext.tempImageUrl || '');
            const favourite = !!this.elements.tileFavourite.checked;

            const payload = {
              name,
              emoji,
              imageId,
              imageUrl,
              displayMode,
              favourite,
              style: this.editingContext?.mode === 'edit'
                ? (this.editingContext.type === 'item'
                    ? (this.state.categories[this.editingContext.categoryIndex]?.items[this.editingContext.index]?.style || '')
                    : '')
                : ''
            };

            const ctx = this.editingContext;

            if (ctx.mode === 'add') {
              if (ctx.type === 'category') {
                this.state.categories.push({ ...payload, items: [] });
                this.currentView = 'main';
              } else {
                this.state.categories[ctx.categoryIndex]?.items.push(payload);
                this.currentView = 'category';
                this.currentCategoryIndex = ctx.categoryIndex;
              }
              this.toast('Tile added', 'success');
            } else {
              if (ctx.type === 'category') {
                const cat = this.state.categories[ctx.index];
                if (cat) {
                  cat.name = payload.name;
                  cat.emoji = payload.emoji;
                  cat.imageId = payload.imageId;
                  cat.imageUrl = payload.imageUrl;
                  cat.displayMode = payload.displayMode;
                }
              } else {
                const item = this.state.categories[ctx.categoryIndex]?.items[ctx.index];
                if (item) {
                  item.name = payload.name;
                  item.emoji = payload.emoji;
                  item.imageId = payload.imageId;
                  item.imageUrl = payload.imageUrl;
                  item.displayMode = payload.displayMode;
                  item.favourite = payload.favourite;
                }
              }
              this.toast('Tile updated', 'success');
            }

            this.queueSave();
            this.closeTileModal();
            this.render();
          }

          toggleFavourite(categoryIndex, index) {
            const item = this.state.categories[categoryIndex]?.items[index];
            if (!item) return;
            item.favourite = !item.favourite;
            this.queueSave();
            this.render();
          }

          openRoutineModal() {
            if (!this.state.sentence.length) {
              this.toast('Build a sentence first', 'info');
              return;
            }

            this.elements.routineName.value = '';
            this.elements.routineEmoji.value = '&#x2b50;';
            this.elements.routinePreview.innerHTML = this.state.sentence.map(i => this.escape(i.name)).join(' → ');
            this.elements.routineModal.showModal();
          }

          saveRoutine() {
            const name = this.cleanText(this.elements.routineName.value, 'Routine');
            const emoji = this.cleanEmoji(this.elements.routineEmoji.value, '&#x2b50;');

            if (!this.state.sentence.length) {
              this.toast('Sentence is empty', 'error');
              return;
            }

            this.state.routines.push({
              name,
              emoji,
              items: this.state.sentence.map(item => this.normaliseItem(item))
            });

            this.state.routines = this.state.routines.slice(-20);
            this.queueSave();
            this.elements.routineModal.close();
            this.render();
            this.toast('Routine saved', 'success');
          }

          openRoutineEditModal(index) {
            const routine = this.state.routines[index];
            if (!routine) return;

            this.editingRoutineIndex = index;
            this.elements.routineEditName.value = routine.name || '';
            this.elements.routineEditEmoji.value = routine.emoji || '&#x2b50;';
            this.renderRoutineEditPreview();
            this.elements.routineEditModal.showModal();
          }

          closeRoutineEditModal() {
            this.editingRoutineIndex = null;
            this.elements.routineEditModal.close();
          }

          renderRoutineEditPreview() {
            const index = this.editingRoutineIndex;
            const routine = this.state.routines[index];
            if (!routine || !Array.isArray(routine.items) || !routine.items.length) {
              this.elements.routineEditPreview.innerHTML = 'No items in this routine yet.';
              return;
            }

            this.elements.routineEditPreview.innerHTML = routine.items
              .map(item => this.escape(item.name))
              .join(' → ');
          }

          saveRoutineEdit() {
            const index = this.editingRoutineIndex;
            const routine = this.state.routines[index];
            if (!routine) return;

            routine.name = this.cleanText(this.elements.routineEditName.value, 'Routine');
            routine.emoji = this.cleanEmoji(this.elements.routineEditEmoji.value, '&#x2b50;');

            this.queueSave();
            this.render();
            this.closeRoutineEditModal();
            this.toast('Routine updated', 'success');
          }

          overwriteRoutineFromSentence() {
            const index = this.editingRoutineIndex;
            const routine = this.state.routines[index];
            if (!routine) return;

            if (!this.state.sentence.length) {
              this.toast('Build a sentence first', 'info');
              return;
            }

            routine.items = this.state.sentence.map(item => this.normaliseItem(item)).slice(0, 30);
            this.renderRoutineEditPreview();
            this.queueSave();
            this.render();
            this.toast('Routine replaced with current sentence', 'success');
          }

          duplicateRoutine(index) {
            const routine = this.state.routines[index];
            if (!routine) return;

            const copy = {
              name: this.cleanText(`${routine.name} Copy`, 'Routine Copy'),
              emoji: routine.emoji || '&#x2b50;',
              items: routine.items.map(item => this.normaliseItem(item))
            };

            this.state.routines.splice(index + 1, 0, copy);
            this.state.routines = this.state.routines.slice(0, 20);
            this.queueSave();
            this.render();
            this.toast('Routine copied', 'success');
          }

          moveRoutine(index, direction) {
            const target = index + direction;
            if (target < 0 || target >= this.state.routines.length) return;

            const arr = this.state.routines;
            [arr[index], arr[target]] = [arr[target], arr[index]];
            this.queueSave();
            this.render();
          }

          applyRoutine(index) {
            const routine = this.state.routines[index];
            if (!routine || !Array.isArray(routine.items) || !routine.items.length) {
              this.toast('Routine is empty', 'info');
              return;
            }

            this.state.sentence = [...routine.items.map(i => this.normaliseItem(i))].slice(-20);
            this.render();
            this.queueSave();

            if (this.state.settings.autoSpeak) {
              this.speak(routine.items.map(i => i.name).join(' '));
            }
          }

          askDelete(context) {
            this.pendingDelete = context;
            let label = 'item';
            if (context.type === 'category') label = 'category';
            if (context.type === 'routine') label = 'routine';
            this.elements.confirmText.textContent = `Delete this ${label}?`;
            this.elements.confirmModal.showModal();
          }

          confirmDelete() {
            const ctx = this.pendingDelete;
            if (!ctx) return;

            if (ctx.type === 'category') {
              this.state.categories.splice(ctx.index, 1);
              this.currentView = 'main';
              this.currentCategoryIndex = null;
            } else if (ctx.type === 'routine') {
              this.state.routines.splice(ctx.index, 1);
            } else {
              this.state.categories[ctx.categoryIndex]?.items.splice(ctx.index, 1);
              this.currentView = 'category';
              this.currentCategoryIndex = ctx.categoryIndex;
            }

            this.pendingDelete = null;
            this.elements.confirmModal.close();
            this.queueSave();
            this.render();
            this.toast('Deleted', 'success');
          }

          moveTile({ type, index, categoryIndex, direction }) {
            if (type === 'category') {
              const target = index + direction;
              if (target < 0 || target >= this.state.categories.length) return;
              const arr = this.state.categories;
              [arr[index], arr[target]] = [arr[target], arr[index]];
              this.queueSave();
              this.render();
              return;
            }

            const items = this.state.categories[categoryIndex]?.items;
            if (!items) return;
            const target = index + direction;
            if (target < 0 || target >= items.length) return;
            [items[index], items[target]] = [items[target], items[index]];
            this.queueSave();
            this.render();
          }

          removeSentenceToken(index) {
            if (index < 0 || index >= this.state.sentence.length) return;
            this.state.sentence.splice(index, 1);
            this.renderSentence();
            this.queueSave();
          }

          speak(text) {
            if (!('speechSynthesis' in window)) {
              this.toast('Speech is not supported on this browser', 'error');
              return;
            }

            try {
              window.speechSynthesis.cancel();
            } catch (_) {}

            const utterance = new SpeechSynthesisUtterance(text);
            utterance.lang = 'en-GB';
            utterance.rate = this.state.settings.speechRate;

            const voices = window.speechSynthesis.getVoices();
            const voice =
              voices.find(v => v.lang === 'en-GB' && /female|susan|libby|hazel|google uk english female/i.test(v.name)) ||
              voices.find(v => v.lang === 'en-GB') ||
              voices[0];

            if (voice) utterance.voice = voice;
            window.speechSynthesis.speak(utterance);
          }

          speakSentence() {
            if (!this.state.sentence.length) {
              this.toast('Sentence is empty', 'info');
              return;
            }
            this.speak(this.state.sentence.map(x => x.name).join(' '));
          }

          undoSentence() {
            if (!this.state.sentence.length) return;
            this.state.sentence.pop();
            this.renderSentence();
            this.queueSave();
          }

          clearSentence() {
            this.state.sentence = [];
            this.renderSentence();
            this.queueSave();
          }

          exportJsonFile() {
            const blob = new Blob([JSON.stringify(this.state, null, 2)], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = 'dotty-learning-tool-backup.json';
            document.body.appendChild(a);
            a.click();
            a.remove();
            URL.revokeObjectURL(url);
            this.toast('Backup exported', 'success');
          }

          async importJsonFile(e) {
            const file = e.target.files?.[0];
            if (!file) return;

            try {
              const text = await file.text();
              const parsed = JSON.parse(text);
              this.state = this.normaliseData(parsed);
              this.showRoutines = !!this.state.ui?.showRoutines;
              this.applySettingsClasses();
              this.queueSave();
              this.render();
              this.toast('Backup loaded', 'success');
            } catch (err) {
              this.toast('Invalid JSON file', 'error');
            } finally {
              e.target.value = '';
            }
          }

          escape(str) {
            return String(str)
              .replace(/&/g, '&amp;')
              .replace(/</g, '&lt;')
              .replace(/>/g, '&gt;')
              .replace(/"/g, '&quot;')
              .replace(/'/g, '&#039;');
          }
        }

        document.addEventListener('DOMContentLoaded', () => {
          const root = document.getElementById('dotty-app-root');
          if (!root) return;
          const app = new DottyTool(root);
          app.init();
          window.dottyTool = app;
        });
      })();
    </script>
    </p>



<p class="wp-block-paragraph">Supporting a child with additional needs can feel like navigating a world without a map — especially when it comes to communication. That’s why we’ve created a free, digital set of SEN visual and emoji learning cards, designed specifically for children with autism, global developmental delay (GDD), and speech and language difficulties. These tools aren’t just useful — they’re essential for families travelling, learning, or living life in full colour.</p>



<p class="wp-block-paragraph"><strong>What Are SEN Visual &amp; Emoji Learning Cards?</strong></p>



<p class="wp-block-paragraph">Our digital SEN cards are a modern take on classic communication tools. Think Picture Exchange Communication System (PECS) — but upgraded for the digital age. We’ve blended clear visuals with emojis children recognise from daily life, especially screens and devices they already love our Emoji Learning Tool.</p>



<p class="wp-block-paragraph">Each card represents a common need, emotion, action, or choice. From “I’m hungry” to “I need a break,” or from “I feel happy” to “I’m scared,” these digital cards give children a safe, friendly way to express themselves without needing words.</p>



<p class="wp-block-paragraph">They work brilliantly on phones, tablets, or touchscreen laptops — no printing required, no cutting out, no fuss. Just tap, show, and connect.</p>



<p class="wp-block-paragraph"><strong>Why Use Emoji-Based SEN Tools?</strong></p>



<p class="wp-block-paragraph">Emoji learning taps into how children with SEN already engage with the world. Emojis are:</p>



<ul class="wp-block-list">
<li>Visually consistent across platforms</li>



<li>Emotionally expressive</li>



<li>Familiar from messaging apps and games</li>



<li>Easy to understand, even for non-readers</li>
</ul>



<p class="wp-block-paragraph">For neurodivergent children, especially those on the autism spectrum, consistency and clarity are everything. Emojis offer both. That’s why we’ve included emoji options on many of the cards — side-by-side with custom visuals — to create context-rich tools that support comprehension and build emotional awareness.</p>



<p class="wp-block-paragraph">totally free!</p>



<p class="wp-block-paragraph">if your planning a trip try our free route planner!<br><a href="https://tantrummingtrailblazers.com/tools/make-my-drive-fun/">Make My Drive Fun, Share Road Trips, Attractions, Stops &amp; More!</a></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/">Digital Emoji Learning Tool SEN Visual Communication Cards </a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4083</post-id>	</item>
		<item>
		<title>Educational Escapes: Learning While Traveling</title>
		<link>https://tantrummingtrailblazers.com/learning/educational-escapes-learning-while-traveling/</link>
					<comments>https://tantrummingtrailblazers.com/learning/educational-escapes-learning-while-traveling/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Thu, 05 Sep 2024 14:24:00 +0000</pubDate>
				<category><![CDATA[Learning]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=1643</guid>

					<description><![CDATA[<p>Traveling with children isn&#8217;t just about creating fun memories; it&#8217;s an incredible opportunity to turn the world into a classroom. Educational travel can greatly&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/learning/educational-escapes-learning-while-traveling/">Educational Escapes: Learning While Traveling</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Flearning%2Feducational-escapes-learning-while-traveling%2F&amp;linkname=Educational%20Escapes%3A%20Learning%20While%20Traveling" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Flearning%2Feducational-escapes-learning-while-traveling%2F&amp;linkname=Educational%20Escapes%3A%20Learning%20While%20Traveling" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Flearning%2Feducational-escapes-learning-while-traveling%2F&amp;linkname=Educational%20Escapes%3A%20Learning%20While%20Traveling" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Ftantrummingtrailblazers.com%2Flearning%2Feducational-escapes-learning-while-traveling%2F&#038;title=Educational%20Escapes%3A%20Learning%20While%20Traveling" data-a2a-url="https://tantrummingtrailblazers.com/learning/educational-escapes-learning-while-traveling/" data-a2a-title="Educational Escapes: Learning While Traveling"></a></p>
<p class="wp-block-paragraph">Traveling with children isn&#8217;t just about creating fun memories; it&#8217;s an incredible opportunity to turn the world into a classroom. Educational travel can greatly benefit children by providing hands-on learning experiences, exposing them to new cultures, and sparking their curiosity about the world. Whether you’re exploring close to home or in a new country, stepping beyond the poolside can lead to enriching adventures that combine fun with education.</p>



<h4 class="wp-block-heading">Nature Walks: Discovering the Great Outdoors</h4>



<p class="wp-block-paragraph"><strong>Local Parks and Trails:</strong> Even in your hometown, local parks and nature trails are excellent places for educational outings. Encourage children to observe different plant species, insects, and animals they encounter. Equip them with a simple field guide or a mobile app to help identify flora and fauna. Nature scavenger hunts can make these walks more engaging, prompting kids to find specific leaves, flowers, or bird feathers.</p>



<p class="wp-block-paragraph"><strong>National and State Parks:</strong> While traveling, national and state parks offer an expanded experience with diverse ecosystems and wildlife. Many parks provide ranger-led programs and educational workshops. Participate in Junior Ranger programs, where children complete activities to earn badges. These programs teach kids about conservation, ecology, and the importance of protecting natural habitats.</p>



<h4 class="wp-block-heading">Exploring Local History and Culture: Day-to-Day Learning</h4>



<p class="wp-block-paragraph"><strong>Historical Landmarks and Museums:</strong> Visiting local historical landmarks and museums can be both educational and inspiring. Many museums offer interactive exhibits, workshops, and storytelling sessions tailored for children. Encourage kids to ask questions and participate in activities. Even small local museums often have fascinating displays that can spark a child’s interest in history and culture.</p>



<p class="wp-block-paragraph"><strong>Cultural Festivals and Community Events:</strong> Participating in local cultural festivals and community events allows children to learn about different traditions and customs. These events often feature music, dance, crafts, and food that provide immersive learning experiences. Encourage children to engage with performers and artisans to learn more about their cultural practices.</p>



<h4 class="wp-block-heading">Everyday Learning: Turning Daily Activities into Educational Adventures</h4>



<p class="wp-block-paragraph"><strong>Gardening and Urban Farming:</strong> Involving children in gardening or visiting urban farms teaches them about plant biology, ecology, and sustainability. Kids can learn about different plant species, the growth cycle, and the importance of caring for the environment. Activities like planting seeds, composting, and harvesting vegetables provide practical, hands-on experiences.</p>



<p class="wp-block-paragraph"><strong>Cooking and Culinary Exploration:</strong> Cooking with children can be a fun way to teach them about nutrition, math, and cultural diversity. Involve kids in meal planning, shopping for ingredients, and preparing dishes. Discuss the origins of different foods and try recipes from various cultures to broaden their culinary horizons. Cooking together also fosters important life skills and healthy eating habits.</p>



<h4 class="wp-block-heading">Interactive Workshops and Classes: Hands-On Learning</h4>



<p class="wp-block-paragraph"><strong>Science and Craft Workshops:</strong> Look for local workshops and classes that focus on science, arts, and crafts. Science centres, community centres, and libraries often offer programs where children can engage in experiments, build models, or create art projects. These hands-on activities make learning fun and tangible.</p>



<p class="wp-block-paragraph"><strong>Outdoor Adventure Programs:</strong> Outdoor adventure programs, such as nature camps or wildlife conservation projects, provide immersive educational experiences. Activities like bird watching, tracking animals, and learning survival skills teach children about ecology, teamwork, and problem-solving. These programs often include guided hikes, wildlife observations, and ecological projects that deepen kids&#8217; connection to nature.</p>



<h4 class="wp-block-heading">Tips and Advice:</h4>



<ul class="wp-block-list">
<li><strong>Plan Ahead:</strong> Research local educational opportunities and events in advance to make the most of your outings.</li>



<li><strong>Engage with Guides:</strong> Encourage children to ask questions and interact with guides, educators, and local experts during activities.</li>



<li><strong>Balance Learning with Fun:</strong> Mix educational activities with free play and relaxation to keep the experience enjoyable for kids.</li>



<li><strong>Pack Essentials:</strong> Bring along notebooks or journals for children to document their experiences and reflections.</li>
</ul>



<h4 class="wp-block-heading">FAQs:</h4>



<p class="wp-block-paragraph"><strong>Q: Are these educational activities suitable for all age groups?</strong> A: Yes, most activities can be adapted to suit different ages and learning levels, from toddlers to teenagers.</p>



<p class="wp-block-paragraph"><strong>Q: How can I make sure my child stays engaged during educational outings?</strong> A: Choose interactive activities that involve hands-on learning and allow children to explore and discover at their own pace.</p>



<p class="wp-block-paragraph"><strong>Q: What should we bring on educational outings?</strong> A: Pack comfortable clothing, a reusable water bottle, a notebook and pen for taking notes, and any specific items required for workshops or activities.</p>



<h4 class="wp-block-heading">Useful Resources and Links:</h4>



<ul class="wp-block-list">
<li><a href="https://www.nwf.org/">National Wildlife Federation</a></li>



<li><a>Smithsonian Education</a></li>



<li><a href="https://www.audubon.org/">Audubon Society</a></li>



<li><a>Local Parks and Recreation Departments</a></li>



<li><a href="https://www.ilovelibraries.org/">Community Centres and Libraries</a></li>
</ul>



<p class="wp-block-paragraph">Educational travel offers families the chance to learn and grow together while exploring new places. From the scientific wonders at interactive workshops to the natural beauty of local parks and the rich history of cultural sites, these educational escapes make learning an adventure. By choosing activities that offer engaging and hands-on experiences, families can create meaningful memories that inspire a lifelong love of learning.</p>



<p class="wp-block-paragraph"><strong>Have you been on an educational outing with your family? </strong>Share your experiences and favourite learning activities in the comments below! If you have any questions about planning your next educational escape, feel free to ask. Let&#8217;s inspire each other to make travel both fun and educational for our children.</p>
<p>The post <a href="https://tantrummingtrailblazers.com/learning/educational-escapes-learning-while-traveling/">Educational Escapes: Learning While Traveling</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/learning/educational-escapes-learning-while-traveling/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1643</post-id>	</item>
		<item>
		<title>World Schooling 📚</title>
		<link>https://tantrummingtrailblazers.com/world-schooling/</link>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Wed, 14 Feb 2024 22:05:50 +0000</pubDate>
				<category><![CDATA[Travel Tips]]></category>
		<category><![CDATA[Cultural]]></category>
		<category><![CDATA[Experiential]]></category>
		<category><![CDATA[Immersion]]></category>
		<category><![CDATA[Learning]]></category>
		<category><![CDATA[Personalized]]></category>
		<category><![CDATA[Schooling]]></category>
		<category><![CDATA[World]]></category>
		<guid isPermaLink="false">http://cashbak.uk/Blog/?page_id=211</guid>

					<description><![CDATA[<p>Family travel guide World schooling. Real life. Big lessons. World schooling is the idea that travel can become part of a child’s education. Not&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/world-schooling/">World Schooling 📚</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><a class="a2a_button_facebook" href="https://www.addtoany.com/add_to/facebook?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Fworld-schooling%2F&amp;linkname=World%20Schooling%20%F0%9F%93%9A" title="Facebook" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_twitter" href="https://www.addtoany.com/add_to/twitter?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Fworld-schooling%2F&amp;linkname=World%20Schooling%20%F0%9F%93%9A" title="Twitter" rel="nofollow noopener" target="_blank"></a><a class="a2a_button_email" href="https://www.addtoany.com/add_to/email?linkurl=https%3A%2F%2Ftantrummingtrailblazers.com%2Fworld-schooling%2F&amp;linkname=World%20Schooling%20%F0%9F%93%9A" title="Email" rel="nofollow noopener" target="_blank"></a><a class="a2a_dd addtoany_share_save addtoany_share" href="https://www.addtoany.com/share#url=https%3A%2F%2Ftantrummingtrailblazers.com%2Fworld-schooling%2F&#038;title=World%20Schooling%20%F0%9F%93%9A" data-a2a-url="https://tantrummingtrailblazers.com/world-schooling/" data-a2a-title="World Schooling &#x1f4da;"></a></p>
<section class="ttb-worldschool-guide" aria-labelledby="ttb-worldschool-title">
  <style>
    .ttb-worldschool-guide {
      --ttb-ink: #1d1712;
      --ttb-soft-ink: #5f554d;
      --ttb-muted: #8d8176;
      --ttb-cream: #f8f3e8;
      --ttb-paper: #fffdf8;
      --ttb-blue: #e8f2f7;
      --ttb-rust: #c84f1a;
      --ttb-orange: #ff7a32;
      --ttb-border: rgba(29, 23, 18, 0.12);
      --ttb-shadow: 0 18px 45px rgba(29, 23, 18, 0.10);

      background: var(--ttb-cream);
      color: var(--ttb-ink);
      font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
      line-height: 1.65;
      overflow: hidden;
    }

    .ttb-worldschool-guide * {
      box-sizing: border-box;
    }

    .ttb-worldschool-wrap {
      max-width: 1180px;
      margin: 0 auto;
      padding: clamp(28px, 5vw, 76px) clamp(18px, 4vw, 38px);
    }

    .ttb-worldschool-hero {
      position: relative;
      display: grid;
      grid-template-columns: minmax(0, 1.15fr) minmax(280px, 0.85fr);
      gap: clamp(22px, 4vw, 42px);
      align-items: end;
      padding: clamp(28px, 5vw, 62px);
      border-radius: 34px;
      background:
        radial-gradient(circle at top right, rgba(255, 122, 50, 0.26), transparent 34%),
        radial-gradient(circle at bottom left, rgba(232, 242, 247, 0.16), transparent 36%),
        linear-gradient(135deg, #1d1712 0%, #35251e 58%, #c84f1a 145%);
      box-shadow: var(--ttb-shadow);
      overflow: hidden;
      isolation: isolate;
    }

    .ttb-worldschool-hero::before {
      content: "";
      position: absolute;
      inset: 0;
      background-image:
        linear-gradient(rgba(255,255,255,0.04) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px);
      background-size: 34px 34px;
      opacity: 0.52;
      z-index: -1;
    }

    .ttb-worldschool-kicker {
      margin: 0 0 14px;
      color: rgba(255,255,255,0.76);
      font-size: 0.78rem;
      font-weight: 900;
      letter-spacing: 0.32em;
      text-transform: uppercase;
    }

    .ttb-worldschool-title {
      margin: 0;
      max-width: 790px;
      color: #fff;
      font-size: clamp(3rem, 9vw, 7.4rem);
      line-height: 0.86;
      letter-spacing: -0.07em;
      font-weight: 950;
    }

    .ttb-worldschool-title span {
      display: block;
    }

    .ttb-script {
      display: block;
      margin-top: 14px;
      color: #f2d7c5;
      font-family: Georgia, "Times New Roman", serif;
      font-size: clamp(1.55rem, 4vw, 3.1rem);
      font-style: italic;
      font-weight: 400;
      letter-spacing: 0;
      line-height: 1.08;
    }

    .ttb-worldschool-intro {
      margin: clamp(18px, 3vw, 26px) 0 0;
      max-width: 800px;
      color: rgba(255,255,255,0.88);
      font-family: Georgia, "Times New Roman", serif;
      font-size: clamp(1.1rem, 2.2vw, 1.45rem);
      font-style: italic;
    }

    .ttb-hero-card {
      padding: 24px;
      border-radius: 26px;
      background: rgba(255, 253, 248, 0.95);
      backdrop-filter: blur(10px);
      box-shadow: 0 12px 35px rgba(0,0,0,0.18);
    }

    .ttb-hero-card h3 {
      margin: 0 0 10px;
      font-size: 1.35rem;
      line-height: 1.15;
    }

    .ttb-hero-card p {
      margin: 0;
      color: var(--ttb-soft-ink);
    }

    .ttb-pill-row {
      display: flex;
      flex-wrap: wrap;
      gap: 10px;
      margin-top: 18px;
    }

    .ttb-pill {
      display: inline-flex;
      align-items: center;
      gap: 7px;
      padding: 9px 12px;
      border: 1px solid var(--ttb-border);
      border-radius: 999px;
      background: #fff;
      color: var(--ttb-ink);
      font-size: 0.88rem;
      font-weight: 800;
      letter-spacing: 0.02em;
    }

    .ttb-section-head {
      margin: clamp(42px, 7vw, 72px) 0 clamp(22px, 4vw, 34px);
    }

    .ttb-section-kicker {
      margin: 0 0 10px;
      color: var(--ttb-rust);
      font-size: 0.78rem;
      font-weight: 900;
      letter-spacing: 0.32em;
      text-transform: uppercase;
    }

    .ttb-section-head h2 {
      margin: 0;
      font-size: clamp(2.3rem, 7vw, 5.4rem);
      line-height: 0.9;
      letter-spacing: -0.06em;
      font-weight: 950;
    }

    .ttb-section-head .ttb-script-dark {
      display: block;
      color: var(--ttb-rust);
      font-family: Georgia, "Times New Roman", serif;
      font-size: clamp(1.55rem, 4vw, 3.1rem);
      font-style: italic;
      font-weight: 400;
      letter-spacing: 0;
      line-height: 1.05;
    }

    .ttb-lede {
      max-width: 880px;
      margin: 18px 0 0;
      color: var(--ttb-soft-ink);
      font-size: clamp(1rem, 2vw, 1.18rem);
    }

    .ttb-card-grid {
      display: grid;
      grid-template-columns: repeat(3, minmax(0, 1fr));
      gap: 18px;
    }

    .ttb-two-col {
      display: grid;
      grid-template-columns: repeat(2, minmax(0, 1fr));
      gap: 18px;
    }

    .ttb-info-card {
      position: relative;
      min-height: 100%;
      padding: 24px;
      border: 1px solid var(--ttb-border);
      border-radius: 26px;
      background: var(--ttb-paper);
      box-shadow: 0 10px 26px rgba(29, 23, 18, 0.06);
    }

    .ttb-info-card::before {
      content: "";
      position: absolute;
      inset: 0 auto 0 0;
      width: 5px;
      border-radius: 26px 0 0 26px;
      background: var(--ttb-rust);
      opacity: 0.88;
    }

    .ttb-icon {
      width: 46px;
      height: 46px;
      display: grid;
      place-items: center;
      margin-bottom: 16px;
      border-radius: 16px;
      background: var(--ttb-blue);
      font-size: 1.45rem;
    }

    .ttb-info-card h3 {
      margin: 0 0 10px;
      font-size: 1.32rem;
      line-height: 1.2;
      letter-spacing: -0.02em;
    }

    .ttb-info-card p {
      margin: 0;
      color: var(--ttb-soft-ink);
    }

    .ttb-mini-list {
      margin: 14px 0 0;
      padding-left: 1.1rem;
      color: var(--ttb-soft-ink);
    }

    .ttb-mini-list li {
      margin: 7px 0;
    }

    .ttb-wide-panel {
      display: grid;
      grid-template-columns: minmax(0, 0.78fr) minmax(0, 1.22fr);
      gap: clamp(18px, 3vw, 34px);
      align-items: start;
      margin-top: 18px;
      padding: clamp(24px, 4vw, 40px);
      border-radius: 34px;
      background: #1d1712;
      color: #fff;
      box-shadow: var(--ttb-shadow);
    }

    .ttb-wide-panel h2 {
      margin: 0;
      font-size: clamp(2.2rem, 6vw, 4.8rem);
      line-height: 0.9;
      letter-spacing: -0.06em;
      font-weight: 950;
    }

    .ttb-wide-panel p {
      margin: 14px 0 0;
      color: rgba(255,255,255,0.78);
    }

    .ttb-checklist {
      display: grid;
      gap: 12px;
      margin: 0;
      padding: 0;
      list-style: none;
    }

    .ttb-checklist li {
      display: grid;
      grid-template-columns: auto 1fr;
      gap: 12px;
      align-items: start;
      padding: 15px 16px;
      border: 1px solid rgba(255,255,255,0.12);
      border-radius: 18px;
      background: rgba(255,255,255,0.07);
      color: rgba(255,255,255,0.9);
    }

    .ttb-check {
      display: grid;
      place-items: center;
      width: 24px;
      height: 24px;
      border-radius: 999px;
      background: var(--ttb-orange);
      color: #1d1712;
      font-weight: 950;
      line-height: 1;
    }

    .ttb-note {
      margin-top: 18px;
      padding: 22px;
      border-left: 6px solid var(--ttb-orange);
      border-radius: 22px;
      background: #fff4ea;
      color: var(--ttb-soft-ink);
    }

    .ttb-note strong {
      color: var(--ttb-ink);
    }

    .ttb-cta {
      margin-top: clamp(36px, 6vw, 62px);
      padding: clamp(28px, 5vw, 54px);
      border-radius: 34px;
      background:
        radial-gradient(circle at top left, rgba(255,122,50,0.22), transparent 34%),
        linear-gradient(135deg, #fffdf8, #eef6f9);
      border: 1px solid var(--ttb-border);
      box-shadow: var(--ttb-shadow);
      text-align: center;
    }

    .ttb-cta h2 {
      margin: 0;
      font-size: clamp(2.4rem, 7vw, 5.6rem);
      line-height: 0.88;
      letter-spacing: -0.07em;
      font-weight: 950;
    }

    .ttb-cta p {
      max-width: 760px;
      margin: 18px auto 0;
      color: var(--ttb-soft-ink);
      font-size: 1.08rem;
    }

    .ttb-button-row {
      display: flex;
      justify-content: center;
      flex-wrap: wrap;
      gap: 12px;
      margin-top: 24px;
    }

    .ttb-button {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      min-height: 48px;
      padding: 13px 20px;
      border-radius: 999px;
      font-weight: 900;
      text-decoration: none;
      letter-spacing: 0.03em;
      transition: transform 0.2s ease, box-shadow 0.2s ease;
    }

    .ttb-button:hover {
      transform: translateY(-2px);
    }

    .ttb-button-primary {
      background: var(--ttb-rust);
      color: #fff;
      box-shadow: 0 12px 26px rgba(200,79,26,0.22);
    }

    .ttb-button-secondary {
      border: 1px solid var(--ttb-border);
      background: #fff;
      color: var(--ttb-ink);
    }

    .ttb-small-print {
      margin-top: 18px;
      color: var(--ttb-muted);
      font-size: 0.92rem;
    }

    @media (max-width: 900px) {
      .ttb-worldschool-hero,
      .ttb-wide-panel {
        grid-template-columns: 1fr;
      }

      .ttb-card-grid,
      .ttb-two-col {
        grid-template-columns: 1fr;
      }
    }

    @media (max-width: 560px) {
      .ttb-worldschool-wrap {
        padding-left: 16px;
        padding-right: 16px;
      }

      .ttb-worldschool-hero {
        border-radius: 26px;
        padding: 26px 20px;
      }

      .ttb-worldschool-title {
        font-size: clamp(3.1rem, 16vw, 4.8rem);
      }

      .ttb-worldschool-intro {
        font-size: 1.05rem;
      }

      .ttb-hero-card,
      .ttb-info-card,
      .ttb-wide-panel,
      .ttb-cta {
        border-radius: 24px;
      }

      .ttb-section-head h2,
      .ttb-wide-panel h2,
      .ttb-cta h2 {
        font-size: clamp(2.5rem, 13vw, 4rem);
      }

      .ttb-button {
        width: 100%;
      }
    }
  </style>

  <div class="ttb-worldschool-wrap">
    <header class="ttb-worldschool-hero">
      <div>
        <p class="ttb-worldschool-kicker">Family travel guide</p>
        <h1 id="ttb-worldschool-title" class="ttb-worldschool-title">
          <span>World schooling.</span>
          <span>Real life.</span>
          <span>Big lessons.</span>
        </h1>
        <p class="ttb-worldschool-intro">
          World schooling is the idea that travel can become part of a child’s education.
          Not every lesson needs a desk, a worksheet or a bell. Sometimes history is a temple,
          science is a rock pool, geography is a train map, and resilience is getting through
          a travel day without everyone fully turning feral.
        </p>
      </div>

      <aside class="ttb-hero-card" aria-label="World schooling summary">
        <h3>What is world schooling?</h3>
        <p>
          It is a flexible approach to learning where travel, culture, nature, museums,
          local people, online resources and everyday experiences all become part of the curriculum.
          It can sit alongside home education, remote learning, school breaks or long-term travel.
        </p>
        <div class="ttb-pill-row">
          <span class="ttb-pill"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f30d.png" alt="🌍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Culture</span>
          <span class="ttb-pill"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4da.png" alt="📚" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Learning</span>
          <span class="ttb-pill"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f9ed.png" alt="🧭" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Curiosity</span>
          <span class="ttb-pill"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f9d2.png" alt="🧒" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Family life</span>
        </div>
      </aside>
    </header>

    <section class="ttb-section-head">
      <p class="ttb-section-kicker">The big idea</p>
      <h2>
        The world becomes the classroom.
        <span class="ttb-script-dark">But snacks still matter.</span>
      </h2>
      <p class="ttb-lede">
        World schooling works best when it is intentional but not rigid. You are not trying to recreate
        a school timetable in a hotel room. You are using real places, real problems and real curiosity
        to help children learn in a way that feels alive.
      </p>
    </section>

    <div class="ttb-card-grid">
      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3db.png" alt="🏛" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>History you can stand inside</h3>
        <p>
          Ancient ruins, castles, temples, museums, memorials and old cities can make history feel real.
          Children remember places better when they have walked through them, climbed the steps or asked
          awkward questions in the gift shop.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f33f.png" alt="🌿" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Science in the wild</h3>
        <p>
          Beaches, mountains, rainforests, deserts, volcanoes, animals, weather and ecosystems all create
          natural science lessons. The world is full of experiments, although some of them involve mud,
          suspicious insects and wet shoes.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f5fa.png" alt="🗺" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Geography with consequences</h3>
        <p>
          Maps, currencies, borders, climates, time zones, transport routes and food systems suddenly
          become useful. Geography stops being abstract when you are trying to find the station before
          your train disappears into the distance.
        </p>
      </article>
    </div>

    <section class="ttb-section-head">
      <p class="ttb-section-kicker">Why families try it</p>
      <h2>
        It builds more than knowledge.
        <span class="ttb-script-dark">It builds humans.</span>
      </h2>
      <p class="ttb-lede">
        The biggest benefit is not that children learn a few facts about a country. It is that they learn
        how to notice, ask, adapt, communicate and make sense of unfamiliar situations.
      </p>
    </section>

    <div class="ttb-two-col">
      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f91d.png" alt="🤝" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Cultural understanding</h3>
        <p>
          Meeting different people, hearing different languages, eating different food and seeing different
          ways of living can help children build empathy and perspective. It turns “other places” into
          actual humans, streets, homes and stories.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f9e0.png" alt="🧠" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Interest-led learning</h3>
        <p>
          If a child becomes obsessed with volcanoes, trains, wildlife, castles, food, space, animation or
          mythology, you can follow the thread. That curiosity can pull in reading, writing, maths, science,
          art and history without it feeling like a forced worksheet ambush.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4ac.png" alt="💬" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Communication skills</h3>
        <p>
          Travel gives children endless chances to ask questions, read signs, order food, listen to accents,
          learn greetings and understand body language. Even shy children can grow through tiny repeated
          moments of confidence.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f9e9.png" alt="🧩" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Adaptability and resilience</h3>
        <p>
          Plans change. Buses vanish. Food is weird. Shoes get wet. Attractions close. These are not failures.
          They are small life lessons in problem-solving, patience and flexibility, with occasional parental
          muttering in the background.
        </p>
      </article>
    </div>

    <section class="ttb-section-head">
      <p class="ttb-section-kicker">Getting started</p>
      <h2>
        Give it structure.
        <span class="ttb-script-dark">Not a straitjacket.</span>
      </h2>
      <p class="ttb-lede">
        The easiest way to begin is to create a light framework. Keep the basics ticking along, then let your
        destinations add the colour. Reading, writing and maths still matter, but they do not need to swallow
        the whole adventure.
      </p>
    </section>

    <div class="ttb-card-grid">
      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3af.png" alt="🎯" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Set simple learning goals</h3>
        <p>
          Pick a few broad aims before you travel. For example: keep reading daily, practise writing through
          journals, use real-life maths, learn about each country, and explore one topic your child genuinely
          cares about.
        </p>
        <ul class="ttb-mini-list">
          <li>Daily reading or audiobooks.</li>
          <li>Travel journal or photo captions.</li>
          <li>Practical maths using money, time, distance and cooking.</li>
        </ul>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4cd.png" alt="📍" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Choose destinations with purpose</h3>
        <p>
          A destination can support learning naturally. Ancient cities are brilliant for history.
          National parks are perfect for nature and geography. Big cities can teach transport, culture,
          language, art and how not to stand in everyone’s way on an escalator.
        </p>
        <ul class="ttb-mini-list">
          <li>Museums and galleries.</li>
          <li>Historical sites and walking tours.</li>
          <li>Wildlife parks, beaches, mountains and nature reserves.</li>
        </ul>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4bb.png" alt="💻" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Use online tools lightly</h3>
        <p>
          Online platforms can help with consistency, especially for maths, reading, languages or formal
          curriculum gaps. The trick is balance. Screens can support the journey, but they should not become
          the entire journey.
        </p>
        <ul class="ttb-mini-list">
          <li>Offline downloads for travel days.</li>
          <li>Short focused sessions rather than long battles.</li>
          <li>Use apps to support, not replace, real-world learning.</li>
        </ul>
      </article>
    </div>

    <section class="ttb-section-head">
      <p class="ttb-section-kicker">Travel logistics</p>
      <h2>
        Slow travel helps.
        <span class="ttb-script-dark">So does decent Wi-Fi.</span>
      </h2>
      <p class="ttb-lede">
        World schooling gets harder when every day is a packing day. Staying longer in fewer places gives
        children time to settle, notice more, build routines and make connections beyond the tourist highlights.
      </p>
    </section>

    <div class="ttb-two-col">
      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f422.png" alt="🐢" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Stay longer when you can</h3>
        <p>
          Slow travel reduces stress and gives learning room to breathe. A week or a month in one place can
          teach more than rushing through ten cities with everyone tired, hot and suspicious of train platforms.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f3e1.png" alt="🏡" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Choose practical accommodation</h3>
        <p>
          Apartments, houses or longer-stay rentals can make learning easier. A table, kitchen, laundry,
          separate sleeping space and quiet corner can be more useful than a fancy lobby and a tiny hotel room.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4f6.png" alt="📶" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Check connectivity</h3>
        <p>
          Reliable internet matters if you use online learning, video calls, cloud documents or remote work.
          Do not trust “Wi-Fi included” blindly. That can mean anything from fibre broadband to a router
          powered by three sleepy hamsters.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f9d2.png" alt="🧒" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Build in social time</h3>
        <p>
          Local playgrounds, clubs, libraries, classes, meetups, sports, family groups and world schooling
          communities can help children meet others. Socialisation does not only happen in classrooms.
          It happens wherever children get chance to play, talk and belong.
        </p>
      </article>
    </div>

    <section class="ttb-section-head">
      <p class="ttb-section-kicker">The grown-up bits</p>
      <h2>
        Check the rules.
        <span class="ttb-script-dark">Then enjoy the adventure.</span>
      </h2>
      <p class="ttb-lede">
        World schooling can be wonderfully flexible, but families still need to understand their legal,
        educational and visa responsibilities. The rules depend on where you are from, where your child is
        registered, and how long you are away.
      </p>
    </section>

    <div class="ttb-wide-panel">
      <div>
        <h2>The sensible checklist</h2>
        <p>
          Before you go full “the world is our classroom”, do the admin. Boring, yes. Important, also yes.
          This is the paperwork bit that stops future-you getting ambushed by avoidable problems.
        </p>
      </div>

      <ul class="ttb-checklist">
        <li>
          <span class="ttb-check">✓</span>
          <span>Check home education or school attendance rules in your home country, state or local authority.</span>
        </li>
        <li>
          <span class="ttb-check">✓</span>
          <span>Speak to your child’s school if you are taking a short break during term time.</span>
        </li>
        <li>
          <span class="ttb-check">✓</span>
          <span>Understand visa limits if staying in one country for longer periods.</span>
        </li>
        <li>
          <span class="ttb-check">✓</span>
          <span>Keep records of learning, reading, writing, projects, trips and completed work.</span>
        </li>
        <li>
          <span class="ttb-check">✓</span>
          <span>Maintain a basic routine for literacy, numeracy and rest days.</span>
        </li>
        <li>
          <span class="ttb-check">✓</span>
          <span>Build a portfolio with photos, journals, tickets, maps, drawings, worksheets and project notes.</span>
        </li>
      </ul>
    </div>

    <section class="ttb-section-head">
      <p class="ttb-section-kicker">Make it stick</p>
      <h2>
        Document the journey.
        <span class="ttb-script-dark">Not just the pretty bits.</span>
      </h2>
      <p class="ttb-lede">
        The learning becomes stronger when children reflect on what they have seen. A five-minute journal,
        a photo diary, a voice note, a sketch or a short video can turn a busy travel day into something
        they actually process and remember.
      </p>
    </section>

    <div class="ttb-card-grid">
      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4d3.png" alt="📓" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Travel journals</h3>
        <p>
          Keep it simple. One thing they saw, one thing they learned, one thing that surprised them.
          Younger children can draw, dictate or stick in tickets and maps.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f4f7.png" alt="📷" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Photo projects</h3>
        <p>
          Give children small missions: doors, animals, signs, colours, transport, food, plants or patterns.
          It trains observation and gives them ownership of the journey.
        </p>
      </article>

      <article class="ttb-info-card">
        <div class="ttb-icon"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f5c2.png" alt="🗂" class="wp-smiley" style="height: 1em; max-height: 1em;" /></div>
        <h3>Learning portfolio</h3>
        <p>
          Save examples of work as you go. This can help track progress, support assessments where needed,
          and remind everyone that yes, learning happened, even on the day with the cancelled bus.
        </p>
      </article>
    </div>

    <div class="ttb-note">
      <strong>Quick reality check:</strong>
      world schooling is flexible, but it is not a legal loophole or a magic replacement for every formal
      education requirement. Check the rules that apply to your family, especially around school attendance,
      home education, visas, residency and assessments. This guide is practical family travel advice,
      not legal advice.
    </div>

    <section class="ttb-cta">
      <h2>Family. Chaos. Learning.</h2>
      <p>
        World schooling is not about having perfect lesson plans in perfect locations with perfectly behaved
        children. It is about helping kids stay curious while the world keeps giving them things to wonder about.
        Some days will be museums and magic. Some days will be laundry and maths on a train. Both count.
      </p>

      <div class="ttb-button-row">
        <a class="ttb-button ttb-button-primary" href="/travel-tips/">
          Read more family travel tips
        </a>
        <a class="ttb-button ttb-button-secondary" href="/asia/">
          Explore our journey
        </a>
      </div>

      <p class="ttb-small-print">
        Built from real family travel, real questions, and the discovery that children can learn almost anywhere,
        provided someone remembered the snacks.
      </p>
    </section>
  </div>
</section>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://tantrummingtrailblazers.com/world-schooling/">World Schooling 📚</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">211</post-id>	</item>
	</channel>
</rss>
