{"id":399,"date":"2025-08-26T09:31:14","date_gmt":"2025-08-26T09:31:14","guid":{"rendered":"https:\/\/tomchan.hk\/?page_id=399"},"modified":"2025-08-26T09:31:15","modified_gmt":"2025-08-26T09:31:15","slug":"developmental-screening","status":"publish","type":"page","link":"https:\/\/tomchan.hk\/?page_id=399","title":{"rendered":"Developmental Screening"},"content":{"rendered":"<!-- Developmental Milestone Screener (range \"4\u20136 months\"; answered recap +\/-; \"--- Summary ---\"; Language below Fine; no Red Flag) -->\r\n<style>\r\n  .dev-app{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;max-width:1100px;margin:0 auto;padding:1rem}\r\n  .dev-card{background:#fff;border:1px solid #e5e7eb;border-radius:14px;padding:1rem 1.25rem;box-shadow:0 1px 2px rgba(0,0,0,.04);margin-bottom:1rem}\r\n  .dev-h{margin:.25rem 0 .75rem;font-size:1.1rem}\r\n  .dev-col{display:flex;flex-direction:column;gap:.5rem}\r\n  .dev-grid{display:grid;grid-template-columns:1fr;gap:.75rem}\r\n  @media(min-width:900px){.dev-grid{grid-template-columns:1fr 1fr}}\r\n  .dev-input{padding:.6rem .7rem;border:1px solid #d1d5db;border-radius:10px;width:100%}\r\n  .dev-btn{cursor:pointer;background:#111827;color:#fff;border:none;border-radius:10px;padding:.6rem .9rem;font-weight:600}\r\n  .q-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:center;gap:.75rem;padding:.5rem 0;border-bottom:1px dashed #e5e7eb}\r\n  .q-text{line-height:1.4}\r\n  .toggle{display:inline-flex;border:1px solid #d1d5db;border-radius:999px;overflow:hidden}\r\n  .toggle button{padding:.35rem .75rem;border:none;background:#c8c8c8;font-weight:600;cursor:pointer}\r\n  .toggle button.active.yes{background:#62d441;color:#fff}\r\n  .toggle button.active.no{background:#ff7373;color:#fff}\r\n  .pill{background:#f3f4f6;border:1px solid #e5e7eb;border-radius:999px;padding:.2rem .6rem;font-size:.82rem;color:#000}\r\n  .muted{color:#6b7280}\r\n  .small{font-size:.9rem}\r\n  .summary-block{white-space:pre-wrap;font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,\"Liberation Mono\",\"Courier New\",monospace;font-size:.95rem}\r\n<\/style>\r\n\r\n<div class=\"dev-app\" id=\"dev-app\">\r\n  <div class=\"dev-card\">\r\n    <h2 style=\"margin:0\"> Development Screener<\/h2>\r\n   <p class=\"muted small\" style=\"margin-top:.25rem\">\r\n\r\n    <\/p>\r\n    <div class=\"dev-grid\">\r\n      <div class=\"dev-col\">\r\n        <label for=\"ageMonthsSelect\"><strong>Child's age<\/strong><\/label>\r\n        <select class=\"dev-input\" id=\"ageMonthsSelect\"><\/select>\r\n      <\/div>\r\n    <\/div>\r\n    <div style=\"display:flex;gap:.5rem;margin-top:.75rem\">\r\n      <button class=\"dev-btn\" id=\"resetBtn\" style=\"display:none;background:#6b7280\">Reset<\/button>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div id=\"workArea\"><\/div>\r\n\r\n  <div class=\"dev-card\" id=\"conclusionCard\" style=\"display:none\">\r\n    <h3 class=\"dev-h\">Conclusion<\/h3>\r\n\r\n    <!-- Answered-item recap (live) -->\r\n    <div id=\"answeredSummary\" class=\"summary-block\" style=\"margin-bottom:.5rem\"><\/div>\r\n\r\n    <!-- Divider line requested -->\r\n    <div id=\"summaryDivider\" class=\"summary-block muted\" style=\"margin:.25rem 0;\">--- Summary ---<\/div>\r\n\r\n    <!-- Domain developmental-age conclusions (live) -->\r\n    <div id=\"conclusionSummary\" class=\"summary-block\"><\/div>\r\n\r\n    <div class=\"muted small\" style=\"margin-top:.75rem\">QR code for this summary<\/div>\r\n    <div id=\"qrSummary\" style=\"margin-top:.25rem\"><\/div>\r\n  <\/div>\r\n<\/div>\r\n\r\n<script>\r\n(function(){\r\n  \/\/ ======= CONFIG =======\r\n  const MILESTONES_JSON_URL = 'https:\/\/tomchan.hk\/tomwebapp\/milestones_normalized.json';\r\n  const PASS_THRESHOLD = 0.6;   \/\/ \u226560% Yes = pass\r\n  const BORDERLINE_LOWER = 0.4; \/\/ 40\u2013<60% Yes at the next band up \u2192 show range with the lower passed band\r\n\r\n  \/\/ ======= STATE =======\r\n  let DATA = null;\r\n  let ageMonths = null;\r\n  let domainOrder = [];\r\n  let domainState = {};\r\n  let completedDomains = new Set();\r\n\r\n  function el(id){ return document.getElementById(id); }\r\n\r\n  \/\/ Age dropdown: 0\u201335 months, then 3y\/4y\/5y\r\n  function buildAgeDropdown(){\r\n    const sel = el('ageMonthsSelect');\r\n    sel.innerHTML = '';\r\n    for(let m=0;m<=35;m++){\r\n      const opt = document.createElement('option');\r\n      opt.value = String(m);\r\n      opt.textContent = `${m} months`;\r\n      sel.appendChild(opt);\r\n    }\r\n    [{m:36,label:'3 years'},{m:48,label:'4 years'},{m:60,label:'5 years'}].forEach(({m,label})=>{\r\n      const opt = document.createElement('option');\r\n      opt.value = String(m);\r\n      opt.textContent = label;\r\n      sel.appendChild(opt);\r\n    });\r\n  }\r\n\r\n  async function loadData(){\r\n    const res = await fetch(MILESTONES_JSON_URL);\r\n    if(!res.ok) throw new Error('Failed to load milestones JSON');\r\n    DATA = await res.json();\r\n\r\n    canonicalizeDomains();\r\n    splitLanguageIfCombined();\r\n    removeRedFlagDomains();\r\n    for (const d of Object.keys(DATA)){ DATA[d].sort((a,b)=>a.age_months-b.age_months); }\r\n    domainOrder = reorderDomains(Object.keys(DATA));\r\n  }\r\n\r\n  function canonicalizeDomains(){\r\n    const alias = {\r\n      'gross motor': 'Gross Motor',\r\n      'fine motor': 'Fine\/Visual Motor',\r\n      'fine\/visual motor': 'Fine\/Visual Motor',\r\n      'visual motor': 'Fine\/Visual Motor',\r\n      'social\/adaptive': 'Social\/Adaptive',\r\n      'cognitive\/play': 'Cognitive\/Play',\r\n      'language (comprehension)': 'Language (Comprehension)',\r\n      'receptive language': 'Language (Comprehension)',\r\n      'vc': 'Language (Comprehension)',\r\n      'language - comprehension': 'Language (Comprehension)',\r\n      'language (expression)': 'Language (Expression)',\r\n      'expressive language': 'Language (Expression)',\r\n      've': 'Language (Expression)',\r\n      'language - expression': 'Language (Expression)'\r\n    };\r\n    const newData = {};\r\n    for (const key of Object.keys(DATA)){\r\n      const canon = alias[key.trim().toLowerCase()] || key;\r\n      if (!newData[canon]) newData[canon] = [];\r\n      newData[canon] = newData[canon].concat(DATA[key]);\r\n    }\r\n    DATA = newData;\r\n  }\r\n\r\n  function removeRedFlagDomains(){\r\n    for (const key of Object.keys(DATA)){\r\n      if (\/red\\s*flag\/i.test(key)){ delete DATA[key]; }\r\n    }\r\n  }\r\n\r\n  \/\/ Split Language into Comprehension vs Expression if combined\r\n  function splitLanguageIfCombined(){\r\n    const generic = DATA['Language'] || DATA['Language (General)'] || null;\r\n    if (!generic) return;\r\n    const compOut = (DATA['Language (Comprehension)'] || []).slice();\r\n    const exprOut = (DATA['Language (Expression)'] || []).slice();\r\n    const compKW = ['understand','comprehend','follows','follow','responds to name','points to','where is','gives when asked','receptive','identify','recognizes','brings when asked','looks when called','show me'];\r\n    const exprKW = ['say','says','word','words','phrase','phrases','sentence','sentences','names','labels','speaks','vocalize','babbles','imitates sounds','asks','tells','uses','mama','dada'];\r\n    const compRe = new RegExp(compKW.map(esc).join('|'),'i');\r\n    const exprRe = new RegExp(exprKW.map(esc).join('|'),'i');\r\n    const compTmp=[], exprTmp=[];\r\n    (generic||[]).forEach(b=>{\r\n      const items = splitItems(b.items||[]);\r\n      const compItems=[], exprItems=[], unknown=[];\r\n      for (const it of items){\r\n        const isC = compRe.test(it), isE = exprRe.test(it);\r\n        if (isC && !isE) compItems.push(it);\r\n        else if (isE && !isC) exprItems.push(it);\r\n        else unknown.push(it);\r\n      }\r\n      if (unknown.length && !compItems.length && !exprItems.length) exprItems.push(...unknown);\r\n      if (compItems.length) compTmp.push({age_months:b.age_months, items:compItems});\r\n      if (exprItems.length) exprTmp.push({age_months:b.age_months, items:exprItems});\r\n    });\r\n    DATA['Language (Comprehension)'] = mergeByAge(compTmp.concat(compOut));\r\n    DATA['Language (Expression)']    = mergeByAge(exprTmp.concat(exprOut));\r\n    delete DATA['Language']; delete DATA['Language (General)'];\r\n\r\n    function mergeByAge(arr){ const map=new Map(); for(const x of arr){ if(!map.has(x.age_months)) map.set(x.age_months,{age_months:x.age_months,items:[]}); map.get(x.age_months).items=map.get(x.age_months).items.concat(x.items||[]);} return Array.from(map.values()); }\r\n    function esc(s){ return s.replace(\/[.*+?^${}()|[\\]\\\\]\/g,'\\\\$&'); }\r\n  }\r\n\r\n  \/\/ Place Language (Comp\/Expr) right after Fine\/Visual Motor\r\n  function reorderDomains(keys){\r\n    const comp='Language (Comprehension)', expr='Language (Expression)', fine='Fine\/Visual Motor';\r\n    const withoutLang = keys.filter(k=>k!==comp && k!==expr);\r\n    let order = withoutLang.slice();\r\n    let fineIndex = order.findIndex(k => k === fine || \/(fine.*motor|visual.*motor)\/i.test(k));\r\n    if (fineIndex === -1) fineIndex = order.findIndex(k => \/motor\/i.test(k));\r\n    const toInsert=[]; if (DATA[comp]) toInsert.push(comp); if (DATA[expr]) toInsert.push(expr);\r\n    if (toInsert.length){\r\n      if (fineIndex >= 0) order.splice(fineIndex+1, 0, ...toInsert);\r\n      else order.push(...toInsert);\r\n    }\r\n    return order;\r\n  }\r\n\r\n  function splitItems(items){\r\n    return items.flatMap(it => String(it).split(\/[;\u2022\\n\\r]+\/)).map(s=>s.trim()).filter(Boolean);\r\n  }\r\n\r\n  function startIndexForAge(bands, m){\r\n    let idx=0, found=false;\r\n    for(let i=0;i<bands.length;i++){ if(bands[i].age_months<=m){ idx=i; found=true; } else break; }\r\n    return found?idx:0;\r\n  }\r\n\r\n  function makeToggle(initialValue,onChange){\r\n    const wrap=document.createElement('div'); wrap.className='toggle';\r\n    const yes=document.createElement('button'); yes.textContent='Yes';\r\n    const no =document.createElement('button');  no.textContent='No';\r\n    let value=initialValue;\r\n    function sync(){ yes.classList.toggle('active',value===true); yes.classList.toggle('yes',value===true); no.classList.toggle('active',value===false); no.classList.toggle('no',value===false); }\r\n    yes.addEventListener('click',()=>{value=true; sync(); onChange?.(value);});\r\n    no .addEventListener('click',()=>{value=false;sync(); onChange?.(value);});\r\n    sync(); wrap.appendChild(yes); wrap.appendChild(no);\r\n    return {el:wrap, get:()=>value, set:v=>{value=v; sync();}};\r\n  }\r\n\r\n  function renderDomain(domain){\r\n    const cont=document.createElement('div'); cont.className='dev-card';\r\n    const st=domainState[domain]; const bands=DATA[domain]; const band=bands[st.index];\r\n\r\n    const header=document.createElement('div');\r\n    header.innerHTML=`<h3 class=\"dev-h\">${domain} <span class=\"pill\">Band: ${band.age_months} mo<\/span><\/h3>`;\r\n\r\n    const info=document.createElement('div'); info.className='muted small';\r\n    info.textContent='Answer all items (Yes\/No). The band evaluates automatically once all are answered.';\r\n\r\n    const qWrap=document.createElement('div'); qWrap.className='dev-col';\r\n\r\n    const items=splitItems(band.items); st.currentAnswers=[];\r\n    if(!st.answerMap[band.age_months]) st.answerMap[band.age_months]={};\r\n\r\n    function tryAuto(){ \r\n      const answered=st.currentAnswers.map(t=>t.get()).filter(v=>v===true||v===false).length; \r\n      updateConclusion(); \/\/ live update\r\n      if(answered===st.currentAnswers.length && st.currentAnswers.length>0){ autoEval(); } \r\n    }\r\n\r\n    items.forEach(txt=>{\r\n      const saved=st.answerMap[band.age_months][txt] ?? null;\r\n      const row=document.createElement('div'); row.className='q-row';\r\n      const q=document.createElement('div'); q.className='q-text'; q.textContent=txt;\r\n      const t=makeToggle(saved,(val)=>{ st.answerMap[band.age_months][txt]=val; tryAuto(); });\r\n      st.currentAnswers.push(t);\r\n      row.appendChild(q); row.appendChild(t.el); qWrap.appendChild(row);\r\n    });\r\n\r\n    cont.appendChild(header); cont.appendChild(info); cont.appendChild(qWrap);\r\n\r\n    function autoEval(){\r\n      const vals=st.currentAnswers.map(t=>t.get());\r\n      const yes=vals.filter(v=>v===true).length;\r\n      const no =vals.filter(v=>v===false).length;\r\n      const prop=(yes+no)>0 ? yes\/(yes+no) : 0;\r\n      st.history.push({index:st.index, age:band.age_months, yes, no, prop});\r\n      if (prop>=PASS_THRESHOLD){\r\n        st.lastPass={index:st.index, age:band.age_months, yes, no, prop};\r\n        st.floor=band.age_months; st.isAppropriate=(st.startBandAge===st.floor);\r\n        finalizeDomain(domain);\r\n      } else {\r\n        if (st.index>0){ st.lastFail={index:st.index, age:band.age_months, yes, no, prop}; st.index--; rerenderDomain(domain); }\r\n        else { st.floor=null; st.isAppropriate=false; finalizeDomain(domain); }\r\n      }\r\n    }\r\n    return cont;\r\n  }\r\n\r\n  function finalizeDomain(domain){\r\n    completedDomains.add(domain);\r\n    rerenderAll();\r\n    updateConclusion();\r\n  }\r\n\r\n  function rerenderDomain(domain){\r\n    const area=el('workArea'); const idx=domainOrder.indexOf(domain); const card=renderDomain(domain);\r\n    if(area.children[idx]) area.replaceChild(card, area.children[idx]); else area.appendChild(card);\r\n    updateConclusion();\r\n  }\r\n\r\n  function rerenderAll(){\r\n    const area=el('workArea'); area.innerHTML='';\r\n    for(const d of domainOrder){ area.appendChild(renderDomain(d)); }\r\n    updateConclusion();\r\n  }\r\n\r\n  \/\/ Age phrase (single age)\r\n  function fmtAgePhrase(m){\r\n    if (m==null) return '\u2014';\r\n    if (m<12) return `${m} months old`;\r\n    const y=Math.floor(m\/12), mo=m%12;\r\n    return mo ? `${y} years ${mo} months old` : `${y} years old`;\r\n  }\r\n\r\n  \/\/ Age phrase without \"old\" (for compact range endpoints)\r\n  function fmtAgeNoOld(m){\r\n    if (m==null) return '\u2014';\r\n    if (m<12) return `${m} months`;\r\n    const y=Math.floor(m\/12), mo=m%12;\r\n    return mo ? `${y} years ${mo} months` : `${y} years`;\r\n  }\r\n\r\n  \/\/ Compact range: \"4\u20136 months\" if both <12; \"3\u20134 years\" if both exact years; else \"X \u2013 Y\" without \"old\"\r\n  function fmtRangeShort(low, high){\r\n    if (low==null || high==null) return '\u2014';\r\n    if (low<12 && high<12) return `${low}\\u2013${high} months`;\r\n    const ly=Math.floor(low\/12), lmo=low%12, hy=Math.floor(high\/12), hmo=high%12;\r\n    if (lmo===0 && hmo===0) return `${ly}\\u2013${hy} years`;\r\n    return `${fmtAgeNoOld(low)} \u2013 ${fmtAgeNoOld(high)}`;\r\n  }\r\n\r\n  \/\/ Answered-item recap as: \"Item +\" for Yes, \"Item -\" for No\r\n  function buildAnsweredRecap(){\r\n    const lines=[];\r\n    for(const domain of domainOrder){\r\n      const st = domainState[domain];\r\n      if(!st) continue;\r\n      const pieces=[];\r\n      const seen = new Map(); \/\/ last answer wins\r\n      for (const ageKey of Object.keys(st.answerMap)){\r\n        const bandMap = st.answerMap[ageKey];\r\n        for (const itemText of Object.keys(bandMap)){\r\n          const val = bandMap[itemText];\r\n          if (val===true || val===false) seen.set(itemText, val);\r\n        }\r\n      }\r\n      seen.forEach((val, text)=>{\r\n        pieces.push(val===true ? `${text} +` : `${text} -`);\r\n      });\r\n      if (pieces.length){\r\n        lines.push(`${domain}: ${pieces.join(', ')}`);\r\n      }\r\n    }\r\n    return lines.join('\\n');\r\n  }\r\n\r\n  \/\/ Borderline range: lower band passed \u226560% AND next higher tested band is 40\u2013<60%\r\n  function borderlineRange(st){\r\n    if (st.floor==null) return null;\r\n    const lower = st.history.find(h => h.age === st.floor && h.prop >= PASS_THRESHOLD);\r\n    if (!lower) return null;\r\n    \/\/ immediate higher band that was tested\r\n    const higherCandidates = st.history.filter(h => h.age > st.floor).sort((a,b)=>a.age-b.age);\r\n    if (!higherCandidates.length) return null;\r\n    const higher = higherCandidates[0];\r\n    if (higher.prop >= BORDERLINE_LOWER && higher.prop < PASS_THRESHOLD){\r\n      return { low: st.floor, high: higher.age };\r\n    }\r\n    return null;\r\n  }\r\n\r\n  function computeConclusionLines(){\r\n    const rows=[];\r\n    for(const domain of domainOrder){\r\n      const st=domainState[domain];\r\n      const isDone = completedDomains.has(domain);\r\n      let label = '\u2014';\r\n\r\n      if (isDone){\r\n        if (st.floor==null){\r\n          label = 'Below first band';\r\n        } else {\r\n          const range = borderlineRange(st);\r\n          if (range){\r\n            label = fmtRangeShort(range.low, range.high);\r\n          } else {\r\n            label = fmtAgePhrase(st.floor);\r\n            if (st.isAppropriate) label += ' (Appropriate to age)';\r\n          }\r\n        }\r\n      }\r\n\r\n      rows.push(`${domain}: ${label}`);\r\n    }\r\n    return rows.join('\\n');\r\n  }\r\n\r\n  \/\/ Live conclusion box (always visible after age is chosen)\r\n  function updateConclusion(){\r\n    const card = el('conclusionCard');\r\n    const answered = buildAnsweredRecap();\r\n    const conclusion = computeConclusionLines();\r\n    const combinedForQR = (answered ? answered + '\\n\\n' : '') + '--- Summary ---\\n' + conclusion;\r\n\r\n    el('answeredSummary').textContent = answered || 'No items answered yet.';\r\n    el('conclusionSummary').textContent = conclusion || '\u2014';\r\n\r\n    \/\/ QR via image (no external scripts needed)\r\n    const qrBox = el('qrSummary'); qrBox.innerHTML = '';\r\n    const img = document.createElement('img');\r\n    img.alt = 'QR code of summary';\r\n    img.width = 256; img.height = 256; img.decoding = 'async'; img.loading = 'lazy';\r\n    img.src = 'https:\/\/api.qrserver.com\/v1\/create-qr-code\/?size=256x256&ecc=M&data=' + encodeURIComponent(combinedForQR);\r\n    qrBox.appendChild(img);\r\n\r\n    card.style.display = 'block';\r\n  }\r\n\r\n  function initDomainState(){\r\n    domainState={}; completedDomains=new Set();\r\n    for(const domain of domainOrder){\r\n      const bands=DATA[domain];\r\n      const startIdx=startIndexForAge(bands, ageMonths);\r\n      domainState[domain] = {\r\n        index:startIdx, history:[], lastPass:null, lastFail:null,\r\n        floor:null, answerMap:{}, startBandAge: bands[startIdx].age_months,\r\n        isAppropriate:false\r\n      };\r\n    }\r\n  }\r\n\r\n  function finalizeDomain(domain){\r\n    completedDomains.add(domain);\r\n    rerenderAll();\r\n    updateConclusion();\r\n  }\r\n\r\n  function rerenderDomain(domain){\r\n    const area=el('workArea'); const idx=domainOrder.indexOf(domain); const card=renderDomain(domain);\r\n    if(area.children[idx]) area.replaceChild(card, area.children[idx]); else area.appendChild(card);\r\n    updateConclusion();\r\n  }\r\n\r\n  function rerenderAll(){\r\n    const area=el('workArea'); area.innerHTML='';\r\n    for(const d of domainOrder){ area.appendChild(renderDomain(d)); }\r\n    updateConclusion();\r\n  }\r\n\r\n  function start(){ initDomainState(); el('resetBtn').style.display='inline-block'; rerenderAll(); }\r\n  function reset(){ el('workArea').innerHTML=''; el('conclusionSummary').textContent=''; el('answeredSummary').textContent=''; el('qrSummary').innerHTML=''; el('conclusionCard').style.display='none'; el('resetBtn').style.display='none'; }\r\n\r\n  async function onAgeChange(){\r\n    ageMonths = parseFloat(el('ageMonthsSelect').value);\r\n    if (!Number.isFinite(ageMonths) || ageMonths < 0) return;\r\n    try{ if(!DATA){ await loadData(); } reset(); start(); }catch(e){ alert(e.message); }\r\n  }\r\n\r\n  \/\/ Boot\r\n  buildAgeDropdown();\r\n  el('ageMonthsSelect').addEventListener('change', onAgeChange);\r\n})();\r\n<\/script>\r\n\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"tags":[],"class_list":["post-399","page","type-page","status-publish","article"],"_links":{"self":[{"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/pages\/399","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tomchan.hk\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=399"}],"version-history":[{"count":1,"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/pages\/399\/revisions"}],"predecessor-version":[{"id":400,"href":"https:\/\/tomchan.hk\/index.php?rest_route=\/wp\/v2\/pages\/399\/revisions\/400"}],"wp:attachment":[{"href":"https:\/\/tomchan.hk\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=399"}],"wp:term":[{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tomchan.hk\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=399"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}