<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Cantonese on Roman Empire</title>
    <link>https://romanempire.dev/categories/cantonese/</link>
    <description>Recent content in Cantonese on Roman Empire</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>en</language>
    <managingEditor>contact@romanempire.dev (Roman)</managingEditor>
    <webMaster>contact@romanempire.dev (Roman)</webMaster>
    <copyright>🦆</copyright>
    <lastBuildDate>Thu, 16 Apr 2026 00:00:00 +0000</lastBuildDate><atom:link href="https://romanempire.dev/categories/cantonese/index.xml" rel="self" type="application/rss+xml" />
    
    <item>
      <title>Jyutping</title>
      <link>https://romanempire.dev/posts/cantonese/jyutping/</link>
      <pubDate>Thu, 16 Apr 2026 00:00:00 +0000</pubDate>
      <author>contact@romanempire.dev (Roman)</author>
      <guid>https://romanempire.dev/posts/cantonese/jyutping/</guid>
      <description>&lt;div class=&#34;lead text-neutral-500 dark:text-neutral-400 !mb-9 text-xl&#34;&gt;&#xA;  Jyutping is the romanization system for Cantonese. It uses initials, finals, and 6 tones.&#xA;&lt;/div&gt;&#xA;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;Syllable Structure&#xA;    &lt;div id=&#34;syllable-structure&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#syllable-structure&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Every Jyutping syllable follows this pattern:&lt;/p&gt;&#xA;&lt;div class=&#34;highlight-wrapper&#34;&gt;&lt;div class=&#34;highlight&#34;&gt;&lt;pre tabindex=&#34;0&#34; class=&#34;chroma&#34;&gt;&lt;code class=&#34;language-text&#34; data-lang=&#34;text&#34;&gt;&lt;span class=&#34;line&#34;&gt;&lt;span class=&#34;cl&#34;&gt;[initial] + [final] + [tone number]&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;&#xA;&lt;ul&gt;&#xA;&lt;li&gt;&lt;strong&gt;initial&lt;/strong&gt; - the opening consonant: &lt;code&gt;g&lt;/code&gt;, &lt;code&gt;s&lt;/code&gt;, &lt;code&gt;ng&lt;/code&gt;, etc.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;final&lt;/strong&gt; - the vowel nucleus plus any closing consonant: &lt;code&gt;aa&lt;/code&gt;, &lt;code&gt;aam&lt;/code&gt;, &lt;code&gt;ik&lt;/code&gt;, etc.&lt;/li&gt;&#xA;&lt;li&gt;&lt;strong&gt;tone number&lt;/strong&gt; - a digit 1-6 appended at the end&lt;/li&gt;&#xA;&lt;/ul&gt;&#xA;&lt;p&gt;For example: &lt;code&gt;g&lt;/code&gt; + &lt;code&gt;aau&lt;/code&gt; + &lt;code&gt;3&lt;/code&gt; = &lt;style&gt;&#xA;  .jp-phrase { display: inline-flex; align-items: baseline; gap: 0.4rem; }&#xA;  .jp-play {&#xA;    display: inline-flex; align-items: center; justify-content: center;&#xA;    width: 1.4em; height: 1.4em; border-radius: 50%;&#xA;    border: 1.5px solid rgb(var(--color-neutral-300));&#xA;    background: rgb(var(--color-neutral-50));&#xA;    cursor: pointer; transition: all 0.2s;&#xA;    flex-shrink: 0; padding: 0;&#xA;  }&#xA;  html.dark .jp-play { border-color: rgb(var(--color-neutral-600)); background: rgb(var(--color-neutral-800)); }&#xA;  .jp-play:hover { border-color: rgb(var(--color-neutral-400)); }&#xA;  html.dark .jp-play:hover { border-color: rgb(var(--color-neutral-500)); }&#xA;  .jp-play svg { width: 0.6em; height: 0.6em; fill: rgb(var(--color-neutral-500)); margin-left: 1px; }&#xA;  html.dark .jp-play svg { fill: rgb(var(--color-neutral-400)); }&#xA;  .jp-play.jp-locked { opacity: 0.4; pointer-events: none; }&#xA;  .jp-word-wrap { position: relative; display: inline-block; }&#xA;  .jp-has-curve { padding-top: 5px; }&#xA;  .jp-curve { display: block; overflow: visible; position: absolute; bottom: calc(100% - 6px); left: 50%; transform: translateX(-50%); pointer-events: none; }&#xA;  .jp-curve path { fill: none; stroke-width: 2; stroke-linecap: round; }&#xA;  .jp-curve .cc-dot { opacity: 0; }&#xA;  .jp-curve.cc-animating .cc-dot { opacity: 1; }&#xA;  .jp-word {&#xA;    cursor: pointer; font-weight: 600;&#xA;    border-bottom: 2px solid transparent;&#xA;    border-radius: 2px; padding: 0 2px;&#xA;    transition: border-color 0.2s, opacity 0.2s;&#xA;  }&#xA;  .jp-word:hover { opacity: 0.8; }&#xA;  .jp-word.jp-playing { border-bottom-color: currentColor; }&#xA;&lt;/style&gt;&#xA;&#xA;&lt;span class=&#34;jp-phrase&#34; id=&#34;jp-2287e78a5e9195d56fdc0a5039ad403f&#34; data-text=&#34;gaau3&#34; data-graph=&#34;true&#34;&gt;&lt;/span&gt;&#xA;&#xA;&lt;script&gt;&#xA;document.addEventListener(&#39;DOMContentLoaded&#39;, () =&gt; {&#xA;  const { TONES, DUR_MS, toneColor, playSound, buildCurve, animateCurve } = CantoneseCommon;&#xA;&#xA;  function playTone(tone, wordEl, curve) {&#xA;    playSound(tone);&#xA;    wordEl.classList.add(&#39;jp-playing&#39;);&#xA;    setTimeout(() =&gt; wordEl.classList.remove(&#39;jp-playing&#39;), DUR_MS);&#xA;    if (curve) animateCurve(curve);&#xA;  }&#xA;&#xA;  function parseTone(word) {&#xA;    const m = word.match(/(\d)/);&#xA;    const num = m ? parseInt(m[1]) : 0;&#xA;    return (num &gt;= 1 &amp;&amp; num &lt;= 6) ? TONES[num - 1] : null;&#xA;  }&#xA;&#xA;  const container = document.getElementById(&#39;jp-2287e78a5e9195d56fdc0a5039ad403f&#39;);&#xA;  const showGraph = container.getAttribute(&#39;data-graph&#39;) === &#39;true&#39;;&#xA;  const words = container.getAttribute(&#39;data-text&#39;).split(/\s+/);&#xA;  const entries = [];&#xA;&#xA;  const playBtn = document.createElement(&#39;button&#39;);&#xA;  playBtn.className = &#39;jp-play&#39;;&#xA;  playBtn.innerHTML = &#39;&lt;svg viewBox=&#34;0 0 24 24&#34;&gt;&lt;polygon points=&#34;6,4 20,12 6,20&#34;/&gt;&lt;/svg&gt;&#39;;&#xA;  container.appendChild(playBtn);&#xA;&#xA;  words.forEach((word, i) =&gt; {&#xA;    if (i &gt; 0) container.appendChild(document.createTextNode(&#39; &#39;));&#xA;    const tone = parseTone(word);&#xA;&#xA;    const wrap = document.createElement(&#39;span&#39;);&#xA;    wrap.className = &#39;jp-word-wrap&#39;;&#xA;&#xA;    let curve = null;&#xA;    if (showGraph &amp;&amp; tone) {&#xA;      curve = buildCurve(tone);&#xA;      curve.classList.add(&#39;jp-curve&#39;);&#xA;      wrap.classList.add(&#39;jp-has-curve&#39;);&#xA;      wrap.appendChild(curve);&#xA;    }&#xA;&#xA;    const span = document.createElement(&#39;span&#39;);&#xA;    span.className = &#39;jp-word&#39;;&#xA;    span.textContent = word;&#xA;    if (tone) {&#xA;      span.style.color = toneColor(tone);&#xA;      span.addEventListener(&#39;click&#39;, () =&gt; playTone(tone, span, curve));&#xA;    }&#xA;    wrap.appendChild(span);&#xA;    container.appendChild(wrap);&#xA;    entries.push({ el: span, tone, curve });&#xA;  });&#xA;&#xA;  let playing = false;&#xA;&#xA;  playBtn.addEventListener(&#39;click&#39;, () =&gt; {&#xA;    if (playing) return;&#xA;    playing = true;&#xA;    playBtn.classList.add(&#39;jp-locked&#39;);&#xA;&#xA;    let i = 0;&#xA;    (function next() {&#xA;      while (i &lt; entries.length &amp;&amp; !entries[i].tone) i++;&#xA;      if (i &gt;= entries.length) {&#xA;        playing = false;&#xA;        playBtn.classList.remove(&#39;jp-locked&#39;);&#xA;        return;&#xA;      }&#xA;      const { el, tone, curve } = entries[i++];&#xA;      playTone(tone, el, curve);&#xA;      setTimeout(next, DUR_MS + 100);&#xA;    })();&#xA;  });&#xA;&#xA;  new MutationObserver(() =&gt; {&#xA;    entries.forEach(({ el, tone, curve }) =&gt; {&#xA;      if (!tone) return;&#xA;      const c = toneColor(tone);&#xA;      el.style.color = c;&#xA;      if (curve) {&#xA;        curve.querySelector(&#39;path&#39;).setAttribute(&#39;stroke&#39;, c);&#xA;        curve.querySelector(&#39;.cc-dot&#39;).setAttribute(&#39;fill&#39;, c);&#xA;      }&#xA;    });&#xA;  }).observe(document.documentElement, { attributes: true, attributeFilter: [&#39;class&#39;] });&#xA;});&#xA;&lt;/script&gt;&#xA;&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://romanempire.dev/posts/cantonese/jyutping/feature.png" />
    </item>
    
    <item>
      <title>Cantonese Tones</title>
      <link>https://romanempire.dev/posts/cantonese/tones/</link>
      <pubDate>Wed, 08 Apr 2026 00:00:00 +0000</pubDate>
      <author>contact@romanempire.dev (Roman)</author>
      <guid>https://romanempire.dev/posts/cantonese/tones/</guid>
      <description>&lt;p&gt;I want to learn cantonese, but what keeps making me confused are the tones. So thought it would be nice to have something interactive that allows to hear the tone and test how well I can tell those apart&lt;/p&gt;&#xA;&#xA;&lt;h2 class=&#34;relative group&#34;&gt;The Six Tones&#xA;    &lt;div id=&#34;the-six-tones&#34; class=&#34;anchor&#34;&gt;&lt;/div&gt;&#xA;    &#xA;    &lt;span&#xA;        class=&#34;absolute top-0 w-6 transition-opacity opacity-0 -start-6 not-prose group-hover:opacity-100 select-none&#34;&gt;&#xA;        &lt;a class=&#34;text-primary-300 dark:text-neutral-700 !no-underline&#34; href=&#34;#the-six-tones&#34; aria-label=&#34;Anchor&#34;&gt;#&lt;/a&gt;&#xA;    &lt;/span&gt;&#xA;    &#xA;&lt;/h2&gt;&#xA;&lt;p&gt;Cantonese has six tones. Click any tone line or card to hear its pitch contour.&lt;/p&gt;</description>
      <media:content xmlns:media="http://search.yahoo.com/mrss/" url="https://romanempire.dev/posts/cantonese/tones/feature.png" />
    </item>
    
  </channel>
</rss>
