<?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>AI Archives - Tantrumming Trailblazers</title>
	<atom:link href="https://tantrummingtrailblazers.com/category/ai/feed/" rel="self" type="application/rss+xml" />
	<link>https://tantrummingtrailblazers.com/category/ai/</link>
	<description>Where Travel and Tantrums Collide in Epic Journeys!</description>
	<lastBuildDate>Wed, 27 Aug 2025 03:07:58 +0000</lastBuildDate>
	<language>en-GB</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=7.0</generator>

<image>
	<url>https://tantrummingtrailblazers.com/wp-content/uploads/2024/06/cropped-DALL·E-2024-06-22-12.00.14-A-minimalist-logo-featuring-three-lines-and-three-colors.-The-design-includes-one-diagonal-line-from-the-bottom-left-to-the-top-right-in-bright-orange-32x32.webp</url>
	<title>AI Archives - Tantrumming Trailblazers</title>
	<link>https://tantrummingtrailblazers.com/category/ai/</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">229502464</site>	<item>
		<title>Prompt Hub for Family Travel</title>
		<link>https://tantrummingtrailblazers.com/ai/prompt-hub-for-family-travel/</link>
					<comments>https://tantrummingtrailblazers.com/ai/prompt-hub-for-family-travel/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Tue, 26 Aug 2025 11:15:46 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=5256</guid>

					<description><![CDATA[<p>AI Travel Prompts &#8211; Tantrumming Trailblazers AI for Travellers Prompt Hub for Family Travel We created the Prompt Hub as part of&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/prompt-hub-for-family-travel/">Prompt Hub for Family Travel</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%2Fprompt-hub-for-family-travel%2F&amp;linkname=Prompt%20Hub%20for%20Family%20Travel" 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%2Fprompt-hub-for-family-travel%2F&amp;linkname=Prompt%20Hub%20for%20Family%20Travel" 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%2Fprompt-hub-for-family-travel%2F&amp;linkname=Prompt%20Hub%20for%20Family%20Travel" 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%2Fprompt-hub-for-family-travel%2F&#038;title=Prompt%20Hub%20for%20Family%20Travel" data-a2a-url="https://tantrummingtrailblazers.com/ai/prompt-hub-for-family-travel/" data-a2a-title="Prompt Hub for Family Travel"></a></p>
<!DOCTYPE html>
<html lang="en-GB">
<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>AI Travel Prompts &#8211; Tantrumming Trailblazers</title>
  <meta name="description" content="Travel prompts built by Tantrumming Trailblazers. Opens the Travel tag on our Prompt Hub, hosted on our sister site HackTheSim." />
  <style>
    :root{
      --tt-bg:#ffffff; --tt-fg:#0f172a; --tt-muted:#475569;
      --tt-accent:#14b8a6; --tt-accent-dark:#0d9488;
      --tt-border:#e5e7eb; --tt-radius:12px; --tt-shadow:0 8px 24px rgba(0,0,0,.08);
    }
    body{margin:0;background:#fff;color:var(--tt-fg);font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;line-height:1.55;}
    a{color:inherit}

    /* Component shell */
    .tt-ai-hub .container{max-width:1100px;margin:0 auto;padding:24px 16px;}
    .tt-hero{background:var(--tt-bg);border:1px solid var(--tt-border);border-radius:var(--tt-radius);box-shadow:var(--tt-shadow);padding:28px;margin:20px 0 28px;}
    .tt-eyebrow{font-size:12px;letter-spacing:.12em;text-transform:uppercase;color:var(--tt-accent);font-weight:700;margin-bottom:8px;}
    .tt-hero h1{font-size:2rem;line-height:1.2;margin:0 0 10px;}
    .tt-hero p{color:var(--tt-muted);margin:0 0 14px;}

    .cta{display:inline-flex;align-items:center;justify-content:center;gap:10px;background:var(--tt-accent);color:#fff;border:0;padding:16px 24px;border-radius:12px;font-weight:800;text-decoration:none;font-size:1.05rem;}
    .cta:hover{background:var(--tt-accent-dark);}
    .cta svg{width:18px;height:18px;}
    .cta-block{display:block;width:100%;max-width:460px}

    .ownership{display:flex;gap:12px;align-items:center;margin-top:12px;background:#f8fafc;border:1px solid var(--tt-border);border-radius:12px;padding:10px 12px;}
    .lockup{display:flex;align-items:center;gap:10px;font-weight:800;}
    .lockup-mark{width:26px;height:26px;border-radius:6px;background:var(--tt-accent);color:#fff;display:grid;place-items:center;font-size:.9rem;box-shadow:inset 0 -2px 0 rgba(0,0,0,.08);}
    .ownership small{color:var(--tt-muted);}

    .tt-grid{display:grid;grid-template-columns:1.2fr .8fr;gap:24px;}
    @media (max-width:900px){.tt-grid{grid-template-columns:1fr;}}
    .card{background:var(--tt-bg);border:1px solid var(--tt-border);border-radius:var(--tt-radius);box-shadow:var(--tt-shadow);padding:22px;}
    .card h2{font-size:1.35rem;margin:0 0 10px;}
    .card p{color:var(--tt-muted);}
    .muted{color:var(--tt-muted);}

    .tt-list{margin:12px 0 0;padding:0;list-style:none;}
    .tt-list li{display:grid;grid-template-columns:22px 1fr;align-items:start;gap:10px;padding:8px 0;border-bottom:1px dashed var(--tt-border);}
    .tt-list li:last-child{border-bottom:0;}
    .tick{width:22px;height:22px;display:inline-grid;place-items:center;border:2px solid var(--tt-accent);border-radius:50%;}

    .code{background:#0f172a;color:#e2e8f0;border-radius:10px;padding:14px;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;overflow:auto;font-size:.9rem;}
    .small{font-size:.9rem;color:#64748b;}

    /* Expanders */
    details{border:1px solid var(--tt-border);border-radius:10px;background:#f8fafc;padding:12px;margin:10px 0;}
    details[open]{background:#f1f5f9;}
    summary{cursor:pointer;font-weight:700;}
    .summary-sub{display:block;font-weight:400;color:#64748b;margin-top:4px;}

    /* Copy buttons */
    .copybar{display:flex;justify-content:flex-end;margin-top:8px;}
    .copybtn{background:#0ea5e9;color:#fff;border:0;border-radius:8px;padding:6px 10px;font-weight:700;cursor:pointer;}
    .copybtn:hover{background:#0284c7;}

    /* Sidebar helpers */
    .pill{display:inline-block;border:1px solid var(--tt-accent);color:var(--tt-accent);background:#f0fdfa;border-radius:999px;padding:6px 12px;font-size:.9rem;font-weight:700}
  </style>
</head>
<body>

  <section class="tt-ai-hub" aria-label="AI for Travellers: Prompt Hub">
    <div class="container">
      <!-- Hero -->
      <header class="tt-hero">
        <div class="tt-eyebrow">AI for Travellers</div>
        <h1>Prompt Hub for Family Travel</h1>
        <p>We created the Prompt Hub as part of Tantrumming Trailblazers to make planning faster and calmer. It lives on our sister site HackTheSim, which we run. The button opens the Travel collection filtered by the travel tag.</p>

        <!-- Clear ownership-labelled CTA -->
        <p style="margin:0">
          <a class="cta cta-block"
             href="https://hackthesim.com/prompt-hub/?tags=travel"
             target="_blank"
             rel="noopener noreferrer"
             aria-label="Open our Travel prompts on HackTheSim in a new tab">
            <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f30d.png" alt="🌍" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Browse Travel Prompts
            <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path fill="currentColor" d="M14 3h7v7h-2V6.41l-9.29 9.3-1.42-1.42 9.3-9.29H14V3zM5 5h6v2H7v10h10v-4h2v6H5z"/></svg>
          </a>
        </p>

        <!-- Explicit ownership note -->
        <div class="ownership" role="note" aria-label="Ownership notice">
          <div class="lockup">
            <span class="lockup-mark" aria-hidden="true">TT</span>
            <span>Built by Tantrumming Trailblazers</span>
          </div>
          <small>Hosted on our sister site <strong>HackTheSim.com</strong>. Same team, same project.</small>
        </div>
      </header>

      <div class="tt-grid">
        <!-- Main column -->
        <article class="card" aria-label="Main content">
          <h2>What you will find in the Travel tag</h2>
          <ul class="tt-list">
            <li><span class="tick">✓</span><div><strong>Clarity day plans</strong> step by step mornings, afternoons, evenings with times and costs.</div></li>
            <li><span class="tick">✓</span><div><strong>Route optimisers</strong> ordered stop lists, fuel or charge notes, toilets, risk flags.</div></li>
            <li><span class="tick">✓</span><div><strong>Budget splitters</strong> daily tables for accommodation, food, transport, activities with a buffer.</div></li>
            <li><span class="tick">✓</span><div><strong>Accessibility and pace planners</strong> step free options, gradients, quiet breaks.</div></li>
            <li><span class="tick">✓</span><div><strong>Packing lists</strong> per person counts, buy there vs bring, cabin vs hold.</div></li>
            <li><span class="tick">✓</span><div><strong>Food finders</strong> allergy friendly options, phrases, backup snacks near base.</div></li>
            <li><span class="tick">✓</span><div><strong>Rainy day Plan B</strong> indoor itineraries with quick swap windows if the sky clears.</div></li>
          </ul>

          <h2>Quick examples you can copy</h2>

          <!-- 1 -->
          <details>
            <summary>Clarity day plan <span class="summary-sub">simple step by step with breaks and costs</span></summary>
            <div id="p1" class="code">Act as a clarity specialist known for simple explanations.
Reframe: [DESTINATION or ROUTE] for a family with [KIDS&#8217; AGES].
Make it step by step with travel times, break points, toilets, snack options, and rough costs.
Explain this as if talking to: [stressed parent planning late at night].
Output: TL;DR, Morning, Afternoon, Evening with one kid friendly stop each. Budget summary with totals.</div>
            <div class="copybar"><button class="copybtn" data-copy="p1">Copy prompt</button></div>
          </details>

          <!-- 2 -->
          <details>
            <summary>First principles route optimiser <span class="summary-sub">least faff, least backtracking</span></summary>
            <div id="p2" class="code">Act as a first principles planner.
Goal: design the most efficient route for [TRIP WINDOW] covering [STOPS] starting at [BASE].
Constraints: minimise backtracking, avoid rush hour [TIMES], keep daily travel under [HOURS], must include [MUST HAVES].
Output: ordered stop list with reasoning, segment table with km or mi, time, fuel or charge stops, toilets. Two alternates: fastest and most scenic.</div>
            <div class="copybar"><button class="copybtn" data-copy="p2">Copy prompt</button></div>
          </details>

          <!-- 3 -->
          <details>
            <summary>Family budget splitter <span class="summary-sub">numbers into neat buckets</span></summary>
            <div id="p3" class="code">Act as a travel budget optimiser.
Trip: [DESTINATION] for [N] days, party [ADULTS] adults, [KIDS] kids. Total budget [CURRENCY].
Output: daily table for accommodation, food, transport, activities, buffer. Three price tiers per category. Five savings levers with estimated impact.</div>
            <div class="copybar"><button class="copybtn" data-copy="p3">Copy prompt</button></div>
          </details>

          <!-- 4 -->
          <details>
            <summary>Accessibility and pace planner <span class="summary-sub">step counts, lifts, quiet time</span></summary>
            <div id="p4" class="code">Act as an accessibility and pace planner.
Context: [CITY], needs [eg buggy friendly, step free, sensory friendly], kids [AGES], pace [SLOW or MED or FAST].
Output: step free plan with lift locations, walking segments with step counts or gradients, transport boarding notes, quiet breaks every [HOURS].</div>
            <div class="copybar"><button class="copybtn" data-copy="p4">Copy prompt</button></div>
          </details>

          <!-- 5 -->
          <details>
            <summary>Smart packing list <span class="summary-sub">counts, cadence, cabin vs hold</span></summary>
            <div id="p5" class="code">Act as a packing strategist.
Trip: [DESTINATION], dates [DATES], weather [HOT or MILD or RAINY], activities [LIST], laundry every [N] days.
Output: per person checklist with counts, shared kit, buy there vs bring, cabin vs hold split. Quality check that weight stays under [KG] per bag.</div>
            <div class="copybar"><button class="copybtn" data-copy="p5">Copy prompt</button></div>
          </details>

          <!-- 6 -->
          <details>
            <summary>Food finder with allergy and kid filters <span class="summary-sub">safe dishes and scripts</span></summary>
            <div id="p6" class="code">Act as a family food scout.
Area: [NEIGHBOURHOOD] radius [KM]. Diet needs: [ALLERGIES or VEGETARIAN], kids [AGES], price [£ RANGE].
Output: six places with vibe, price, seating, play area, queue risk. One safe dish per place and phrases to explain the need. Backup snack plan near [ACCOM].</div>
            <div class="copybar"><button class="copybtn" data-copy="p6">Copy prompt</button></div>
          </details>

          <!-- 7 -->
          <details>
            <summary>Rainy day Plan B <span class="summary-sub">indoor options and quick swaps</span></summary>
            <div id="p7" class="code">Act as a rainy day planner.
Location: [CITY], kids [AGES], budget per day [CURRENCY], transport [ON FOOT or PUBLIC or CAR].
Output: three indoor itineraries ranked by energy level, each with time blocks, travel times, booking notes, snack stops. At home micro plan and escape windows if weather clears.</div>
            <div class="copybar"><button class="copybtn" data-copy="p7">Copy prompt</button></div>
          </details>

          <h2>How to use this in 30 seconds</h2>
          <ol class="muted">
            <li>Click the button above. You will land on our travel tag in Prompt Hub.</li>
            <li>Pick a card. Swap the bracketed bits for your family details.</li>
            <li>Run it in your favourite model. Ask for two variants and keep the one that sings.</li>
          </ol>

          <h2>Pro tips for better results</h2>
          <ul class="tt-list">
            <li><span class="tick">✓</span><div><strong>State kid ages</strong> to get realistic pace and breaks.</div></li>
            <li><span class="tick">✓</span><div><strong>Fix a finish time</strong> so evenings do not drift.</div></li>
            <li><span class="tick">✓</span><div><strong>Give a budget</strong> even a rough one unlocks better trade offs.</div></li>
            <li><span class="tick">✓</span><div><strong>Ask for checks</strong> steps under a limit, quiet spaces, shade, toilets.</div></li>
          </ul>

          <h2>Accuracy and safety notice</h2>
          <p class="small">Always double check opening hours, booking rules, visa or entry requirements, and transport changes. Treat outputs as a smart draft, then verify with official sources before you go.</p>
        </article>

        <!-- Sidebar -->
        <aside class="card" aria-label="Summary">
          <h2>TL;DR</h2>
          <ul class="tt-list" style="margin-top:6px;">
            <li><span class="tick">✓</span><div><strong>Owned by Tantrumming Trailblazers</strong></div></li>
            <li><span class="tick">✓</span><div><strong>Travel prompts</strong> filtered by the travel tag <span class="pill" aria-hidden="true">tag: travel</span></div></li>
            <li><span class="tick">✓</span><div><strong>One click</strong> opens on our sister site in a new tab</div></li>
            <li><span class="tick">✓</span><div><strong>Free</strong> and updated regularly</div></li>
          </ul>

          <h3 style="margin-top:16px;">FAQ</h3>
          <p class="small"><strong>Who built this?</strong> Tantrumming Trailblazers. Hosted on HackTheSim, which we also run.</p>
          <p class="small"><strong>Can I use it for non travel work?</strong> Yes. Use the full Prompt Hub for marketing, product and more.</p>
          <p class="small">Want to explore more than travel? <a href="https://hackthesim.com/prompt-hub/" target="_blank" rel="noopener">Browse the full Prompt Hub</a>.</p>
        </aside>
      </div>
    </div>
  </section>

  <!-- Copy-to-clipboard script -->
  <script>
    (function(){
      function copy(id, btn){
        var el = document.getElementById(id);
        if(!el) return;
        var text = el.innerText;
        navigator.clipboard.writeText(text).then(function(){
          var old = btn.textContent;
          btn.textContent = 'Copied';
          setTimeout(function(){ btn.textContent = old; }, 1400);
        }).catch(function(){
          alert('Copy failed. Please select and copy manually.');
        });
      }
      document.querySelectorAll('.copybtn').forEach(function(btn){
        btn.addEventListener('click', function(){
          copy(btn.getAttribute('data-copy'), btn);
        });
      });
    })();
  </script>

  <!-- FAQ schema for a little SEO love -->
  <script type="application/ld+json">
  {
    "@context": "https://schema.org",
    "@type": "FAQPage",
    "mainEntity": [
      {
        "@type": "Question",
        "name": "Who built the Prompt Hub?",
        "acceptedAnswer": { "@type": "Answer", "text": "Tantrumming Trailblazers created the Prompt Hub and host it on their sister site HackTheSim." }
      },
      {
        "@type": "Question",
        "name": "Is the Travel tag free to use?",
        "acceptedAnswer": { "@type": "Answer", "text": "Yes. You can browse and use the prompts for free." }
      },
      {
        "@type": "Question",
        "name": "Can I explore prompts beyond travel?",
        "acceptedAnswer": { "@type": "Answer", "text": "Yes. Visit the full Prompt Hub to see prompts for marketing, product, writing and more." }
      }
    ]
  }
  </script>
</body>
</html>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/prompt-hub-for-family-travel/">Prompt Hub for Family Travel</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/prompt-hub-for-family-travel/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">5256</post-id>	</item>
		<item>
		<title>Hub</title>
		<link>https://tantrummingtrailblazers.com/hub/</link>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Sun, 20 Apr 2025 05:40:05 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?page_id=4526</guid>

					<description><![CDATA[<p>Dennis Knight – Hub of Chaos Dennis Knight Enter the Eye. Primevil lives. Travel, tech &#038; legacy code. Tantrumming Trailblazers Family travel powered by AI, tech&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/hub/">Hub</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%2Fhub%2F&amp;linkname=Hub" 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%2Fhub%2F&amp;linkname=Hub" 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%2Fhub%2F&amp;linkname=Hub" 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%2Fhub%2F&#038;title=Hub" data-a2a-url="https://tantrummingtrailblazers.com/hub/" data-a2a-title="Hub"></a></p>
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Dennis Knight – Hub of Chaos</title>

  <!-- GOOGLE FONT -->
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600&#038;display=swap" rel="stylesheet">

  <style>
  /* ----------  DESIGN TOKENS  ---------- */
  :root{
    --bg-0:#0f1113;--bg-1:#1a1d21;--bg-2:#25292f;
    --txt-main:#e9edf2;--txt-muted:#9ea5b0;
    --accent:#00eaff;--accent-soft:rgba(0,255,255,.15);
    --radius:.75rem;--trans:.4s ease;--shadow:0 .6rem 1.4rem -.5rem var(--accent-soft);
  }
  @media(prefers-color-scheme:light){
    :root{
      --bg-0:#fafcfd;--bg-1:#ffffff;--bg-2:#f0f3f6;
      --txt-main:#20262e;--txt-muted:#5e6671;
      --accent:#00b7e6;--accent-soft:rgba(0,183,230,.15);
      --shadow:0 .6rem 1.2rem -.4rem var(--accent-soft);
    }
  }

  /* ----------  GLOBAL  ---------- */
  *,*::before,*::after{box-sizing:border-box}
  html{scroll-behavior:smooth}
  body{
    margin:0;font-family:"Inter",system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;
    background:var(--bg-0);color:var(--txt-main);line-height:1.55;-webkit-font-smoothing:antialiased;
  }
  img{display:block;max-width:100%}
  a{text-decoration:none;color:inherit}

  /* ----------  HEADER  ---------- */
  header{
    padding:4rem 1rem 3.2rem;text-align:center;
    background:linear-gradient(140deg,var(--bg-1),var(--bg-2) 70%);
    border-block-end:2px solid var(--accent);
  }
  header h1{
    margin:0;font-weight:600;
    font-size:clamp(2.3rem,5vw,3.6rem);
    color:var(--accent);text-shadow:0 0 .65rem var(--accent-soft);
  }
  header p{
    margin:.65rem 0 0;font-style:italic;
    font-size:clamp(1rem,2.4vw,1.25rem);color:var(--txt-muted);
  }

  /* ----------  LAYOUT  ---------- */
  .wrap{
    width:min(70rem,92%);margin-inline:auto;
    padding-block:3rem 4.5rem;
    display:grid;gap:2.4rem;
  }
  @media(min-width:56rem){
    .wrap{grid-template-columns:repeat(2,1fr)}
  }

  /* ----------  CARD / ACCORDION  ---------- */
  .card{
    border:1px solid color-mix(in srgb,var(--accent) 18%,transparent);
    border-radius:var(--radius);background:var(--bg-1);
    box-shadow:var(--shadow);overflow:hidden;
    transition:transform .3s ease;
  }
  .card:is(:hover,:focus-within){transform:translateY(-2px)}

  /*     === RESPONSIVE IMAGE WRAPPER ===     */
  .banner{
    display:block;overflow:hidden;aspect-ratio:16/9;
    background:var(--bg-2);               /* fallback colour */
    position:relative;
  }
  .banner::before{                         /* skeleton shimmer */
    content:"";position:absolute;inset:0;
    background:linear-gradient(90deg,transparent 0%,rgba(255,255,255,.08) 50%,transparent 100%);
    animation:sheen 1.6s infinite;
  }
  @keyframes sheen{to{transform:translateX(100%)}}

  .banner img{
    width:100%;height:100%;object-fit:cover;
    opacity:0;transition:opacity var(--trans);
  }
  .banner img.loaded{opacity:1}
  /* drop skeleton once image shows */
  .banner.loaded::before{display:none}

  /* ACCORDION */
  details{transition:max-height .5s ease}
  summary{
    list-style:none;
    display:flex;justify-content:space-between;align-items:center;
    cursor:pointer;padding:1rem 1.25rem;
    background:var(--bg-2);color:var(--accent);
    font-weight:600;font-size:1.25rem;
  }
  summary::-webkit-details-marker{display:none}
  summary::after{
    content:"▸";font-size:1.35rem;transition:transform .3s ease;
  }
  details[open] summary::after{transform:rotate(90deg)}

  /* summary line */
  .summary-line{padding:0 1.25rem 1rem;font-weight:600;color:var(--txt-muted)}

  /* panel */
  .panel{padding:0 1.25rem 1.25rem;max-width:66ch}
  .panel p{margin:.95rem 0 1.35rem;text-wrap:pretty}

  /* button */
  .btn{
    display:inline-block;font-weight:600;letter-spacing:.2px;
    padding:.75rem 1.9rem;border-radius:2rem;
    background:var(--accent);color:#000;
    box-shadow:0 0 1rem var(--accent-soft);
    transition:transform .25s,box-shadow .25s;
  }
  .btn:hover{transform:translateY(-3px) scale(1.05);box-shadow:0 0 1.6rem var(--accent)}
  .btn:focus-visible{outline:2px solid var(--accent);outline-offset:3px}

  /* ----------  FOOTER  ---------- */
  footer{
    text-align:center;padding:3rem 1rem 2.8rem;
    font-size:.9rem;color:var(--txt-muted);
    background:var(--bg-1);border-block-start:2px solid var(--accent);
  }
  @media(prefers-reduced-motion:reduce){
    *{animation:none !important;transition:none !important}
  }
  </style>
</head>
<body>

<header>
  <h1>Dennis Knight</h1>
  <p>Enter the Eye. Primevil lives. Travel, tech &#038; legacy code.</p>
</header>

<main class="wrap">

  <!-- ========== TEMPLATE ==========
       Each card uses:
       <picture class="banner"> → aspect‑ratio keeps shape while loading
       JavaScript down the bottom swaps .loaded class once the img fires `load`
  =================================-->

  <!-- TANTRUMMING TRAILBLAZERS -->
  <article class="card">
    <picture class="banner">
      <img decoding="async" loading="lazy"
           src="https://tantrummingtrailblazers.com/wp-content/uploads/2023/12/tantrumming-cover.jpg"
           alt="Tantrumming Trailblazers banner">
    </picture>
    <details>
      <summary>Tantrumming Trailblazers</summary>
      <p class="summary-line">Family travel powered by AI, tech hacks &#038; tiny adventurers.</p>
      <div class="panel">
        <p><strong>The mission:</strong> prove you can schlepp two kids, a pram and a ridiculous amount of luggage around the planet <em>without</em> blowing a gasket. We’re chronicling every meltdown, miracle and mind‑blowing discovery from Paris back‑streets to Patagonian glaciers.</p>

        <p>• <u>AI in the pram bay</u> — GPT prompts for itinerary juggling, on‑the‑spot stroller‑friendly café finders and packing bots that watch the weather so you don’t pack twelve jumpers for Cairns.<br>
           • <u>Sensory‑friendly scouting</u> — decibel readings for cafés, footpath texture ratings and crowd‑density overlays.<br>
           • <u>Gear lab</u> — savage, hands‑on reviews of carriers, noise‑cancelling headphones for ankle‑biters and the mythical “one‑hand‑open” travel cup.</p>

        <a class="btn" href="https://tantrummingtrailblazers.com" target="_blank" rel="noopener">Visit Site</a>
      </div>
    </details>
  </article>

  <!-- EXPLORE THANET -->
  <article class="card">
    <picture class="banner">
      <img decoding="async" loading="lazy"
           src="https://explorethanet.co.uk/wp-content/uploads/2023/10/thanet-banner.jpg"
           alt="Explore Thanet coastline banner">
    </picture>
    <details>
      <summary>Explore Thanet</summary>
      <p class="summary-line">From tidal magic to sea‑swept adventures — Kent the right way.</p>
      <div class="panel">
        <p>Thanet isn’t just another English shoreline. It’s chalk‑cliff mazes, rock pools older than most pubs and pirates’ tunnels you can still squeeze through. We built the most obsessive local guide going:</p>

        <p>• Hour‑by‑hour tide charts piped into live maps so you know exactly when that secret cove exists.<br>
           • Drone‑mapped cliff paths with gradient warnings for wheels &#038; weary knees.<br>
           • Fish‑and‑chip leaderboard (chips 40 %, fish 50 %, “vinegar swagger” 10 %).<br>
           • AR history mode: lift your phone, watch Turner’s canvases fade over the harbour — goosebumps.</p>

        <a class="btn" href="https://explorethanet.co.uk" target="_blank" rel="noopener">Visit Site</a>
      </div>
    </details>
  </article>

  <!-- PRIMEVIL GUILD -->
  <article class="card">
    <picture class="banner">
      <img decoding="async" loading="lazy"
           src="https://primevil.co.uk/wp-content/uploads/2024/03/primevil-logo-banner.png"
           alt="Primevil Guild banner">
    </picture>
    <details>
      <summary>Primevil Guild</summary>
      <p class="summary-line">Forged in darkness. Made in Mir. Sharpened in DAoC.</p>
      <div class="panel">
        <p>What started as a caffeine‑fuelled clan in <em>Legend of Mir 2</em> barrel‑rolled into <em>Dark Age of Camelot</em> relic raids and a two‑decade legacy.</p>

        <p>• Searchable archive of strats, relic‑steal diaries &#038; Ventrilo recordings.<br>
           • Frag videos remastered to 60 fps.<br>
           • Hall of Memes – every in‑joke preserved from Hawk’s 17th lv‑50 ranger to Gudd Arms whacking a keep door instead of the lord.<br>
           • 20‑year reunion calendar with scrims in <em>Pantheon</em> &#038; <em>New World</em>.</p>

        <a class="btn" href="https://primevil.co.uk" target="_blank" rel="noopener">Enter the Legacy</a>
      </div>
    </details>
  </article>

  <!-- BULLSHIT BINGO -->
  <article class="card">
    <picture class="banner">
      <img decoding="async" loading="lazy"
           src="https://bullshitbingo.co.uk/wp-content/uploads/2023/09/bingo-header.png"
           alt="Bullshit Bingo header art">
    </picture>
    <details>
      <summary>Bullshit Bingo</summary>
      <p class="summary-line">The ultimate multiplayer Zoom chaos card. Corporate war begins.</p>
      <div class="panel">
        <p>Every meeting, the buzzword‑grenades fly. Our weapon? <strong>Bullshit Bingo</strong>:</p>

        <p>Load a 5×5 grid, feed it the speaker’s audio stream and wait. “Circle back”? Boom, square lit. First full house sets off a browser‑wide confetti nuke — perfect ice‑breaker or HR nightmare.</p>

        <p>• 120+ jargon bombs rotating weekly.<br>
           • Works on Zoom, Teams, Meet.<br>
           • Stats page exposing the worst offenders.<br>
           • Optional HR‑safe mode (bleeps the B‑word).</p>

        <a class="btn" href="https://bullshitbingo.co.uk" target="_blank" rel="noopener">Play Now</a>
      </div>
    </details>
  </article>

  <!-- NFL UK FANS -->
  <article class="card">
    <picture class="banner">
      <img decoding="async" loading="lazy"
           src="https://nflukfans.co.uk/wp-content/uploads/2024/04/nfluk-banner.jpg"
           alt="NFL UK Fans banner">
    </picture>
    <details>
      <summary>NFL UK Fans</summary>
      <p class="summary-line">Team flair. Trash‑talk. Touchdowns. And AI Dan is coming.</p>
      <div class="panel">
        <p>Gridiron’s landed on UK soil and we’ve built the loudest bleachers outside the States.</p>

        <p>• Weekly matchup megathread with spoiler tags.<br>
           • Elo‑driven power rankings spiked with “banter delta”.<br>
           • @QuickSnap bot dumps every scoring drive into chat 45 sec after it happens.<br>
           • Coming ’25: <u>AI Dan</u> — scheme nerd, cap‑space accountant and ruthless fantasy adviser.</p>

        <a class="btn" href="https://nflukfans.co.uk" target="_blank" rel="noopener">Join the Huddle</a>
      </div>
    </details>
  </article>

</main>

<footer>
  Built by Dennis Knight – across galaxies, timelines &#038; twisted realms.
</footer>

<!-- ----------  FADE‑IN SCRIPT  ----------
     Adds .loaded to <img> once each banner finishes so CSS can drop the skeleton -->
<script>
  document.querySelectorAll('.banner img').forEach(img=>{
    if(img.complete){markLoaded(img);}else{img.addEventListener('load',()=>markLoaded(img));}
  });
  function markLoaded(img){
    img.classList.add('loaded');
    img.closest('.banner').classList.add('loaded');
  }
</script>

</body>
</html>
<p>The post <a href="https://tantrummingtrailblazers.com/hub/">Hub</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4526</post-id>	</item>
		<item>
		<title>Digital Emoji Learning Tool SEN Visual Communication Cards </title>
		<link>https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/</link>
					<comments>https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Thu, 13 Mar 2025 16:19:03 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[Customizable]]></category>
		<category><![CDATA[Educational]]></category>
		<category><![CDATA[Emoji-Based]]></category>
		<category><![CDATA[Interactive]]></category>
		<category><![CDATA[Learning]]></category>
		<category><![CDATA[Tool]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=4083</guid>

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



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



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



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



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



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



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



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



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



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



<li>Emotionally expressive</li>



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



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



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



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



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



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/">Digital Emoji Learning Tool SEN Visual Communication Cards </a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/sen-visual-communication-cards/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">4083</post-id>	</item>
		<item>
		<title>AI Packing List Generator, Thoughts Wanted!</title>
		<link>https://tantrummingtrailblazers.com/ai/ai-packing-list-generator-thoughts-wanted/</link>
					<comments>https://tantrummingtrailblazers.com/ai/ai-packing-list-generator-thoughts-wanted/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Thu, 16 Jan 2025 09:04:42 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/ai/ai-packing-list-generator-thoughts-wanted/</guid>

					<description><![CDATA[<p>Morning peeps, I have developed a AI-powered tool that creates custom packing lists tailored to your trip details, destination, activities, and even&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/ai-packing-list-generator-thoughts-wanted/">AI Packing List Generator, Thoughts Wanted!</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%2Fai-packing-list-generator-thoughts-wanted%2F&amp;linkname=AI%20Packing%20List%20Generator%2C%20Thoughts%20Wanted%21" 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%2Fai-packing-list-generator-thoughts-wanted%2F&amp;linkname=AI%20Packing%20List%20Generator%2C%20Thoughts%20Wanted%21" 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%2Fai-packing-list-generator-thoughts-wanted%2F&amp;linkname=AI%20Packing%20List%20Generator%2C%20Thoughts%20Wanted%21" 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%2Fai-packing-list-generator-thoughts-wanted%2F&#038;title=AI%20Packing%20List%20Generator%2C%20Thoughts%20Wanted%21" data-a2a-url="https://tantrummingtrailblazers.com/ai/ai-packing-list-generator-thoughts-wanted/" data-a2a-title="AI Packing List Generator, Thoughts Wanted!"></a></p>
<p class="wp-block-paragraph">Morning peeps, I have developed a AI-powered tool that creates custom packing lists tailored to your trip details, destination, activities, and even average weather. Alongside this, I’m building other AI travel tools to simplify planning and exploring for me and in effect everyone, which I will release over the next several weeks.</p>



<p class="wp-block-paragraph">Your feedback at this stage on the packing list generator would help shape this tool into something truly helpful!</p>



<p class="wp-block-paragraph">The packing list generator on this blog <a href="http://www.tantrummingtrailblazers.com/pack-like-a-pro">http://www.tantrummingtrailblazers.com/pack-like-a-pro</a>, and i would love to hear your thoughts:<code>• What’s one thing you always forget to pack? • Would features like “Print to PDF” or direct printing be useful? • Any ideas to make it even better?</code></p>



<p class="wp-block-paragraph">This is just the beginning, your input will help make travel planning smoother for everyone.</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/ai-packing-list-generator-thoughts-wanted/">AI Packing List Generator, Thoughts Wanted!</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/ai-packing-list-generator-thoughts-wanted/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3495</post-id>	</item>
		<item>
		<title>Turn Every Drive Into an Adventure!💡🚗</title>
		<link>https://tantrummingtrailblazers.com/ai/turn-every-drive-into-an-adventure%f0%9f%92%a1%f0%9f%9a%97/</link>
					<comments>https://tantrummingtrailblazers.com/ai/turn-every-drive-into-an-adventure%f0%9f%92%a1%f0%9f%9a%97/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Fri, 10 Jan 2025 20:06:22 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<category><![CDATA[travel]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/ai/turn-every-drive-into-an-adventure%f0%9f%92%a1%f0%9f%9a%97/</guid>

					<description><![CDATA[<p>Have you ever been on a long drive and wondered, “What cool things am I missing along the way?” Well, that’s where&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/turn-every-drive-into-an-adventure%f0%9f%92%a1%f0%9f%9a%97/">Turn Every Drive Into an Adventure!💡🚗</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%2Fturn-every-drive-into-an-adventure%25f0%259f%2592%25a1%25f0%259f%259a%2597%2F&amp;linkname=Turn%20Every%20Drive%20Into%20an%20Adventure%21%F0%9F%92%A1%F0%9F%9A%97" 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%2Fturn-every-drive-into-an-adventure%25f0%259f%2592%25a1%25f0%259f%259a%2597%2F&amp;linkname=Turn%20Every%20Drive%20Into%20an%20Adventure%21%F0%9F%92%A1%F0%9F%9A%97" 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%2Fturn-every-drive-into-an-adventure%25f0%259f%2592%25a1%25f0%259f%259a%2597%2F&amp;linkname=Turn%20Every%20Drive%20Into%20an%20Adventure%21%F0%9F%92%A1%F0%9F%9A%97" 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%2Fturn-every-drive-into-an-adventure%25f0%259f%2592%25a1%25f0%259f%259a%2597%2F&#038;title=Turn%20Every%20Drive%20Into%20an%20Adventure%21%F0%9F%92%A1%F0%9F%9A%97" data-a2a-url="https://tantrummingtrailblazers.com/ai/turn-every-drive-into-an-adventure%f0%9f%92%a1%f0%9f%9a%97/" data-a2a-title="Turn Every Drive Into an Adventure!&#x1f4a1;&#x1f697;"></a></p>
<p class="p1 wp-block-paragraph">Have you ever been on a long drive and wondered, “What cool things am I missing along the way?” Well, that’s where TantrummingTrailblazers custom “<a href="https://tantrummingtrailblazers.com/tools/make-my-drive-fun/">Make My Drive Fun</a>” comes in! Whether you’re after hidden gems, family-friendly pit stops, or the best coffee shops en route, we’ve got you covered.</p>



<p class="p1 wp-block-paragraph">With our <strong>FREE</strong> easy-to-use route planner, you can:</p>



<p class="p1 wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Find fun stops along your exact route.</p>



<p class="p1 wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Discover nearby attractions, rest stops, and scenic views.</p>



<p class="p1 wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Plan toilet breaks or grab a quick bite with the family.</p>



<p class="p1 wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2714.png" alt="✔" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Customise your search for what you want to see!</p>



<p class="p1 wp-block-paragraph">But here’s the thing… </p>



<p class="p1 wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f6a8.png" alt="🚨" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Due to an overwhelming amount of bot spam (blame the tech gremlins), we’ve had to make this exclusive FREE tool available to registered users only. We value your experience, and this change helps ensure that real travellers like <strong>YOU</strong> to enjoy seamless access without interference.</p>



<p class="p1 wp-block-paragraph"><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f449.png" alt="👉" class="wp-smiley" style="height: 1em; max-height: 1em;" /> Sign up to unlock <a href="https://tantrummingtrailblazers.com/tools/make-my-drive-fun/">Make My Drive Fun</a> and make every journey unforgettable.</p>



<p class="p1 wp-block-paragraph">Registration is free, quick, and easy plus, as a member, you’ll get access to updates, tips, and exciting new AI features we’re rolling out soon!</p>



<p class="p1 wp-block-paragraph">Don’t let the bots ruin your adventure—join our community today! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f699.png" alt="🚙" class="wp-smiley" style="height: 1em; max-height: 1em;" /><img src="https://s.w.org/images/core/emoji/17.0.2/72x72/2728.png" alt="✨" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="p1 wp-block-paragraph"><a href="#">Sign Up &amp; Start Planning</a></p>



<p class="p1 wp-block-paragraph">Your next adventure is literally just a click away! <img src="https://s.w.org/images/core/emoji/17.0.2/72x72/1f30d.png" alt="🌍" class="wp-smiley" style="height: 1em; max-height: 1em;" /></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/turn-every-drive-into-an-adventure%f0%9f%92%a1%f0%9f%9a%97/">Turn Every Drive Into an Adventure!💡🚗</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/turn-every-drive-into-an-adventure%f0%9f%92%a1%f0%9f%9a%97/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3409</post-id>	</item>
		<item>
		<title>AI Travel Planner</title>
		<link>https://tantrummingtrailblazers.com/ai/3152/</link>
					<comments>https://tantrummingtrailblazers.com/ai/3152/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Mon, 23 Dec 2024 15:39:00 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=3152</guid>

					<description><![CDATA[<p>Welcome to our AI Travel Planner, currently in testing and exclusively available to registered users. Simply register or log in to gain&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/3152/">AI Travel Planner</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%2F3152%2F&amp;linkname=AI%20Travel%20Planner" 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%2F3152%2F&amp;linkname=AI%20Travel%20Planner" 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%2F3152%2F&amp;linkname=AI%20Travel%20Planner" 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%2F3152%2F&#038;title=AI%20Travel%20Planner" data-a2a-url="https://tantrummingtrailblazers.com/ai/3152/" data-a2a-title="AI Travel Planner"></a></p>
<p class="p1 wp-block-paragraph">Welcome to our AI Travel Planner, currently in testing and exclusively available to registered users.</p>



<p class="wp-block-paragraph">Simply <a href="http://tantrummingtrailblazers.com/wp-admin">register or log in</a> to gain access and start planning your adventures with ease!</p>






<p class="wp-block-paragraph"></p>



<p class="wp-block-paragraph"></p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/3152/">AI Travel Planner</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/3152/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3152</post-id>	</item>
		<item>
		<title>AI-Powered Travel Tips: Tailored Adventures Every Family Member Will Love</title>
		<link>https://tantrummingtrailblazers.com/ai/ai-powered-travel-tips-tailored-adventures-every-family-member-will-love/</link>
					<comments>https://tantrummingtrailblazers.com/ai/ai-powered-travel-tips-tailored-adventures-every-family-member-will-love/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Fri, 20 Dec 2024 21:57:00 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=3075</guid>

					<description><![CDATA[<p>Hello again, Tantrummers! Last time, we delved into how AI is helping us navigate public transport with ease. Today, let’s talk about&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/ai-powered-travel-tips-tailored-adventures-every-family-member-will-love/">AI-Powered Travel Tips: Tailored Adventures Every Family Member Will Love</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%2Fai-powered-travel-tips-tailored-adventures-every-family-member-will-love%2F&amp;linkname=AI-Powered%20Travel%20Tips%3A%20Tailored%20Adventures%20Every%20Family%20Member%20Will%20Love" 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%2Fai-powered-travel-tips-tailored-adventures-every-family-member-will-love%2F&amp;linkname=AI-Powered%20Travel%20Tips%3A%20Tailored%20Adventures%20Every%20Family%20Member%20Will%20Love" 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%2Fai-powered-travel-tips-tailored-adventures-every-family-member-will-love%2F&amp;linkname=AI-Powered%20Travel%20Tips%3A%20Tailored%20Adventures%20Every%20Family%20Member%20Will%20Love" 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%2Fai-powered-travel-tips-tailored-adventures-every-family-member-will-love%2F&#038;title=AI-Powered%20Travel%20Tips%3A%20Tailored%20Adventures%20Every%20Family%20Member%20Will%20Love" data-a2a-url="https://tantrummingtrailblazers.com/ai/ai-powered-travel-tips-tailored-adventures-every-family-member-will-love/" data-a2a-title="AI-Powered Travel Tips: Tailored Adventures Every Family Member Will Love"></a></p>
<p class="wp-block-paragraph">Hello again, Tantrummers! Last time, we delved into how AI is helping us navigate public transport with ease. Today, let’s talk about how AI ensures that every member of our family, from Dotty &amp; Georgia to us Parents, can get personalised activity and dining recommendations.</p>



<p class="wp-block-paragraph">When we arrived in Dubrovnik, we wanted to explore the Old Town without overwhelming our kids (its not known as kid friendly so any help is of use). Finding the right balance between sightseeing and relaxation is tricky so asking ChatGPT for some advice is worth a punt. Thanks to its ability to search the internet for the latest family-friendly spots, we received up-to-date and relevant suggestions.</p>



<p class="wp-block-paragraph"><strong>My Prompt:</strong></p>



<p class="wp-block-paragraph">“We’re visiting Dubrovnik with a toddler. Can you recommend some family-friendly activities and cafes in the Old Town?”</p>



<p class="wp-block-paragraph"><strong>ChatGP Response:</strong></p>



<p class="wp-block-paragraph">“Absolutely! Here are some family-friendly activities and cafes in Dubrovnik’s Old Town:</p>



<p class="wp-block-paragraph"><strong>Activities:</strong></p>



<p class="wp-block-paragraph">1.<strong> Visit the Dubrovnik Aquarium:&nbsp;</strong>Located within the old city walls, the aquarium showcases local marine life, including colourful fish, sea turtles, and other Adriatic species.&nbsp;</p>



<p class="wp-block-paragraph">2. <strong>Lokrum Island:</strong> A short ferry ride away, it offers lovely gardens, peacocks roaming around, and shaded spots perfect for a picnic.</p>



<p class="wp-block-paragraph">3. <strong>Fort Lovrijenac:</strong> Explore this historic fortress with open spaces for little ones to run around safely.</p>



<p class="wp-block-paragraph"><strong>Cafes:</strong></p>



<p class="wp-block-paragraph">1. Gradska Kavana Arsenal: A spacious café with outdoor seating where you can relax while keeping an eye on your toddler.</p>



<p class="wp-block-paragraph">2. Café Festival: Known for its friendly atmosphere and delicious pastries, it’s a great spot for a family break.</p>



<p class="wp-block-paragraph">3. Dubrovnik Bakery: Tucked away in a quiet street, this bakery offers a variety of treats and has high chairs available for little ones.</p>



<p class="wp-block-paragraph">These spots should help you enjoy Dubrovnik without any stress!”</p>



<p class="wp-block-paragraph"><strong>How AI Helps:</strong></p>



<p class="wp-block-paragraph">Our ChatGPT-4 AI provided us with, up-to-date suggestions that catered to our familys needs, ensuring everyone had a great time without any unnecessary hassle. From scenic walks suitable for a toddler to cosy cafes perfect for a family break, AI covered all the bases.</p>



<p class="wp-block-paragraph"><strong>Tips for Getting the Best Family-Friendly Recommendations:</strong></p>



<p class="wp-block-paragraph">1.<strong> Specify Family Needs: </strong>Mention any specific requirements, like activities suitable for toddlers or cafes with high chairs.</p>



<p class="wp-block-paragraph">2.<strong> Include Interests:</strong> Let AI know if you’re interested in history, outdoor activities, or relaxing spots.</p>



<p class="wp-block-paragraph">3. <strong>Ask for Nearby Options: </strong>To save time, request recommendations that are close to your current location or accommodation.</p>



<p class="wp-block-paragraph">With AI’s personalised and up-to-date recommendations, our family adventures are more enjoyable and stress-free, allowing us to create unforgettable memories together.</p>



<p class="wp-block-paragraph">Have you discovered things to do with the help of AI? Drop your favourites in the comments.</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/ai-powered-travel-tips-tailored-adventures-every-family-member-will-love/">AI-Powered Travel Tips: Tailored Adventures Every Family Member Will Love</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/ai-powered-travel-tips-tailored-adventures-every-family-member-will-love/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3075</post-id>	</item>
		<item>
		<title>Effortless Travel: Unlock Stress-Free Family Adventures with AI</title>
		<link>https://tantrummingtrailblazers.com/ai/effortless-travel-unlock-stress-free-family-adventures-with-ai/</link>
					<comments>https://tantrummingtrailblazers.com/ai/effortless-travel-unlock-stress-free-family-adventures-with-ai/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Wed, 18 Dec 2024 09:59:00 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=3070</guid>

					<description><![CDATA[<p>In my last post, I broadly explored how AI has transformed our dining and exploration experiences by translating menus and revealing the&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/effortless-travel-unlock-stress-free-family-adventures-with-ai/">Effortless Travel: Unlock Stress-Free Family Adventures with AI</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%2Feffortless-travel-unlock-stress-free-family-adventures-with-ai%2F&amp;linkname=Effortless%20Travel%3A%20Unlock%20Stress-Free%20Family%20Adventures%20with%20AI" 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%2Feffortless-travel-unlock-stress-free-family-adventures-with-ai%2F&amp;linkname=Effortless%20Travel%3A%20Unlock%20Stress-Free%20Family%20Adventures%20with%20AI" 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%2Feffortless-travel-unlock-stress-free-family-adventures-with-ai%2F&amp;linkname=Effortless%20Travel%3A%20Unlock%20Stress-Free%20Family%20Adventures%20with%20AI" 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%2Feffortless-travel-unlock-stress-free-family-adventures-with-ai%2F&#038;title=Effortless%20Travel%3A%20Unlock%20Stress-Free%20Family%20Adventures%20with%20AI" data-a2a-url="https://tantrummingtrailblazers.com/ai/effortless-travel-unlock-stress-free-family-adventures-with-ai/" data-a2a-title="Effortless Travel: Unlock Stress-Free Family Adventures with AI"></a></p>
<p class="p1 wp-block-paragraph">In my last post, I broadly explored how AI has transformed our dining and exploration experiences by translating menus and revealing the stories behind historical landmarks. But navigating a new city’s public transport can be a whole different challenge. In this post ill break down our trusty guide to help us through the maze of train, metro lines and bus routes.</p>



<p class="p1 wp-block-paragraph"><strong>Ever found yourself standing in a bustling metro station, map in hand, feeling utterly lost?</strong> We’ve been there too, and AI has been our lifesaver in those moments!</p>



<p class="p1 wp-block-paragraph"><strong>Picture this:</strong> your in Paris, standing at the foot of the Eiffel Tower, eager to explore Montmartre. The metro map looks like a complicated web of lines and stations, and the French names are a bit of a tongue-twister. Instead of feeling overwhelmed, we turned to our ChatGPT which can search the internet for live updates and the latest information.</p>



<p class="p1 wp-block-paragraph"><strong>We asked in ChatGPT (called a prompt):</strong></p>



<p class="p1 wp-block-paragraph">“How do I get from the Eiffel Tower to Montmartre by train in Paris?”</p>



<p class="p1 wp-block-paragraph"><strong>ChatGPTs Simple Response Was As Follows:</strong></p>



<p class="p1 wp-block-paragraph">“Sure! Here’s how you can get from the Eiffel Tower to Montmartre using the Paris Metro:</p>



<p class="p5 wp-block-paragraph"><strong>Start at Trocadéro Station</strong> (near the Eiffel Tower).</p>



<p class="wp-block-paragraph"><strong>Take Line 6 (direction Nation)</strong> for <strong>1 stop</strong>.</p>



<p class="wp-block-paragraph">Get off at <strong>Charles de Gaulle–Étoile</strong>.</p>



<p class="wp-block-paragraph"><strong>Transfer to Line 2 (direction Nation)</strong>.</p>



<p class="wp-block-paragraph"><strong>Stay on Line 2 for 6 stops</strong> and get off at <strong>Anvers</strong>.</p>



<p class="wp-block-paragraph"><strong>Walk to Montmartre from Anvers station</strong> (approximately 10 minutes uphill). Follow signs or maps pointing to <strong>Montmartre</strong> or <strong>Sacré-Cœur Basilica</strong>.</p>



<p class="wp-block-paragraph">(Alternatively, you can get off at <strong>Pigalle</strong>, but <strong>Anvers</strong> is closer to Montmartre&#8217;s main attractions, such as the Sacré-Cœur Basilica.)</p>



<p class="p5 wp-block-paragraph">This route is efficient and takes about 25 minutes in total.</p>



<p class="p1 wp-block-paragraph"><strong>How AI Helps:</strong></p>



<p class="p1 wp-block-paragraph">Thanks to ChatGPT which pulls live data from the internet, we received clear, step-by-step instructions that felt like having a local guide right in our pocket. No more guessing or getting lost – just smooth, stress-free navigation with the most current information and human instructions.</p>



<p class="p1 wp-block-paragraph"><strong>Tips for Using AI for Public Transport:</strong></p>



<p class="p5 wp-block-paragraph">1. Be Specific with Your Prompts: Clearly state your starting point and destination.</p>



<p class="p5 wp-block-paragraph">2. Use Local Transport Terms: Include terms like “metro,” “bus,” or “tram” to get accurate responses.</p>



<p class="p5 wp-block-paragraph">3. Ask to check for Real-Time Updates: AI like ChatGPT-4 can provide live updates on schedules and any service changes.</p>



<p class="p1 wp-block-paragraph">AI will truly transformed the way we navigate new cities in the future, turning what could be a stressful experience into a seamless adventure, you can be ahead of this curve by following me.</p>



<p class="p1 wp-block-paragraph">I would love to hear if AI has helped you explore new cities, if you have not tried, give it a go!</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/effortless-travel-unlock-stress-free-family-adventures-with-ai/">Effortless Travel: Unlock Stress-Free Family Adventures with AI</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/effortless-travel-unlock-stress-free-family-adventures-with-ai/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3070</post-id>	</item>
		<item>
		<title>Effortless Trip Planning: How AI Makes Travel Smoother and Fun</title>
		<link>https://tantrummingtrailblazers.com/ai/effortless-trip-planning-how-ai-makes-travel-smoother-and-fun/</link>
					<comments>https://tantrummingtrailblazers.com/ai/effortless-trip-planning-how-ai-makes-travel-smoother-and-fun/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Mon, 16 Dec 2024 09:30:00 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=3079</guid>

					<description><![CDATA[<p>Previously, we shared how AI assists us in navigating public transport and finding family-friendly activities. Today, let’s dive into how our AI&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/effortless-trip-planning-how-ai-makes-travel-smoother-and-fun/">Effortless Trip Planning: How AI Makes Travel Smoother and Fun</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%2Feffortless-trip-planning-how-ai-makes-travel-smoother-and-fun%2F&amp;linkname=Effortless%20Trip%20Planning%3A%20How%20AI%20Makes%20Travel%20Smoother%20and%20Fun" 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%2Feffortless-trip-planning-how-ai-makes-travel-smoother-and-fun%2F&amp;linkname=Effortless%20Trip%20Planning%3A%20How%20AI%20Makes%20Travel%20Smoother%20and%20Fun" 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%2Feffortless-trip-planning-how-ai-makes-travel-smoother-and-fun%2F&amp;linkname=Effortless%20Trip%20Planning%3A%20How%20AI%20Makes%20Travel%20Smoother%20and%20Fun" 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%2Feffortless-trip-planning-how-ai-makes-travel-smoother-and-fun%2F&#038;title=Effortless%20Trip%20Planning%3A%20How%20AI%20Makes%20Travel%20Smoother%20and%20Fun" data-a2a-url="https://tantrummingtrailblazers.com/ai/effortless-trip-planning-how-ai-makes-travel-smoother-and-fun/" data-a2a-title="Effortless Trip Planning: How AI Makes Travel Smoother and Fun"></a></p>
<p class="wp-block-paragraph">Previously, we shared how AI assists us in navigating public transport and finding family-friendly activities. Today, let’s dive into how our AI assistant takes the stress out of travel planning, helping us create efficient and enjoyable itineraries that suit our family’s needs.</p>



<p class="wp-block-paragraph">I am sure like us, you have felt overwhelmed by the endless options when planning a day out in a new city? Let our AI do the heavy lifting for you!</p>



<p class="wp-block-paragraph">Planning a day in Rome with visits to the Vatican Museums, the Colosseum, and a relaxing walk around Villa Borghese would be ambitious. But you can turn ChatGPT, which can access live information, to help you map out the day without feeling rushed.</p>



<p class="wp-block-paragraph"><strong>My Example Prompt:</strong></p>



<p class="wp-block-paragraph">“We have one day in Rome and want to visit the Vatican Museums, the Colosseum, and take a walk around Villa Borghese. Can you help us plan our itinerary?”</p>



<p class="wp-block-paragraph"><strong>ChatGPT’s Response:</strong></p>



<p class="wp-block-paragraph">“Of course! Here’s a suggested itinerary for your day in Rome:</p>



<p class="wp-block-paragraph"><strong>8:30 AM – Vatican Museums</strong></p>



<p class="wp-block-paragraph">• Arrival: Aim to arrive by 8:15 AM to skip the longer lines.</p>



<p class="wp-block-paragraph">• Duration: Allocate about 2.5 hours to explore the museums and Sistine Chapel.</p>



<p class="wp-block-paragraph">• Tip: Check the latest opening hours and book skip-the-line tickets online.</p>



<p class="wp-block-paragraph"><strong>11:00 AM – St. Peter’s Basilica</strong></p>



<p class="wp-block-paragraph">• Walk: It’s a short 5-minute walk from the museums.</p>



<p class="wp-block-paragraph">• Duration: Spend around 1 hour exploring the basilica.</p>



<p class="wp-block-paragraph">• Tip: Dress modestly to enter the basilica and consider a guided tour for a richer experience.</p>



<p class="wp-block-paragraph"><strong>12:30 PM – Lunch Break</strong></p>



<p class="wp-block-paragraph">• Restaurant Suggestion: Grab a family-friendly meal at Ristorante La Zanzara, located nearby. They offer a diverse menu that caters to both adults and children.</p>



<p class="wp-block-paragraph"><strong>2:00 PM – Colosseum</strong></p>



<p class="wp-block-paragraph">• Transport: Take a taxi or public transport to the Colosseum.</p>



<p class="wp-block-paragraph">• Duration: Allocate 1.5 hours for a guided tour.</p>



<p class="wp-block-paragraph">• Tip: Verify the latest entry requirements and book tickets in advance to avoid long queues.</p>



<p class="wp-block-paragraph"><strong>4:00 PM – Walk Around Villa Borghese</strong></p>



<p class="wp-block-paragraph">• Transport: Head to Villa Borghese via taxi or bus.</p>



<p class="wp-block-paragraph">• Duration: Spend the rest of the afternoon enjoying the gardens, renting bikes, or visiting the Bioparco Zoo if your toddler is interested.</p>



<p class="wp-block-paragraph">• Tip: Check the weather forecast and plan accordingly for outdoor activities.</p>



<p class="wp-block-paragraph"><strong>6:30 PM – Early Dinner</strong></p>



<p class="wp-block-paragraph">• Restaurant Suggestion: Enjoy an early dinner at Casina Valadier, which offers beautiful views and a relaxing atmosphere. Make sure to check their current opening hours and reservation policies.</p>



<p class="wp-block-paragraph">Tips:</p>



<p class="wp-block-paragraph">• Tickets: Book tickets for the Vatican Museums and Colosseum in</p>



<p class="wp-block-paragraph">advance to save time.</p>



<p class="wp-block-paragraph">• Breaks: Make sure to take short breaks, especially for kids to rest.</p>



<p class="wp-block-paragraph">• Live Updates: Use AI to check for any last-minute changes or events that might affect your itinerary.</p>



<p class="wp-block-paragraph"><strong>How AI Helps</strong></p>



<p class="wp-block-paragraph">As you can see AI created a balanced, up to date itinerary that maximises sightseeing opportunities while ensuring plenty of breaks for the little ones. It even suggested specific restaurants and provided practical tips to avoid common pitfalls. (IMO its too much for a single day, but if you really only had 1 day then this busy schedule is doable)</p>



<p class="wp-block-paragraph"><strong>Tips for Effective Travel Planning with AI</strong></p>



<p class="wp-block-paragraph">1. Outline Your Priorities: List the must-see attractions and any specific interests your family has.</p>



<p class="wp-block-paragraph">2. Include Time for Breaks: Make sure to account for downtime, especially when travelling with young children.</p>



<p class="wp-block-paragraph">3. Ask for Logistics: Request information on transportation between attractions and dining options nearby.</p>



<p class="wp-block-paragraph">As you can see with AI’s help, travel planning is streamlined allowing planning to be simplified and you can enjoy more of our destinations without the usual stress.</p>



<p class="wp-block-paragraph">How do you currently plan your travel itineraries? Have you tried using AI for your trip planning? Share your tips or let us know if you need help organising your next adventure!</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/effortless-trip-planning-how-ai-makes-travel-smoother-and-fun/">Effortless Trip Planning: How AI Makes Travel Smoother and Fun</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/effortless-trip-planning-how-ai-makes-travel-smoother-and-fun/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3079</post-id>	</item>
		<item>
		<title>AI Has Simplified Our Travels: From Translating Menus To Unlocking History!</title>
		<link>https://tantrummingtrailblazers.com/ai/ai-has-simplified-our-travels-translating-menus-and-unlocking-history/</link>
					<comments>https://tantrummingtrailblazers.com/ai/ai-has-simplified-our-travels-translating-menus-and-unlocking-history/#respond</comments>
		
		<dc:creator><![CDATA[Dennis]]></dc:creator>
		<pubDate>Wed, 11 Dec 2024 21:56:07 +0000</pubDate>
				<category><![CDATA[AI]]></category>
		<guid isPermaLink="false">https://tantrummingtrailblazers.com/?p=3045</guid>

					<description><![CDATA[<p>When you’re travelling the world, there’s a certain charm in deciphering local menus, exploring traditional dishes, or marvelling at ancient sites. But&#8230;</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/ai-has-simplified-our-travels-translating-menus-and-unlocking-history/">AI Has Simplified Our Travels: From Translating Menus To Unlocking History!</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%2Fai-has-simplified-our-travels-translating-menus-and-unlocking-history%2F&amp;linkname=AI%20Has%20Simplified%20Our%20Travels%3A%20From%20Translating%20Menus%20To%20Unlocking%20History%21" 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%2Fai-has-simplified-our-travels-translating-menus-and-unlocking-history%2F&amp;linkname=AI%20Has%20Simplified%20Our%20Travels%3A%20From%20Translating%20Menus%20To%20Unlocking%20History%21" 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%2Fai-has-simplified-our-travels-translating-menus-and-unlocking-history%2F&amp;linkname=AI%20Has%20Simplified%20Our%20Travels%3A%20From%20Translating%20Menus%20To%20Unlocking%20History%21" 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%2Fai-has-simplified-our-travels-translating-menus-and-unlocking-history%2F&#038;title=AI%20Has%20Simplified%20Our%20Travels%3A%20From%20Translating%20Menus%20To%20Unlocking%20History%21" data-a2a-url="https://tantrummingtrailblazers.com/ai/ai-has-simplified-our-travels-translating-menus-and-unlocking-history/" data-a2a-title="AI Has Simplified Our Travels: From Translating Menus To Unlocking History!"></a></p>
<p class="wp-block-paragraph">When you’re travelling the world, there’s a certain charm in deciphering local menus, exploring traditional dishes, or marvelling at ancient sites. But sometimes, the charm fades when faced with unidentifiable ingredients or ancient carvings that demand expertise in dead languages. That’s where AI comes in seamlessly bridging the gap between mystery and understanding, often with nothing more than a simple photo, no complex prompting, just a basic photo and a what does this mean will do.</p>



<p class="wp-block-paragraph">So imagine, your sitting down at a cosy cafe in a small town. The menu is beautifully handwritten, but as a vegetarian, you’re unsure which dishes suit your preferences. AI tools have transformed this challenge. It’s not just about translating words like “pasta” or “soup.” which often isnt helpful at all, you can simply snap a photo, and the AI does the rest, there is no complicated prompts required. It identifies ingredients, highlights vegetarian or vegan-friendly options (if asked) and even provides background on how the dish is traditionally prepared.</p>



<p class="wp-block-paragraph">For someone like my wife, who’s a vegetarian, this type of assistance adds a whole new layer of confidence when exploring local cuisine, because often the local people will not have a clue what your talking about when you ask, is this vegetarian.</p>



<p class="wp-block-paragraph">on another note, during our recent visit to Pompeii, I was interested in some text from 2000 years ago etched into the stone. Instead of scratching my head, I snapped a picture and AI gave me a translation with context explaining the historical significance, the purpose behind the carvings, and even their era-specific phrasing (it was election supporting graffiti) crazy to think its still readable and AI could translate it and tell me about its meaning and who to vote for!.</p>



<p class="wp-block-paragraph">Similarly, at the Da Vinchi museum in florence we saw machines with no details so a quick photo brought them to life. The AI identified each device from just a picture of it, detailed how it worked, and explained its role in shaping modern engineering. Unlike in the UK, Europe do not put up as much detail on what is on display, often there is nothing.</p>



<p class="wp-block-paragraph"><strong>How can you do this?</strong></p>



<p class="wp-block-paragraph">Uploading images to ChatGPT via the mobile app is straightforward. Here’s how you can do it step by step:</p>



<ol class="wp-block-list">
<li><strong>Tap the Plus Icon (+)</strong>: In the message bar of the ChatGPT app, tap the <strong><code>+</code></strong> icon to expand the options menu.</li>



<li><strong>Choose an Image Source</strong>: Select whether you want to:
<ul class="wp-block-list">
<li>Upload an image from your phone&#8217;s gallery or files.</li>



<li>Take a new photo with your phone&#8217;s camera.</li>
</ul>
</li>



<li><strong>Comment if Needed</strong>: Adding text notes for example (translate this menu to English and tell me if anything is vegetarian)</li>



<li><strong>Send the Image</strong>: Once you&#8217;ve selected your image and added your question, tap <strong>Send</strong> to share it in the chat and await your reply!.</li>
</ol>



<div class="wp-block-media-text has-media-on-the-right is-stacked-on-mobile" style="grid-template-columns:auto 85%"><div class="wp-block-media-text__content">
<p class="wp-block-paragraph"><br><strong>Identify a location!</strong></p>
</div><figure class="wp-block-media-text__media"><img fetchpriority="high" decoding="async" width="594" height="176" src="https://tantrummingtrailblazers.com/wp-content/uploads/2024/12/image-1.png" alt="Sure! However, I would need more context about the image to create a specific and effective alt text. Could you please describe the image or provide details about its content, subject, and purpose?" class="wp-image-3047 size-full" srcset="https://tantrummingtrailblazers.com/wp-content/uploads/2024/12/image-1.png 594w, https://tantrummingtrailblazers.com/wp-content/uploads/2024/12/image-1-300x89.png 300w, https://tantrummingtrailblazers.com/wp-content/uploads/2024/12/image-1-590x176.png 590w" sizes="(max-width: 594px) 100vw, 594px" /></figure></div>



<p class="wp-block-paragraph">So this is how I use photos in ChatGPT to help me navigate and enrich my travel experiences. By simply uploading a photo and asking, &#8220;What does this mean?&#8221; I’ve been able to translate ancient text, understand the story behind a piece of art, and even figure out the ingredients in a local dish. It’s quick, seamless, and takes away the guesswork when exploring unfamiliar places so if your not using it, you should!</p>



<p class="wp-block-paragraph"><strong>What Next</strong><br>This is just the start of how AI can enhance your travels. </p>



<p class="wp-block-paragraph">Next time, I’ll explore how it can help plan your adventures from uncovering hidden gems to designing the perfect day in a city you’ve never visited.</p>



<p class="wp-block-paragraph">In the meantime, what’s the most surprising thing AI has helped you discover on your travels? Let me know in the comments!</p>
<p>The post <a href="https://tantrummingtrailblazers.com/ai/ai-has-simplified-our-travels-translating-menus-and-unlocking-history/">AI Has Simplified Our Travels: From Translating Menus To Unlocking History!</a> appeared first on <a href="https://tantrummingtrailblazers.com">Tantrumming Trailblazers</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://tantrummingtrailblazers.com/ai/ai-has-simplified-our-travels-translating-menus-and-unlocking-history/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">3045</post-id>	</item>
	</channel>
</rss>
