<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Jyutping on Roman Empire</title>
    <link>https://romanempire.dev/tags/jyutping/</link>
    <description>Recent content in Jyutping 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/tags/jyutping/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>
    
  </channel>
</rss>
