{"id":1125,"date":"2026-07-28T15:45:12","date_gmt":"2026-07-28T07:45:12","guid":{"rendered":"https:\/\/bali.business\/en\/listing-health-check\/"},"modified":"2026-07-28T15:45:12","modified_gmt":"2026-07-28T07:45:12","slug":"listing-health-check","status":"publish","type":"page","link":"https:\/\/bali.business\/ko\/listing-health-check\/","title":{"rendered":"Listing Health Check"},"content":{"rendered":"<p>Is your business listed on bali.business? Find it below and see exactly what is missing \u2014 then claim it so customers can reach you.<\/p>\n\t<div id=\"bbh-1\" class=\"bbh\" data-api=\"https:\/\/bali.business\/ko\/wp-json\/balibiz\/v1\/health\"\n\t     data-claim=\"https:\/\/bali.business\/ko\/claim-listing\/\"\n\t     data-add=\"https:\/\/bali.business\/ko\/my-profile\/?tab=add-listing\"\n\t     data-manage=\"https:\/\/bali.business\/ko\/my-profile\/\"\n\t     data-i18n=\"{&quot;too_short&quot;:&quot;Please type at least 3 characters.&quot;,&quot;searching&quot;:&quot;Searching\\u2026&quot;,&quot;search_failed&quot;:&quot;Search failed.&quot;,&quot;not_found_title&quot;:&quot;We could not find that business.&quot;,&quot;not_found_body&quot;:&quot;If it is not listed yet, you can add it free \\u2014 it takes a few minutes.&quot;,&quot;add_free&quot;:&quot;Add your business free&quot;,&quot;loading&quot;:&quot;Loading\\u2026&quot;,&quot;load_failed&quot;:&quot;Could not load that listing.&quot;,&quot;fix_these&quot;:&quot;Fix these first&quot;,&quot;complete&quot;:&quot;This listing looks complete.&quot;,&quot;already_good&quot;:&quot;Already good&quot;,&quot;claim_cta&quot;:&quot;This is my business \\u2014 claim it&quot;,&quot;claimed_note&quot;:&quot;This listing is already claimed.&quot;,&quot;manage_cta&quot;:&quot;Sign in to update it&quot;,&quot;network_error&quot;:&quot;Network error. Please try again.&quot;,&quot;open_listing&quot;:&quot;Open listing details&quot;,&quot;back_results&quot;:&quot;Back to results&quot;}\">\n\t\t<form class=\"bbh-form\" autocomplete=\"off\">\n\t\t\t<label class=\"bbh-label\" for=\"bbh-q\">Search your business name<\/label>\n\t\t\t<div class=\"bbh-row\">\n\t\t\t\t<input id=\"bbh-q\" class=\"bbh-input\" type=\"search\" minlength=\"3\"\n\t\t\t\t       placeholder=\"e.g. Cantika Zest Spa\" \/>\n\t\t\t\t<button class=\"bbh-btn\" type=\"submit\">Check<\/button>\n\t\t\t<\/div>\n\t\t\t<p class=\"bbh-hint\">Type at least 3 characters.<\/p>\n\t\t<\/form>\n\t\t<div class=\"bbh-out\" aria-live=\"polite\"><\/div>\n\t<\/div>\n\n\t<style>\n\t.bbh{max-width:720px;margin:0 auto;font-size:16px}\n\t.bbh-row{display:flex;gap:8px;flex-wrap:wrap}\n\t.bbh-input{flex:1 1 240px;padding:12px 14px;border:1px solid #d8d8d8;border-radius:8px;font-size:16px}\n\t\/*\n\t * Colour\/specificity note (applies to .bbh-btn, .bbh-card, .bbh-cta below):\n\t * this theme's own CSS targets bare `button` \/ `a` elements, beating this\n\t * shortcode's original bare `.bbh-card{...}` \/ `.bbh-cta{...}` rules and\n\t * repainting them in the theme's own orange-on-orange palette --\n\t * rgb(221,110,66) bg with rgb(112,112,112) text on cards (1.51:1),\n\t * rgb(221,110,66) text on the claim CTA's rgb(245,166,35) bg (1.62:1).\n\t * Both measured well under WCAG AA's 4.5:1 minimum for text.\n\t *\n\t * Scoping alone (`.bbh .bbh-card` etc., a descendant selector, two\n\t * classes) is NOT sufficient here and was verified insufficient before\n\t * reaching for !important: inspecting the live page's matched CSS rules\n\t * (document.styleSheets, filtered to rules whose cssText contains\n\t * \"!important\") found the theme declares\n\t *   button, .button, input[type=\"submit\"], .widget button, [... ~20\n\t *   more selectors, none matching .bbh-cta] {\n\t *     background: var(--bb-sunset) !important;\n\t *     border-color: var(--bb-sunset) !important;\n\t *   }\n\t * and separately\n\t *   a, button { outline: none !important; }\n\t * !important always wins over a non-!important rule regardless of\n\t * specificity, so `.bbh .bbh-card{background:#fff}` (no !important)\n\t * could never beat that rule no matter how it was scoped -- confirmed\n\t * live: after deploying the scoping-only version, .bbh-card's computed\n\t * background was still rgb(221,110,66), and a focused card's computed\n\t * outlineStyle was \"none\" (the custom focus ring was completely\n\t * invisible). Both `<button>`-based elements below (.bbh-btn, .bbh-card)\n\t * match that theme rule and need !important on the properties it forces\n\t * (background\/background-color\/border-color, and outline for the focus\n\t * ring). `.bbh-cta` is a plain `<a>` with no `.button` class, does NOT\n\t * match that selector list, and was confirmed live to already render\n\t * correctly without !important -- so !important is applied only where\n\t * proven necessary, per \"prefer specificity, !important only if scoping\n\t * genuinely proves insufficient.\"\n\t *\n\t * Separately: this shortcode's OWN originally-designed CTA colours\n\t * (#fff text on #f5a623) measure only 2.03:1 -- a pre-existing design\n\t * flaw, not something the theme broke. #4e3204 is a darkened version of\n\t * the same brand orange (#f5a623) at the same hue\/saturation, just much\n\t * lower lightness, chosen so the buttons still read as \"this site's\n\t * orange\" rather than an unrelated colour. #4e3204 on #f5a623 measures\n\t * 5.81:1, comfortably over 4.5:1. Used for both .bbh-btn (search) and\n\t * .bbh-cta (claim) so the two orange buttons in this widget stay\n\t * visually consistent with each other.\n\t *\/\n\t.bbh .bbh-btn{padding:12px 22px;border:0 !important;border-radius:8px;background:#f5a623 !important;background-color:#f5a623 !important;color:#4e3204;font-weight:600;cursor:pointer;font-size:16px}\n\t.bbh .bbh-btn:hover{filter:brightness(.95)}\n\t.bbh-label{display:block;font-weight:600;margin-bottom:6px}\n\t.bbh-hint{color:#777;font-size:13px;margin:6px 0 0}\n\t.bbh .bbh-card{box-sizing:border-box;width:100%;display:flex;justify-content:space-between;gap:12px;align-items:center;border:1px solid #eee;border-color:#eee !important;border-radius:10px;padding:14px;margin:10px 0;cursor:pointer;font:inherit;text-align:left;background:#fff !important;background-color:#fff !important;color:#707070;appearance:none;-webkit-appearance:none}\n\t.bbh .bbh-card:hover{border-color:#f5a623 !important}\n\t\/* Focus ring recoloured from the original #f5a623 (only 2.03:1 against\n\t * the now-white card background, failing the 3:1 non-text\/UI-component\n\t * contrast minimum) to #4e3204 (11.78:1 against white). !important is\n\t * required here too: the theme's `a, button{outline:none!important}`\n\t * (see note above) otherwise removes the ring entirely -- confirmed\n\t * live, computed outlineStyle was \"none\" before this was added. *\/\n\t.bbh .bbh-card:focus-visible{outline:2px solid #4e3204 !important;outline-offset:2px}\n\t.bbh-card-info{display:block}\n\t.bbh-name{font-weight:600;display:block}\n\t\/* #777 on white measures 4.478:1 -- just under the 4.5:1 AA minimum\n\t * (pre-existing, not theme-caused; found while verifying every piece of\n\t * text that sits on the card background this round is actually\n\t * readable). Bumped to #707070 (4.95:1, same shade already used for the\n\t * card's own text colour above) to close that gap. *\/\n\t.bbh-meta{color:#707070;font-size:13px;display:block}\n\t.bbh-score{font-weight:700;font-size:20px;min-width:64px;text-align:right}\n\t\/* .bbh-score renders at 20px\/700 weight, which qualifies as WCAG \"large\n\t * text\" (>=18.66px bold), so the applicable minimum is 3:1, not 4.5:1.\n\t * .bbh-good (#1e9e5a, 3.45:1) and .bbh-bad (#d1442f, 4.59:1) both already\n\t * clear that bar on white -- left untouched. .bbh-mid (the original\n\t * #e08a00) measured only 2.69:1, failing even the 3:1 large-text\n\t * minimum. Replaced with #a36500: same hue and saturation as the\n\t * original (37 deg, 100% -- only lightness dropped, 44% -> 32%), so it\n\t * reads as the same amber, just darker, rather than an unrelated\n\t * colour. #a36500 measures 4.75:1 on white -- clears even the stricter\n\t * 4.5:1 normal-text threshold, so it stays compliant even if this\n\t * badge's font-size or weight ever changes. Keeping the same hue as the\n\t * original also maximises its hue separation from .bbh-bad's red (37\n\t * deg vs 7.8 deg = ~29 deg apart) while remaining nothing like\n\t * .bbh-good's green (148 deg) -- verified distinct from both, not just\n\t * passing its own contrast check in isolation. *\/\n\t.bbh-good{color:#1e9e5a}.bbh-mid{color:#a36500}.bbh-bad{color:#d1442f}\n\t.bbh-bar{height:10px;background:#eee;border-radius:6px;overflow:hidden;margin:10px 0 16px}\n\t.bbh-bar > i{display:block;height:100%;background:#1e9e5a}\n\t.bbh-item{display:flex;gap:10px;padding:9px 0;border-bottom:1px solid #f2f2f2}\n\t.bbh-item:last-child{border-bottom:0}\n\t.bbh-ico{flex:0 0 20px;font-weight:700}\n\t\/* color needs !important here too, discovered on the FIRST live re-check\n\t * after this round's initial deploy (a gap in the earlier verification\n\t * pass, which only searched for rules matching the *background*\n\t * !important selector list and concluded -- wrongly -- that .bbh-cta's\n\t * color needed no !important). Live inspection of matched CSS rules\n\t * found a SEPARATE theme rule: `a, a:hover, ... { color:\n\t * var(--bb-sunset) !important; }`, which matches any bare <a> including\n\t * this one and was still forcing rgb(221,110,66) text -- the exact\n\t * 1.62:1 defect this round set out to fix, still present after the\n\t * first (incomplete) deploy of this round's fix. *\/\n\t.bbh .bbh-cta{display:inline-block;margin-top:16px;padding:12px 20px;background:#f5a623;background-color:#f5a623;color:#4e3204 !important;border-radius:8px;text-decoration:none;font-weight:600}\n\t.bbh-empty{padding:16px;background:#fafafa;border-radius:10px}\n\t\/* Shown instead of the claim CTA when the listing is already claimed.\n\t * #1e5c33 on #eef7f1 measures 8.1:1, well over the 4.5:1 AA minimum, and\n\t * is the same green family as .bbh-good so a \"this is settled\" state\n\t * reads consistently with the per-check pass ticks above it. *\/\n\t.bbh .bbh-claimed{margin:16px 0 0;padding:12px 14px;background:#eef7f1;border-radius:8px;color:#1e5c33}\n\t.bbh .bbh-back{margin:0 0 12px;padding:0;border:0 !important;background:transparent !important;background-color:transparent !important;color:#4e3204 !important;font:inherit;font-weight:600;cursor:pointer;text-decoration:underline}\n\t.bbh .bbh-back:focus-visible{outline:2px solid #4e3204 !important;outline-offset:2px}\n\t<\/style>\n\n\t<script>\n\t(function(){\n\t  \/\/ getElementById \u2014 not document.currentScript. Caching\/optimisation\n\t  \/\/ plugins defer, async, or relocate inline scripts; currentScript is\n\t  \/\/ null then, and `.parentNode` on null throws before any null guard.\n\t  var root=document.getElementById(\"bbh-1\");\n\t  if(!root) return;\n\t  var api=root.dataset.api, claim=root.dataset.claim, add=root.dataset.add, manage=root.dataset.manage;\n\t  var I18N={};\n\t  try{ I18N=JSON.parse(root.dataset.i18n||'{}'); }catch(e){ I18N={}; }\n\t  var out=root.querySelector('.bbh-out'), form=root.querySelector('.bbh-form'), q=root.querySelector('#bbh-q');\n\t  var lastRows=null;\n\t  function cls(s){return s>=80?'bbh-good':(s>=50?'bbh-mid':'bbh-bad');}\n\t  \/\/ SAFE FOR ELEMENT-TEXT CONTEXT ONLY. esc() round-trips through\n\t  \/\/ textContent -> innerHTML, which escapes & < > for safe insertion as\n\t  \/\/ HTML text. It does NOT escape quote characters, so its output must\n\t  \/\/ NEVER be placed inside an HTML attribute value (e.g.\n\t  \/\/ title=\"'+esc(x)+'\"\") -- a name containing a double quote would break\n\t  \/\/ out of the attribute and inject markup. For attribute values built\n\t  \/\/ from dynamic data, use element.setAttribute(...) instead (see the\n\t  \/\/ aria-label set on each result card below), which is escaping-safe\n\t  \/\/ by construction regardless of what characters the value contains.\n\t  function esc(s){var d=document.createElement('div');d.textContent=s==null?'':s;return d.innerHTML;}\n\n\t  function renderResults(rows){\n\t    lastRows=rows;\n\t    out.innerHTML=rows.map(function(r){\n\t      return '<button type=\"button\" class=\"bbh-card\" data-id=\"'+r.id+'\">'+\n\t        '<span class=\"bbh-card-info\"><span class=\"bbh-name\">'+esc(r.name)+'<\/span>'+\n\t        '<span class=\"bbh-meta\">'+esc([r.category,r.region].filter(Boolean).join(' \u00b7 '))+'<\/span><\/span>'+\n\t        '<span class=\"bbh-score '+cls(r.score)+'\">'+r.score+'<\/span><\/button>';\n\t    }).join('');\n\t    Array.prototype.forEach.call(out.querySelectorAll('.bbh-card'),function(c,i){\n\t      var r=rows[i];\n\t      \/\/ setAttribute, not esc()+string-concat: r.name can contain a\n\t      \/\/ double quote (see the esc() guard comment above), and\n\t      \/\/ setAttribute assigns the raw value with no HTML parsing\n\t      \/\/ involved, so it is safe regardless of what characters r.name\n\t      \/\/ contains. Native <button> is natively keyboard-operable\n\t      \/\/ (focusable, Enter\/Space both activate it) with no extra\n\t      \/\/ keydown handling required.\n\t      c.setAttribute('aria-label',(I18N.open_listing||'Open listing details')+': '+r.name+' ('+r.score+'\/100)');\n\t      c.addEventListener('click',function(){detail(c.dataset.id);});\n\t    });\n\t  }\n\n\t  form.addEventListener('submit',function(e){\n\t    e.preventDefault();\n\t    var term=q.value.trim();\n\t    if(term.length<3){out.innerHTML='<p class=\"bbh-hint\">'+esc(I18N.too_short||'Please type at least 3 characters.')+'<\/p>';return;}\n\t    out.innerHTML='<p class=\"bbh-hint\">'+esc(I18N.searching||'Searching\u2026')+'<\/p>';\n\t    fetch(api+'\/search?q='+encodeURIComponent(term))\n\t      .then(function(r){return r.json().then(function(b){return {ok:r.ok,body:b};});})\n\t      .then(function(res){\n\t        if(!res.ok){out.innerHTML='<p class=\"bbh-hint\">'+esc((res.body&&res.body.message)||I18N.search_failed||'Search failed.')+'<\/p>';return;}\n\t        var rows=res.body.results||[];\n\t        if(!rows.length){\n\t          lastRows=null;\n\t          out.innerHTML='<div class=\"bbh-empty\"><p><strong>'+esc(I18N.not_found_title||'We could not find that business.')+'<\/strong><\/p>'+\n\t            '<p>'+esc(I18N.not_found_body||'If it is not listed yet, you can add it free \u2014 it takes a few minutes.')+'<\/p>'+\n\t            '<a class=\"bbh-cta\" href=\"'+add+'\">'+esc(I18N.add_free||'Add your business free')+'<\/a><\/div>';\n\t          return;\n\t        }\n\t        renderResults(rows);\n\t      })\n\t      .catch(function(){out.innerHTML='<p class=\"bbh-hint\">'+esc(I18N.network_error||'Network error. Please try again.')+'<\/p>';});\n\t  });\n\n\t  function detail(id){\n\t    out.innerHTML='<p class=\"bbh-hint\">'+esc(I18N.loading||'Loading\u2026')+'<\/p>';\n\t    fetch(api+'\/'+id)\n\t      .then(function(r){return r.json().then(function(b){return {ok:r.ok,body:b};});})\n\t      .then(function(res){\n\t        if(!res.ok){out.innerHTML='<p class=\"bbh-hint\">'+esc((res.body&&res.body.message)||I18N.load_failed||'Could not load that listing.')+'<\/p>';return;}\n\t        var d=res.body;\n\t        if(!d||typeof d.score!=='number'){out.innerHTML='<p class=\"bbh-hint\">'+esc(I18N.load_failed||'Could not load that listing.')+'<\/p>';return;}\n\t        var fixes=(d.fixes||[]).map(function(f){\n\t          return '<div class=\"bbh-item\"><span class=\"bbh-ico bbh-bad\">\u2715<\/span><span><strong>'+esc(f.label)+'<\/strong><br>'+esc(f.tip)+'<\/span><\/div>';\n\t        }).join('');\n\t        var passed=(d.checks||[]).filter(function(c){return c.ok;}).map(function(c){\n\t          return '<div class=\"bbh-item\"><span class=\"bbh-ico bbh-good\">\u2713<\/span><span>'+esc(c.label)+'<\/span><\/div>';\n\t        }).join('');\n\t        \/\/ Claimed-vs-unclaimed tail. The detail endpoint has always returned\n\t        \/\/ `claimed`, but nothing in this script ever read it, so the \"This\n\t        \/\/ is my business \u2014 claim it\" CTA rendered unconditionally --\n\t        \/\/ including for an owner whose listing is ALREADY claimed, routing\n\t        \/\/ them back into a flow they have already completed. Use the field:\n\t        \/\/ when claimed is true, confirm that and point at the owner\n\t        \/\/ dashboard to update it instead. Both new strings resolve through\n\t        \/\/ I18N like every other JS-injected string here (see the $i18n note\n\t        \/\/ in render_tool(): anything injected after the initial page render\n\t        \/\/ is invisible to TranslatePress's output buffer and must be\n\t        \/\/ resolved server-side through __()).\n\t        var tail = d.claimed\n\t          ? '<p class=\"bbh-claimed\"><strong>'+esc(I18N.claimed_note||'This listing is already claimed.')+'<\/strong><\/p>'+\n\t            (manage?'<a class=\"bbh-cta\" href=\"'+manage+'\">'+esc(I18N.manage_cta||'Sign in to update it')+'<\/a>':'')\n\t          : '<a class=\"bbh-cta\" href=\"'+claim+'\">'+esc(I18N.claim_cta||'This is my business \u2014 claim it')+'<\/a>';\n\t        var back = lastRows\n\t          ? '<p><button type=\"button\" class=\"bbh-back\">'+esc(I18N.back_results||'Back to results')+'<\/button><\/p>'\n\t          : '';\n\t        out.innerHTML=back+\n\t          '<h3>'+esc(d.name)+'<\/h3>'+\n\t          '<p class=\"bbh-score '+cls(d.score)+'\" style=\"text-align:left\">'+d.score+' \/ 100<\/p>'+\n\t          '<div class=\"bbh-bar\"><i style=\"width:'+d.score+'%\"><\/i><\/div>'+\n\t          (fixes?'<h4>'+esc(I18N.fix_these||'Fix these first')+'<\/h4>'+fixes:'<p><strong>'+esc(I18N.complete||'This listing looks complete.')+'<\/strong><\/p>')+\n\t          (passed?'<h4>'+esc(I18N.already_good||'Already good')+'<\/h4>'+passed:'')+\n\t          tail;\n\t        var backBtn=out.querySelector('.bbh-back');\n\t        if(backBtn){ backBtn.addEventListener('click',function(){ renderResults(lastRows); }); }\n\t      })\n\t      .catch(function(){out.innerHTML='<p class=\"bbh-hint\">'+esc(I18N.network_error||'Network error. Please try again.')+'<\/p>';});\n\t  }\n\t})();\n\t<\/script>\n\t\n","protected":false},"excerpt":{"rendered":"<p>Is your business listed on bali.business? Find it below and see exactly what is missing \u2014 then claim it so customers can reach you.<\/p>","protected":false},"author":867,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-1125","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/bali.business\/ko\/wp-json\/wp\/v2\/pages\/1125","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bali.business\/ko\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/bali.business\/ko\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/bali.business\/ko\/wp-json\/wp\/v2\/users\/867"}],"replies":[{"embeddable":true,"href":"https:\/\/bali.business\/ko\/wp-json\/wp\/v2\/comments?post=1125"}],"version-history":[{"count":0,"href":"https:\/\/bali.business\/ko\/wp-json\/wp\/v2\/pages\/1125\/revisions"}],"wp:attachment":[{"href":"https:\/\/bali.business\/ko\/wp-json\/wp\/v2\/media?parent=1125"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}