<?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>Sat, 19 Jul 2025 07:04:34 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9</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</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>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>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: "e0be59b60d",
        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>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><strong>What Are SEN Visual &amp; Emoji Learning Cards?</strong></p>



<p>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>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>They work brilliantly on phones, tablets, or touchscreen laptops — no printing required, no cutting out, no fuss. Just tap, show, and connect.</p>



<p><strong>Why Use Emoji-Based SEN Tools?</strong></p>



<p>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>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>totally free!</p>



<p>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></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</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>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><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><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><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><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><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><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><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><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><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><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><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>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><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>World schooling is a transformative educational approach where families use travel as a central component of their children&#8217;s</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><p>World schooling is a transformative educational approach where families use travel as a central component of their children&rsquo;s learning. By immersing children in different cultures, languages, and environments, they gain hands-on education that goes beyond traditional classroom experiences.</p>



<p>World schooling blends education with exploration, offering children experiential learning through real-world adventures. It allows for personalized, interest-driven education, tailored to each child&rsquo;s unique curiosity and the family&#8217;s travel experiences.</p>



<h3 class="wp-block-heading">The Benefits of World Schooling</h3>



<ul class="wp-block-list">
<li><strong>Cultural Immersion</strong><br>Children experience diverse cultures firsthand, fostering empathy, tolerance, and a broader worldview.</li>



<li><strong>Personalized Learning</strong><br>Education is tailored to each child&rsquo;s pace and interests, leading to more meaningful engagement and understanding.</li>



<li><strong>Life Skills</strong><br>Travel encourages families to solve problems and adapt, teaching children essential life skills like adaptability, communication, and resilience.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity">



<h3 class="wp-block-heading">Getting Started with World Schooling: Planning Your Educational Journey &#128218;</h3>



<p><strong>Planning and Preparation</strong></p>



<ul class="wp-block-list">
<li><strong>Set Educational Goals</strong>: Determine what you want your children to learn&mdash;whether it&#8217;s language skills, history, or science.</li>



<li><strong>Choose Destinations with Purpose</strong>: Select locations that align with your educational objectives. For history, visit places with rich landmarks; for natural sciences, explore destinations with diverse ecosystems.</li>
</ul>



<p><strong>Curriculum Development</strong></p>



<ul class="wp-block-list">
<li><strong>Leverage Local Resources</strong>: Use museums, historical sites, and nature reserves as part of your curriculum. Engage local experts to deepen your child&rsquo;s educational experience.</li>



<li><strong>Utilize Online Resources</strong>: Use online educational tools and platforms to maintain a consistent learning structure, no matter where you are.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity">



<h3 class="wp-block-heading">The Logistics of World Schooling: Travel, Accommodation, and Connectivity &#128708;</h3>



<p><strong>Travel Arrangements</strong></p>



<ul class="wp-block-list">
<li><strong>Opt for Slow Travel</strong>: Stay longer in fewer locations to enrich learning and reduce travel fatigue.</li>



<li><strong>Travel Off-Peak</strong>: Visit destinations during off-peak times to lower costs and create a more focused learning environment.</li>
</ul>



<p><strong>Accommodation</strong></p>



<ul class="wp-block-list">
<li><strong>Long-term Rentals</strong>: Choose apartments or houses that provide a stable environment for learning and daily routines.</li>



<li><strong>Community Engagement</strong>: Stay in locations where you can interact with locals or other world-schooling families for social opportunities and shared learning experiences.</li>
</ul>



<p><strong>Connectivity</strong></p>



<ul class="wp-block-list">
<li><strong>Reliable Internet</strong>: Ensure a stable connection to support online learning tools and stay connected with educational communities.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity">



<h3 class="wp-block-heading">Navigating Challenges: Legalities, Socialization, and Educational Compliance &#128679;</h3>



<p><strong>Understanding Legal Requirements</strong></p>



<ul class="wp-block-list">
<li><strong>Schooling Laws</strong>: Research home-schooling laws in your home country and how they apply while traveling.</li>



<li><strong>Visa and Residency</strong>: Be aware of visa requirements for long-term stays and ensure compliance.</li>
</ul>



<p><strong>Socialization and Community</strong></p>



<ul class="wp-block-list">
<li><strong>Local Activities</strong>: Encourage children to join local events and activities to build friendships and deepen cultural understanding.</li>



<li><strong>Online Communities</strong>: Connect with other world-schooling families through forums, social media groups, and meet-ups for support and networking.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity">



<h3 class="wp-block-heading">Enriching the World Schooling Experience: Extra Tips and Resources &#127775;</h3>



<p><strong>Documenting the Journey</strong></p>



<ul class="wp-block-list">
<li><strong>Travel Journal</strong>: Encourage children to document their travels through writing, photography, or video.</li>



<li><strong>Portfolios</strong>: Maintain a portfolio of their work for both tracking progress and educational assessments.</li>
</ul>



<p><strong>Continuous Learning and Adaptation</strong></p>



<ul class="wp-block-list">
<li><strong>Adapt Teaching Methods</strong>: Be ready to change your approach based on what works best for your child as they grow and their interests evolve.</li>
</ul>



<hr class="wp-block-separator has-alpha-channel-opacity">



<h3 class="wp-block-heading">Embracing the World Schooling Lifestyle &#127890;</h3>



<p>World schooling is not just an educational path&mdash;it&rsquo;s a lifestyle that encourages constant learning, growth, and discovery. By adopting this dynamic approach, you give your children a unique foundation of knowledge and skills that will last a lifetime.</p>



<hr class="wp-block-separator has-alpha-channel-opacity">



<h3 class="wp-block-heading">Ready to embark on this exciting educational journey? </h3>



<p>Follow our blog for more tips, and join our community for support and inspiration. Let&rsquo;s make your world-schooling adventure enriching and fulfilling!</p>



<p></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>
