{"id":180448,"date":"2026-09-08T05:41:55","date_gmt":"2026-09-08T05:41:55","guid":{"rendered":"http:\/\/1.234.83.56:65000\/?page_id=180448"},"modified":"2026-09-11T06:39:41","modified_gmt":"2026-09-11T06:39:41","slug":"calculator","status":"publish","type":"page","link":"https:\/\/hb-global.shop\/th\/calculator\/","title":{"rendered":"Calculator"},"content":{"rendered":"<div class=\"wpb-content-wrapper\"><div data-parent=\"true\" class=\"vc_row row-container\" id=\"calculator_row\"><div class=\"row full-width row-parent\"><div class=\"wpb_row row-inner\"><div class=\"wpb_column pos-top pos-center align_left column_parent col-lg-12 single-internal-gutter\"><div class=\"uncol style-light\"  ><div class=\"uncoltable\"><div class=\"uncell no-block-padding\" ><div class=\"uncont\" >\n\t<div class=\"wpb_raw_code wpb_raw_html \" >\n\t\t<div class=\"wpb_wrapper\">\n\t\t\t<!-- Scale calculator for WordPress Custom HTML block (no html\/head\/body) -->\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\" \/>\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin \/>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=IBM+Plex+Sans:wght@400;500;600;700&family=Syne:wght@700;800&display=swap\" rel=\"stylesheet\" \/>\n<style>\n\n    :root {\n      --ink: #14201c;\n      --ink-soft: #3d524a;\n      --panel: #f3f6f4;\n      --panel-strong: #ffffff;\n      --line: #c5d4cc;\n      --accent: #1f6f56;\n      --accent-soft: #7ec8a3;\n      --warn: #c45c26;\n      --canvas-void: #0f1a16;\n      --glow: rgba(126, 200, 163, 0.18);\n      --font-display: \"Syne\", sans-serif;\n      --font-body: \"IBM Plex Sans\", sans-serif;\n    }\n\n    .cheokdo-root, .cheokdo-root * { box-sizing: border-box; }\n\n    .cheokdo-root {\n      margin: 0;\n      color: var(--ink);\n      font-family: var(--font-body);\n      background:\n        radial-gradient(1200px 600px at 10% -10%, rgba(126, 200, 163, 0.22), transparent 55%),\n        radial-gradient(900px 500px at 100% 0%, rgba(232, 197, 71, 0.12), transparent 45%),\n        linear-gradient(165deg, #e8eee9 0%, #d5e0da 48%, #c9d7d0 100%);\n      -webkit-font-smoothing: antialiased;\n      touch-action: manipulation;\n      border-radius: 1rem;\n    }\n\n    .cheokdo-root button,\n    .cheokdo-root input,\n    .cheokdo-root select { font: inherit; }\n    .cheokdo-root button { cursor: pointer; }\n\n    \/* Reset WordPress\/theme select styling (prevents repeating caret icons) *\/\n    .cheokdo-root select {\n      -webkit-appearance: none !important;\n      -moz-appearance: none !important;\n      appearance: none !important;\n      background-color: #fff !important;\n      background-image: linear-gradient(45deg, transparent 50%, #3d524a 50%),\n        linear-gradient(135deg, #3d524a 50%, transparent 50%) !important;\n      background-position: calc(100% - 14px) calc(50% - 2px), calc(100% - 9px) calc(50% - 2px) !important;\n      background-size: 5px 5px, 5px 5px !important;\n      background-repeat: no-repeat !important;\n      box-shadow: none !important;\n      text-indent: 0 !important;\n      text-overflow: clip !important;\n      max-width: 100%;\n    }\n    .cheokdo-root select::-ms-expand { display: none; }\n    .sr-only {\n      position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;\n      overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;\n    }\n\n    @keyframes fade-up {\n      from { opacity: 0; transform: translateY(12px); }\n      to { opacity: 1; transform: translateY(0); }\n    }\n    @keyframes pulse-ring {\n      0% { box-shadow: 0 0 0 0 rgba(31, 111, 86, 0.35); }\n      70% { box-shadow: 0 0 0 12px rgba(31, 111, 86, 0); }\n      100% { box-shadow: 0 0 0 0 rgba(31, 111, 86, 0); }\n    }\n\n    .app {\n      max-width: 1120px;\n      margin: 0 auto;\n      padding: 1rem;\n      display: flex;\n      flex-direction: column;\n      gap: 1rem;\n      min-height: 0;\n    }\n    @media (min-width: 640px) {\n      .app { padding: 1.5rem 1.25rem; gap: 1.25rem; }\n    }\n\n    .header {\n      display: flex;\n      flex-direction: column;\n      gap: 0.75rem;\n      animation: fade-up 0.55s ease-out both;\n    }\n    @media (min-width: 640px) {\n      .header {\n        flex-direction: row;\n        align-items: flex-end;\n        justify-content: space-between;\n      }\n    }\n    .brand {\n      font-family: var(--font-display);\n      font-size: clamp(2.25rem, 6vw, 3rem);\n      font-weight: 800;\n      letter-spacing: -0.03em;\n      margin: 0;\n      line-height: 1;\n    }\n    .tagline {\n      margin: 0.4rem 0 0;\n      max-width: 36rem;\n      color: var(--ink-soft);\n      font-size: 0.95rem;\n      line-height: 1.5;\n    }\n\n    .upload-btn {\n      display: inline-flex;\n      align-items: center;\n      justify-content: center;\n      gap: 0.5rem;\n      border-radius: 0.85rem;\n      background: var(--accent);\n      color: #fff;\n      font-weight: 600;\n      font-size: 0.9rem;\n      padding: 0.85rem 1.1rem;\n      box-shadow: 0 10px 30px var(--glow);\n      animation: pulse-ring 2.4s ease-out infinite;\n      border: 0;\n      user-select: none;\n    }\n    .upload-btn.drag { outline: 2px solid var(--accent-soft); outline-offset: 2px; }\n    .upload-btn:hover { filter: brightness(1.08); }\n\n    .error {\n      display: none;\n      border-radius: 0.85rem;\n      border: 1px solid rgba(196, 92, 38, 0.3);\n      background: #fff4ee;\n      color: var(--warn);\n      padding: 0.75rem 1rem;\n      font-size: 0.9rem;\n    }\n    .error.show { display: block; }\n\n    .layout {\n      display: grid;\n      gap: 1rem;\n      flex: 1;\n      animation: fade-up 0.65s ease-out 0.1s both;\n    }\n    @media (min-width: 960px) {\n      .layout { grid-template-columns: minmax(0, 1fr) 320px; }\n    }\n\n    .main-col {\n      display: flex;\n      flex-direction: column;\n      gap: 0.75rem;\n      min-height: 420px;\n    }\n    @media (min-width: 960px) {\n      .main-col { min-height: 640px; }\n    }\n\n    .toolbar {\n      display: flex;\n      flex-wrap: wrap;\n      align-items: center;\n      gap: 0.4rem;\n      border-radius: 1rem;\n      background: rgba(255,255,255,0.82);\n      padding: 0.45rem;\n      box-shadow: 0 1px 0 rgba(20,32,28,0.06);\n      backdrop-filter: blur(8px);\n    }\n    .tool {\n      display: inline-flex;\n      align-items: center;\n      gap: 0.35rem;\n      border: 0;\n      border-radius: 0.75rem;\n      padding: 0.55rem 0.75rem;\n      font-size: 0.8rem;\n      font-weight: 600;\n      background: var(--panel);\n      color: var(--ink);\n    }\n    .tool.muted {\n      background: transparent;\n      color: var(--ink-soft);\n    }\n    .tool.active { background: var(--accent); color: #fff; }\n    .tool:disabled { opacity: 0.4; cursor: not-allowed; }\n    .tool:not(:disabled):hover { filter: brightness(0.97); }\n    .filename {\n      margin-left: auto;\n      font-size: 0.75rem;\n      color: var(--ink-soft);\n      max-width: 12rem;\n      overflow: hidden;\n      text-overflow: ellipsis;\n      white-space: nowrap;\n      padding: 0 0.4rem;\n    }\n\n    .canvas-wrap {\n      position: relative;\n      flex: 1;\n      min-height: 280px;\n      overflow: hidden;\n      border-radius: 1.25rem;\n      background:\n        linear-gradient(135deg, rgba(126,200,163,0.08) 0%, transparent 40%),\n        radial-gradient(circle at 30% 20%, rgba(232,197,71,0.08), transparent 50%),\n        var(--canvas-void);\n    }\n    canvas {\n      position: absolute;\n      inset: 0;\n      width: 100%;\n      height: 100%;\n      touch-action: none;\n      cursor: crosshair;\n    }\n    .canvas-hint {\n      position: absolute;\n      inset: 0;\n      display: flex;\n      align-items: center;\n      justify-content: center;\n      padding: 1.5rem;\n      text-align: center;\n      color: var(--accent-soft);\n      pointer-events: none;\n      font-size: 0.9rem;\n      line-height: 1.6;\n      opacity: 0.85;\n    }\n    .canvas-hint.hide { display: none; }\n    .hint {\n      margin: 0;\n      font-size: 0.8rem;\n      color: var(--ink-soft);\n    }\n\n    .side {\n      display: flex;\n      flex-direction: column;\n      gap: 0.75rem;\n    }\n    .panel {\n      border-radius: 1rem;\n      background: rgba(255,255,255,0.92);\n      padding: 1rem;\n      box-shadow: 0 12px 40px rgba(20,32,28,0.06);\n      backdrop-filter: blur(8px);\n    }\n    .panel h2 {\n      margin: 0 0 0.75rem;\n      font-family: var(--font-display);\n      font-size: 1rem;\n      font-weight: 700;\n      letter-spacing: -0.02em;\n    }\n    .empty {\n      margin: 0;\n      font-size: 0.9rem;\n      color: var(--ink-soft);\n      line-height: 1.5;\n    }\n    .path-list {\n      list-style: none;\n      margin: 0;\n      padding: 0;\n      display: flex;\n      flex-direction: column;\n      gap: 0.45rem;\n      max-height: 14rem;\n      overflow-y: auto;\n    }\n    .path-item {\n      width: 100%;\n      display: flex;\n      align-items: flex-start;\n      gap: 0.7rem;\n      border: 1px solid transparent;\n      border-radius: 0.75rem;\n      background: var(--panel);\n      padding: 0.65rem 0.75rem;\n      text-align: left;\n    }\n    .path-item.active {\n      border-color: var(--accent);\n      background: rgba(31, 111, 86, 0.08);\n    }\n    .dot {\n      width: 0.7rem;\n      height: 0.7rem;\n      border-radius: 50%;\n      margin-top: 0.25rem;\n      flex-shrink: 0;\n    }\n    .path-title {\n      display: flex;\n      align-items: center;\n      gap: 0.4rem;\n      font-size: 0.9rem;\n      font-weight: 600;\n    }\n    .badge {\n      border-radius: 0.3rem;\n      background: rgba(31, 111, 86, 0.15);\n      color: var(--accent);\n      font-size: 0.65rem;\n      font-weight: 600;\n      padding: 0.1rem 0.35rem;\n    }\n    .path-meta {\n      display: block;\n      margin-top: 0.15rem;\n      font-size: 0.75rem;\n      color: var(--ink-soft);\n    }\n\n    .field-row { display: flex; gap: 0.5rem; align-items: stretch; }\n    .field-row input, .field-row select {\n      border: 1px solid var(--line) !important;\n      border-radius: 0.75rem !important;\n      padding: 0.65rem 0.75rem !important;\n      font-size: 0.9rem !important;\n      line-height: 1.3 !important;\n      outline: none !important;\n      min-width: 0;\n      height: auto !important;\n      min-height: 2.6rem !important;\n    }\n    .field-row input {\n      flex: 1 1 auto;\n      background: #fff !important;\n      background-image: none !important;\n    }\n    .field-row select {\n      flex: 0 0 auto;\n      width: 5.25rem !important;\n      max-width: 5.25rem !important;\n      padding-right: 1.75rem !important;\n    }\n    .field-row input:focus, .field-row select:focus {\n      border-color: var(--accent) !important;\n    }\n    .help {\n      margin: 0 0 0.75rem;\n      font-size: 0.75rem;\n      color: var(--ink-soft);\n      line-height: 1.5;\n    }\n    .primary {\n      width: 100%;\n      margin-top: 0.75rem;\n      border: 0;\n      border-radius: 0.75rem;\n      background: var(--ink);\n      color: #fff;\n      font-weight: 600;\n      font-size: 0.9rem;\n      padding: 0.7rem;\n    }\n    .primary:hover { opacity: 0.92; }\n\n    .results { display: grid; gap: 0.75rem; font-size: 0.9rem; }\n    .result-row {\n      display: flex;\n      justify-content: space-between;\n      gap: 0.75rem;\n      align-items: baseline;\n      border-bottom: 1px solid rgba(197, 212, 204, 0.7);\n      padding-bottom: 0.5rem;\n    }\n    .result-row:last-child { border-bottom: 0; padding-bottom: 0; }\n    .result-row dt { color: var(--ink-soft); }\n    .result-row dd { margin: 0; font-weight: 600; text-align: right; }\n    .result-row dd.em { color: var(--accent); font-size: 1.1rem; }\n\n    .tabs {\n      display: grid;\n      grid-template-columns: 1fr 1fr;\n      gap: 0.35rem;\n      margin-bottom: 0.85rem;\n      padding: 0.25rem;\n      border-radius: 0.85rem;\n      background: var(--panel);\n    }\n    .tab {\n      border: 0;\n      border-radius: 0.65rem;\n      padding: 0.55rem 0.5rem;\n      font-size: 0.85rem;\n      font-weight: 700;\n      background: transparent;\n      color: var(--ink-soft);\n    }\n    .tab.active {\n      background: #fff;\n      color: var(--accent);\n      box-shadow: 0 1px 4px rgba(20, 32, 28, 0.08);\n    }\n    .calc-pane[hidden] { display: none !important; }\n    .height-row {\n      display: flex;\n      flex-direction: column;\n      gap: 0.4rem;\n      margin-bottom: 0.75rem;\n    }\n    .height-row > span {\n      font-size: 0.75rem;\n      color: var(--ink-soft);\n      font-weight: 600;\n    }\n    .divide-out {\n      border-radius: 0.75rem;\n      background: rgba(31, 111, 86, 0.1);\n      padding: 0.75rem;\n    }\n    .divide-out .label { margin: 0; font-size: 0.75rem; color: var(--ink-soft); }\n    .divide-out .value {\n      margin: 0.25rem 0 0;\n      font-family: var(--font-display);\n      font-size: 1.5rem;\n      font-weight: 700;\n      color: var(--accent);\n    }\n    .min-count {\n      margin: 0.75rem 0 0;\n      padding: 0.7rem 0.75rem;\n      border-radius: 0.75rem;\n      background: var(--ink);\n      color: #fff;\n      font-size: 0.9rem;\n      font-weight: 600;\n      line-height: 1.45;\n      text-align: center;\n    }\n    .min-count strong {\n      font-family: var(--font-display);\n      font-size: 1.15rem;\n      color: var(--accent-soft)!important;\n    }\n    .rate-note {\n      margin: 0.35rem 0 0.75rem;\n      font-size: 0.8rem;\n      font-weight: 600;\n      color: var(--accent);\n    }\n    .source-badge {\n      display: inline-block;\n      margin-top: 0.55rem;\n      border-radius: 0.35rem;\n      background: rgba(31, 111, 86, 0.12);\n      color: var(--accent);\n      font-size: 0.7rem;\n      font-weight: 700;\n      padding: 0.2rem 0.45rem;\n    }\n    .source-badge[hidden] { display: none !important; }\n    .divider-label {\n      margin: 0.85rem 0 0.55rem;\n      font-size: 0.75rem;\n      font-weight: 700;\n      color: var(--ink-soft);\n      text-transform: uppercase;\n      letter-spacing: 0.04em;\n    }\n\n    \/* WordPress embed: constrain if inside theme content *\/\n    .cheokdo-root { width: 100%; }\n  \n<\/style>\n\n<div class=\"cheokdo-root app\" id=\"app\">\n    <header class=\"header\">\n      <div>\n        <h1 class=\"brand\">Quantity Calculator<\/h1>\n        <p class=\"tagline\">Measure from a photo, or enter a known length directly \u2014 then estimate Packer and Fastener quantities.<\/p>\n      <\/div>\n      <label class=\"upload-btn\" id=\"uploadLabel\">\n        <svg width=\"18\" height=\"18\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\"><path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"\/><polyline points=\"17 8 12 3 7 8\"\/><line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"\/><\/svg>\n        Upload image\n        <input class=\"sr-only\" id=\"fileInput\" type=\"file\" accept=\"image\/jpeg,image\/png,.jpg,.jpeg,.png\" \/>\n      <\/label>\n    <\/header>\n\n    <div class=\"error\" id=\"error\" role=\"alert\"><\/div>\n\n    <div class=\"layout\">\n      <section class=\"main-col\">\n        <div class=\"toolbar\" id=\"toolbar\">\n          <button type=\"button\" class=\"tool active\" data-action=\"mode-line\">Line<\/button>\n          <button type=\"button\" class=\"tool\" data-action=\"mode-curve\">Curve<\/button>\n          <button type=\"button\" class=\"tool\" data-action=\"tool-select\">Select<\/button>\n          <button type=\"button\" class=\"tool muted\" data-action=\"undo\">Undo<\/button>\n          <button type=\"button\" class=\"tool muted\" data-action=\"delete\" id=\"btnDelete\" disabled>Delete<\/button>\n          <button type=\"button\" class=\"tool muted\" data-action=\"clear\">Clear all<\/button>\n          <span class=\"filename\" id=\"filename\"><\/span>\n        <\/div>\n\n        <div class=\"canvas-wrap\" id=\"canvasWrap\">\n          <canvas id=\"canvas\"><\/canvas>\n          <div class=\"canvas-hint\" id=\"canvasHint\">\n            Upload a JPG or PNG to show it here.<br \/>\n            Draw lines or curves with a mouse or finger.\n          <\/div>\n        <\/div>\n        <p class=\"hint\" id=\"hint\">Drag on the canvas to draw a straight line. Release to add the segment.<\/p>\n      <\/section>\n\n      <aside class=\"side\">\n        <section class=\"panel\">\n          <h2>Segments<\/h2>\n          <div id=\"pathListEmpty\" class=\"empty\">No segments yet. Draw a line or curve to get started.<\/div>\n          <ul class=\"path-list\" id=\"pathList\" hidden><\/ul>\n        <\/section>\n\n        <section class=\"panel\">\n          <h2>Real length<\/h2>\n          <p class=\"help\">Use a drawn segment as a scale reference, or skip the image and type a known length below.<\/p>\n          <div id=\"calibEmpty\" class=\"empty\">Select a drawn segment to set a photo reference (optional).<\/div>\n          <div id=\"calibForm\" hidden>\n            <p class=\"help\" id=\"calibHelp\"><\/p>\n            <div class=\"field-row\">\n              <input id=\"realLength\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"any\" placeholder=\"e.g. 12.5\" \/>\n              <select id=\"unit\">\n                <option value=\"mm\">mm<\/option>\n                <option value=\"cm\" selected>cm<\/option>\n                <option value=\"m\">m<\/option>\n                <option value=\"in\">in<\/option>\n                <option value=\"ft\">ft<\/option>\n                <option value=\"px\">px<\/option>\n              <\/select>\n            <\/div>\n            <button type=\"button\" class=\"primary\" id=\"applyCalib\">Use as reference<\/button>\n          <\/div>\n\n          <p class=\"divider-label\">Or enter known length<\/p>\n          <p class=\"help\">If you already know the measured length, enter it here \u2014 no image required.<\/p>\n          <div class=\"field-row\">\n            <input id=\"manualLength\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"any\" placeholder=\"e.g. 3.5\" \/>\n            <select id=\"manualUnit\">\n              <option value=\"mm\">mm<\/option>\n              <option value=\"cm\">cm<\/option>\n              <option value=\"m\" selected>m<\/option>\n              <option value=\"in\">in<\/option>\n              <option value=\"ft\">ft<\/option>\n              <option value=\"px\">px<\/option>\n            <\/select>\n          <\/div>\n          <button type=\"button\" class=\"primary\" id=\"applyManual\" style=\"margin-top:0.75rem\">Use known length<\/button>\n          <button type=\"button\" class=\"tool muted\" id=\"clearManual\" style=\"margin-top:0.45rem;width:100%;justify-content:center\">Clear known length<\/button>\n          <span class=\"source-badge\" id=\"manualBadge\" hidden>Using known length<\/span>\n        <\/section>\n\n        <section class=\"panel\">\n          <h2>Results<\/h2>\n          <dl class=\"results\">\n            <div class=\"result-row\"><dt>Total pixel length<\/dt><dd id=\"totalPx\">0.0 px<\/dd><\/div>\n            <div class=\"result-row\"><dt>Scale<\/dt><dd id=\"scaleText\">Reference needed<\/dd><\/div>\n            <div class=\"result-row\"><dt>Total real length<\/dt><dd class=\"em\" id=\"totalReal\">\u2014<\/dd><\/div>\n          <\/dl>\n        <\/section>\n\n        <section class=\"panel\">\n          <h2>Quantity<\/h2>\n          <div class=\"tabs\" role=\"tablist\" aria-label=\"Calculation mode\">\n            <button type=\"button\" class=\"tab active\" role=\"tab\" id=\"tabPacker\" data-calc=\"packer\" aria-selected=\"true\">Packer<\/button>\n            <button type=\"button\" class=\"tab\" role=\"tab\" id=\"tabFastener\" data-calc=\"fastener\" aria-selected=\"false\">Fastener<\/button>\n          <\/div>\n\n          <div class=\"calc-pane\" id=\"panePacker\" role=\"tabpanel\">\n            <p class=\"help\">Divides total real length by the Packer coverage length.<\/p>\n            <p class=\"rate-note\" id=\"packerRate\">(1 recommended per 1.75 m)<\/p>\n            <div class=\"divide-out\">\n              <p class=\"label\">Length \u00f7 1.75 m<\/p>\n              <p class=\"value\" id=\"packerResult\">\u2014<\/p>\n            <\/div>\n            <p class=\"min-count\" id=\"packerMin\">Minimum Packers required: <strong>\u2014<\/strong><\/p>\n          <\/div>\n\n          <div class=\"calc-pane\" id=\"paneFastener\" role=\"tabpanel\" hidden>\n            <p class=\"help\">Area = length \u00d7 height, then divide by Fastener coverage area.<\/p>\n            <p class=\"rate-note\" id=\"fastenerRate\">(1 recommended per 8.25 m\u00b2)<\/p>\n            <div class=\"height-row\">\n              <span>Height<\/span>\n              <div class=\"field-row\">\n                <input id=\"fastenerHeight\" type=\"number\" inputmode=\"decimal\" min=\"0\" step=\"any\" placeholder=\"e.g. 2.5\" value=\"\" \/>\n                <select id=\"heightUnit\">\n                  <option value=\"mm\">mm<\/option>\n                  <option value=\"cm\">cm<\/option>\n                  <option value=\"m\" selected>m<\/option>\n                  <option value=\"in\">in<\/option>\n                  <option value=\"ft\">ft<\/option>\n                  <option value=\"px\">px<\/option>\n                <\/select>\n              <\/div>\n            <\/div>\n            <div class=\"divide-out\" style=\"margin-bottom:0.55rem\">\n              <p class=\"label\">Area (length \u00d7 height)<\/p>\n              <p class=\"value\" id=\"fastenerArea\">\u2014<\/p>\n            <\/div>\n            <div class=\"divide-out\">\n              <p class=\"label\">Area \u00f7 8.25 m\u00b2<\/p>\n              <p class=\"value\" id=\"fastenerResult\">\u2014<\/p>\n            <\/div>\n            <p class=\"min-count\" id=\"fastenerMin\">Minimum Fasteners required: <strong>\u2014<\/strong><\/p>\n          <\/div>\n        <\/section>\n      <\/aside>\n    <\/div>\n  <\/div>\n\n  <script>\n(function () {\n  \"use strict\";\n\n  var COLORS = [\"#1f6f56\", \"#c45c26\", \"#2f5d8c\", \"#8a3d6b\", \"#5a6b1f\", \"#7a4a12\"];\n\n  \/\/ Packer \/ Fastener coverage (shown in UI; edit these to change rates)\n  var PACKER_UNIT_LENGTH_M = 1.75; \/\/ 1 Packer per 1.75 m\n  var FASTENER_AREA_M2 = 8.25;     \/\/ 1 Fastener per 8.25 m\u00b2\n\n  var UNIT_TO_M = {\n    mm: 0.001,\n    cm: 0.01,\n    m: 1,\n    in: 0.0254,\n    ft: 0.3048,\n    px: 1\n  };\n\n  var state = {\n    imageUrl: null,\n    imageName: \"\",\n    image: null,\n    paths: [],\n    draft: [],\n    mode: \"line\",\n    tool: \"draw\",\n    selectedId: null,\n    calibratedId: null,\n    calibratedReal: null,\n    colorIndex: 0,\n    drawing: false,\n    view: null,\n    calcTab: \"packer\",\n    manualLength: null,\n    manualUnit: \"m\"\n  };\n\n  var els = {\n    fileInput: document.getElementById(\"fileInput\"),\n    uploadLabel: document.getElementById(\"uploadLabel\"),\n    error: document.getElementById(\"error\"),\n    filename: document.getElementById(\"filename\"),\n    canvas: document.getElementById(\"canvas\"),\n    canvasWrap: document.getElementById(\"canvasWrap\"),\n    canvasHint: document.getElementById(\"canvasHint\"),\n    hint: document.getElementById(\"hint\"),\n    pathList: document.getElementById(\"pathList\"),\n    pathListEmpty: document.getElementById(\"pathListEmpty\"),\n    calibEmpty: document.getElementById(\"calibEmpty\"),\n    calibForm: document.getElementById(\"calibForm\"),\n    calibHelp: document.getElementById(\"calibHelp\"),\n    realLength: document.getElementById(\"realLength\"),\n    unit: document.getElementById(\"unit\"),\n    applyCalib: document.getElementById(\"applyCalib\"),\n    manualLength: document.getElementById(\"manualLength\"),\n    manualUnit: document.getElementById(\"manualUnit\"),\n    applyManual: document.getElementById(\"applyManual\"),\n    clearManual: document.getElementById(\"clearManual\"),\n    manualBadge: document.getElementById(\"manualBadge\"),\n    totalPx: document.getElementById(\"totalPx\"),\n    scaleText: document.getElementById(\"scaleText\"),\n    totalReal: document.getElementById(\"totalReal\"),\n    tabPacker: document.getElementById(\"tabPacker\"),\n    tabFastener: document.getElementById(\"tabFastener\"),\n    panePacker: document.getElementById(\"panePacker\"),\n    paneFastener: document.getElementById(\"paneFastener\"),\n    fastenerHeight: document.getElementById(\"fastenerHeight\"),\n    heightUnit: document.getElementById(\"heightUnit\"),\n    packerResult: document.getElementById(\"packerResult\"),\n    packerMin: document.getElementById(\"packerMin\"),\n    packerRate: document.getElementById(\"packerRate\"),\n    fastenerArea: document.getElementById(\"fastenerArea\"),\n    fastenerResult: document.getElementById(\"fastenerResult\"),\n    fastenerMin: document.getElementById(\"fastenerMin\"),\n    fastenerRate: document.getElementById(\"fastenerRate\"),\n    btnDelete: document.getElementById(\"btnDelete\"),\n    toolbar: document.getElementById(\"toolbar\")\n  };\n\n  function toM(value, unitName) {\n    return value * (UNIT_TO_M[unitName] || 1);\n  }\n\n  function minCountFrom(raw) {\n    if (raw == null || !Number.isFinite(raw) || raw < 0) return null;\n    if (raw === 0) return 0;\n    return Math.ceil(raw);\n  }\n\n  function setMinMessage(el, kind, count) {\n    var n = count == null ? \"\u2014\" : String(count);\n    el.innerHTML = \"Minimum \" + kind + \"s required: <strong>\" + n + \"<\/strong>\";\n  }\n\n  function dist(a, b) {\n    var dx = a.x - b.x, dy = a.y - b.y;\n    return Math.hypot(dx, dy);\n  }\n\n  function pathLen(points) {\n    var t = 0;\n    for (var i = 1; i < points.length; i++) t += dist(points[i - 1], points[i]);\n    return t;\n  }\n\n  function formatLen(v, digits) {\n    if (!Number.isFinite(v)) return \"\u2014\";\n    digits = digits == null ? 2 : digits;\n    if (Math.abs(v) >= 100) return v.toFixed(1);\n    return v.toFixed(digits);\n  }\n\n  function uid() {\n    return Date.now().toString(36) + \"-\" + Math.random().toString(36).slice(2, 8);\n  }\n\n  function shouldAdd(points, next) {\n    if (!points.length) return true;\n    return dist(points[points.length - 1], next) >= 2.5;\n  }\n\n  function distToSeg(p, a, b) {\n    var dx = b.x - a.x, dy = b.y - a.y;\n    if (dx === 0 && dy === 0) return dist(p, a);\n    var t = Math.max(0, Math.min(1, ((p.x - a.x) * dx + (p.y - a.y) * dy) \/ (dx * dx + dy * dy)));\n    return dist(p, { x: a.x + t * dx, y: a.y + t * dy });\n  }\n\n  function hitTest(point, threshold) {\n    var best = null, bestD = threshold;\n    state.paths.forEach(function (path) {\n      for (var i = 1; i < path.points.length; i++) {\n        var d = distToSeg(point, path.points[i - 1], path.points[i]);\n        if (d < bestD) { bestD = d; best = path.id; }\n      }\n    });\n    return best;\n  }\n\n  function showError(msg) {\n    if (!msg) {\n      els.error.classList.remove(\"show\");\n      els.error.textContent = \"\";\n      return;\n    }\n    els.error.textContent = msg;\n    els.error.classList.add(\"show\");\n  }\n\n  function loadFile(file) {\n    if (!file) return;\n    var ok = \/image\\\/(jpeg|jpg|png)\/i.test(file.type) || \/\\.(jpe?g|png)$\/i.test(file.name);\n    if (!ok) { showError(\"Only JPG or PNG files are supported.\"); return; }\n    showError(null);\n    if (state.imageUrl) URL.revokeObjectURL(state.imageUrl);\n    state.imageUrl = URL.createObjectURL(file);\n    state.imageName = file.name;\n    state.paths = [];\n    state.draft = [];\n    state.selectedId = null;\n    state.calibratedId = null;\n    state.calibratedReal = null;\n    state.colorIndex = 0;\n    els.realLength.value = \"\";\n    var img = new Image();\n    img.onload = function () {\n      state.image = img;\n      els.canvasHint.classList.add(\"hide\");\n      els.filename.textContent = file.name;\n      recomputeView();\n      paint();\n      renderUI();\n    };\n    img.onerror = function () {\n      showError(\"Could not load the image.\");\n      state.image = null;\n    };\n    img.src = state.imageUrl;\n  }\n\n  function recomputeView() {\n    var wrap = els.canvasWrap;\n    var img = state.image;\n    if (!wrap || !img) { state.view = null; return; }\n    var pad = 16;\n    var availW = Math.max(wrap.clientWidth - pad * 2, 1);\n    var availH = Math.max(wrap.clientHeight - pad * 2, 1);\n    var scale = Math.min(availW \/ img.naturalWidth, availH \/ img.naturalHeight);\n    var drawW = img.naturalWidth * scale;\n    var drawH = img.naturalHeight * scale;\n    state.view = {\n      scale: scale,\n      offsetX: (wrap.clientWidth - drawW) \/ 2,\n      offsetY: (wrap.clientHeight - drawH) \/ 2,\n      imageWidth: img.naturalWidth,\n      imageHeight: img.naturalHeight\n    };\n  }\n\n  function toImagePoint(clientX, clientY) {\n    var v = state.view;\n    if (!v) return null;\n    var rect = els.canvas.getBoundingClientRect();\n    var x = (clientX - rect.left - v.offsetX) \/ v.scale;\n    var y = (clientY - rect.top - v.offsetY) \/ v.scale;\n    if (x < 0 || y < 0 || x > v.imageWidth || y > v.imageHeight) return null;\n    return { x: x, y: y };\n  }\n\n  function drawPath(ctx, points, color, selected, dashed) {\n    var v = state.view;\n    if (!v || points.length < 1) return;\n    ctx.save();\n    ctx.translate(v.offsetX, v.offsetY);\n    ctx.scale(v.scale, v.scale);\n    ctx.beginPath();\n    ctx.moveTo(points[0].x, points[0].y);\n    for (var i = 1; i < points.length; i++) ctx.lineTo(points[i].x, points[i].y);\n    ctx.lineWidth = (selected ? 3.2 : 2.2) \/ v.scale;\n    ctx.strokeStyle = color;\n    ctx.lineCap = \"round\";\n    ctx.lineJoin = \"round\";\n    if (dashed) ctx.setLineDash([8 \/ v.scale, 6 \/ v.scale]);\n    if (selected) { ctx.shadowColor = color; ctx.shadowBlur = 10 \/ v.scale; }\n    ctx.stroke();\n    var endR = (selected ? 5 : 3.5) \/ v.scale;\n    [points[0], points[points.length - 1]].forEach(function (p) {\n      ctx.beginPath();\n      ctx.fillStyle = \"#fff\";\n      ctx.strokeStyle = color;\n      ctx.lineWidth = 2 \/ v.scale;\n      ctx.setLineDash([]);\n      ctx.arc(p.x, p.y, endR, 0, Math.PI * 2);\n      ctx.fill();\n      ctx.stroke();\n    });\n    ctx.restore();\n  }\n\n  function paint() {\n    var canvas = els.canvas;\n    var wrap = els.canvasWrap;\n    var ctx = canvas.getContext(\"2d\");\n    if (!ctx) return;\n    var dpr = window.devicePixelRatio || 1;\n    var width = wrap.clientWidth;\n    var height = wrap.clientHeight;\n    if (canvas.width !== Math.floor(width * dpr) || canvas.height !== Math.floor(height * dpr)) {\n      canvas.width = Math.floor(width * dpr);\n      canvas.height = Math.floor(height * dpr);\n      canvas.style.width = width + \"px\";\n      canvas.style.height = height + \"px\";\n    }\n    ctx.setTransform(dpr, 0, 0, dpr, 0, 0);\n    ctx.clearRect(0, 0, width, height);\n\n    var img = state.image;\n    var v = state.view;\n    if (!img || !v) return;\n\n    ctx.save();\n    ctx.shadowColor = \"rgba(15, 26, 22, 0.28)\";\n    ctx.shadowBlur = 28;\n    ctx.shadowOffsetY = 10;\n    ctx.drawImage(img, v.offsetX, v.offsetY, v.imageWidth * v.scale, v.imageHeight * v.scale);\n    ctx.restore();\n\n    state.paths.forEach(function (path) {\n      drawPath(ctx, path.points, path.color, path.id === state.selectedId, false);\n    });\n    if (state.draft.length) drawPath(ctx, state.draft, \"#e8c547\", true, true);\n  }\n\n  function getScale() {\n    if (state.calibratedReal == null || !state.calibratedId) return null;\n    var path = state.paths.find(function (p) { return p.id === state.calibratedId; });\n    if (!path) return null;\n    var px = pathLen(path.points);\n    if (px <= 0) return null;\n    return state.calibratedReal \/ px;\n  }\n\n  function unit() { return els.unit.value; }\n\n  function renderUI() {\n    \/\/ toolbar active states\n    Array.prototype.forEach.call(els.toolbar.querySelectorAll(\".tool\"), function (btn) {\n      var a = btn.getAttribute(\"data-action\");\n      var active = false;\n      if (a === \"mode-line\") active = state.tool === \"draw\" && state.mode === \"line\";\n      if (a === \"mode-curve\") active = state.tool === \"draw\" && state.mode === \"curve\";\n      if (a === \"tool-select\") active = state.tool === \"select\";\n      btn.classList.toggle(\"active\", active);\n    });\n    els.btnDelete.disabled = !state.selectedId;\n    els.canvas.style.cursor = state.tool === \"select\" ? \"pointer\" : \"crosshair\";\n\n    if (state.tool === \"draw\") {\n      els.hint.textContent = state.mode === \"line\"\n        ? \"Drag on the canvas to draw a straight line. Release to add the segment.\"\n        : \"Draw a freehand curve with your mouse or finger.\";\n    } else {\n      els.hint.textContent = \"Tap a segment or pick one from the list, then enter its real length.\";\n    }\n\n    \/\/ path list\n    if (!state.paths.length) {\n      els.pathList.hidden = true;\n      els.pathListEmpty.hidden = false;\n      els.pathList.innerHTML = \"\";\n    } else {\n      els.pathList.hidden = false;\n      els.pathListEmpty.hidden = true;\n      var scale = getScale();\n      els.pathList.innerHTML = state.paths.map(function (path, index) {\n        var px = pathLen(path.points);\n        var real = scale == null ? null : px * scale;\n        var active = path.id === state.selectedId ? \" active\" : \"\";\n        var badge = path.id === state.calibratedId ? '<span class=\"badge\">Ref<\/span>' : \"\";\n        var meta = formatLen(px, 1) + \" px\" + (real != null ? \" \u00b7 \" + formatLen(real) + \" \" + unit() : \"\");\n        return '<li><button type=\"button\" class=\"path-item' + active + '\" data-id=\"' + path.id + '\">' +\n          '<span class=\"dot\" style=\"background:' + path.color + '\"><\/span>' +\n          '<span><span class=\"path-title\">#' + (index + 1) + \" \" + (path.kind === \"line\" ? \"Line\" : \"Curve\") + \" \" + badge + \"<\/span>\" +\n          '<span class=\"path-meta\">' + meta + \"<\/span><\/span><\/button><\/li>\";\n      }).join(\"\");\n    }\n\n    \/\/ calibration form\n    var selected = state.paths.find(function (p) { return p.id === state.selectedId; });\n    if (!selected) {\n      els.calibEmpty.hidden = false;\n      els.calibForm.hidden = true;\n    } else {\n      els.calibEmpty.hidden = true;\n      els.calibForm.hidden = false;\n      var idx = state.paths.findIndex(function (p) { return p.id === selected.id; }) + 1;\n      els.calibHelp.textContent = \"Enter the real length of segment #\" + idx + \". This becomes the scale reference.\";\n    }\n\n    var totalPx = state.paths.reduce(function (s, p) { return s + pathLen(p.points); }, 0);\n    var scale = getScale();\n    var drawnReal = scale == null ? null : totalPx * scale;\n    var usingManual = state.manualLength != null && Number.isFinite(state.manualLength) && state.manualLength > 0;\n    var totalReal = usingManual ? state.manualLength : drawnReal;\n    var displayUnit = usingManual ? state.manualUnit : unit();\n\n    els.totalPx.textContent = formatLen(totalPx, 1) + \" px\";\n    if (usingManual) {\n      els.scaleText.textContent = \"Direct input\";\n    } else {\n      els.scaleText.textContent = scale == null ? \"Reference needed\" : (\"1 px = \" + formatLen(scale, 4) + \" \" + unit());\n    }\n    els.totalReal.textContent = totalReal == null ? \"\u2014\" : (formatLen(totalReal) + \" \" + displayUnit);\n    els.manualBadge.hidden = !usingManual;\n\n    \/\/ Packer \/ Fastener tabs\n    var isPacker = state.calcTab === \"packer\";\n    els.tabPacker.classList.toggle(\"active\", isPacker);\n    els.tabFastener.classList.toggle(\"active\", !isPacker);\n    els.tabPacker.setAttribute(\"aria-selected\", isPacker ? \"true\" : \"false\");\n    els.tabFastener.setAttribute(\"aria-selected\", isPacker ? \"false\" : \"true\");\n    els.panePacker.hidden = !isPacker;\n    els.paneFastener.hidden = isPacker;\n\n    els.packerRate.textContent = \"(1 recommended per \" + PACKER_UNIT_LENGTH_M + \" m)\";\n    els.fastenerRate.textContent = \"(1 recommended per \" + FASTENER_AREA_M2 + \" m\u00b2)\";\n\n    var lengthM = totalReal == null ? null : toM(totalReal, displayUnit);\n\n    \/\/ Packer: length_m \/ 1.75 m\n    var packerRaw = null;\n    if (lengthM != null && PACKER_UNIT_LENGTH_M > 0) {\n      packerRaw = lengthM \/ PACKER_UNIT_LENGTH_M;\n    }\n    els.packerResult.textContent = packerRaw == null ? \"\u2014\" : formatLen(packerRaw, 3);\n    setMinMessage(els.packerMin, \"Packer\", minCountFrom(packerRaw));\n\n    \/\/ Fastener: (length_m * height_m) \/ 8.25 m\u00b2\n    var heightVal = Number(els.fastenerHeight.value);\n    var heightM = Number.isFinite(heightVal) && heightVal > 0\n      ? toM(heightVal, els.heightUnit.value)\n      : null;\n    var areaM2 = null;\n    var fastenerRaw = null;\n    if (lengthM != null && heightM != null) {\n      areaM2 = lengthM * heightM;\n      if (FASTENER_AREA_M2 > 0) fastenerRaw = areaM2 \/ FASTENER_AREA_M2;\n    }\n    els.fastenerArea.textContent = areaM2 == null ? \"\u2014\" : (formatLen(areaM2, 3) + \" m\u00b2\");\n    els.fastenerResult.textContent = fastenerRaw == null ? \"\u2014\" : formatLen(fastenerRaw, 3);\n    setMinMessage(els.fastenerMin, \"Fastener\", minCountFrom(fastenerRaw));\n  }\n\n  function completeStroke() {\n    if (!state.drawing) return;\n    state.drawing = false;\n    if (state.draft.length >= 2) {\n      var path = {\n        id: uid(),\n        kind: state.mode,\n        points: state.draft.slice(),\n        color: COLORS[state.colorIndex % COLORS.length]\n      };\n      state.colorIndex += 1;\n      state.paths.push(path);\n      state.selectedId = path.id;\n      state.tool = \"select\";\n    }\n    state.draft = [];\n    paint();\n    renderUI();\n  }\n\n  \/\/ events\n  els.fileInput.addEventListener(\"change\", function (e) {\n    loadFile(e.target.files && e.target.files[0]);\n    e.target.value = \"\";\n  });\n\n  [\"dragover\", \"dragenter\"].forEach(function (ev) {\n    els.uploadLabel.addEventListener(ev, function (e) {\n      e.preventDefault();\n      els.uploadLabel.classList.add(\"drag\");\n    });\n  });\n  els.uploadLabel.addEventListener(\"dragleave\", function () {\n    els.uploadLabel.classList.remove(\"drag\");\n  });\n  els.uploadLabel.addEventListener(\"drop\", function (e) {\n    e.preventDefault();\n    els.uploadLabel.classList.remove(\"drag\");\n    loadFile(e.dataTransfer.files && e.dataTransfer.files[0]);\n  });\n\n  els.toolbar.addEventListener(\"click\", function (e) {\n    var btn = e.target.closest(\"[data-action]\");\n    if (!btn) return;\n    var action = btn.getAttribute(\"data-action\");\n    if (action === \"mode-line\") { state.tool = \"draw\"; state.mode = \"line\"; }\n    if (action === \"mode-curve\") { state.tool = \"draw\"; state.mode = \"curve\"; }\n    if (action === \"tool-select\") { state.tool = \"select\"; }\n    if (action === \"undo\") {\n      var removed = state.paths.pop();\n      if (removed) {\n        if (state.selectedId === removed.id) state.selectedId = state.paths.length ? state.paths[state.paths.length - 1].id : null;\n        if (state.calibratedId === removed.id) { state.calibratedId = null; state.calibratedReal = null; }\n      }\n      state.draft = [];\n      paint();\n    }\n    if (action === \"delete\" && state.selectedId) {\n      var id = state.selectedId;\n      state.paths = state.paths.filter(function (p) { return p.id !== id; });\n      if (state.calibratedId === id) { state.calibratedId = null; state.calibratedReal = null; }\n      state.selectedId = null;\n      paint();\n    }\n    if (action === \"clear\") {\n      state.paths = [];\n      state.draft = [];\n      state.selectedId = null;\n      state.calibratedId = null;\n      state.calibratedReal = null;\n      els.realLength.value = \"\";\n      paint();\n    }\n    renderUI();\n  });\n\n  els.pathList.addEventListener(\"click\", function (e) {\n    var btn = e.target.closest(\"[data-id]\");\n    if (!btn) return;\n    state.selectedId = btn.getAttribute(\"data-id\");\n    state.tool = \"select\";\n    paint();\n    renderUI();\n  });\n\n  els.applyCalib.addEventListener(\"click\", function () {\n    var selected = state.paths.find(function (p) { return p.id === state.selectedId; });\n    if (!selected) { showError(\"Select a segment first.\"); return; }\n    var value = Number(els.realLength.value);\n    if (!Number.isFinite(value) || value <= 0) { showError(\"Real length must be a number greater than 0.\"); return; }\n    if (pathLen(selected.points) <= 0) { showError(\"The selected segment is too short.\"); return; }\n    showError(null);\n    state.calibratedId = selected.id;\n    state.calibratedReal = value;\n    \/\/ Drawing reference takes over from known-length override\n    state.manualLength = null;\n    els.manualLength.value = \"\";\n    renderUI();\n  });\n\n  els.applyManual.addEventListener(\"click\", function () {\n    var value = Number(els.manualLength.value);\n    if (!Number.isFinite(value) || value <= 0) {\n      showError(\"Known length must be a number greater than 0.\");\n      return;\n    }\n    showError(null);\n    state.manualLength = value;\n    state.manualUnit = els.manualUnit.value;\n    renderUI();\n  });\n\n  els.clearManual.addEventListener(\"click\", function () {\n    state.manualLength = null;\n    els.manualLength.value = \"\";\n    showError(null);\n    renderUI();\n  });\n\n  els.unit.addEventListener(\"change\", renderUI);\n  els.manualUnit.addEventListener(\"change\", function () {\n    if (state.manualLength != null) state.manualUnit = els.manualUnit.value;\n    renderUI();\n  });\n  els.fastenerHeight.addEventListener(\"input\", renderUI);\n  els.heightUnit.addEventListener(\"change\", renderUI);\n\n  els.tabPacker.addEventListener(\"click\", function () {\n    state.calcTab = \"packer\";\n    renderUI();\n  });\n  els.tabFastener.addEventListener(\"click\", function () {\n    state.calcTab = \"fastener\";\n    renderUI();\n  });\n\n  els.canvas.addEventListener(\"pointerdown\", function (e) {\n    if (!state.image || !state.view) return;\n    var point = toImagePoint(e.clientX, e.clientY);\n    if (!point) return;\n    if (state.tool === \"select\") {\n      state.selectedId = hitTest(point, 12 \/ state.view.scale);\n      paint();\n      renderUI();\n      return;\n    }\n    els.canvas.setPointerCapture(e.pointerId);\n    state.drawing = true;\n    state.draft = [point];\n    paint();\n  });\n\n  els.canvas.addEventListener(\"pointermove\", function (e) {\n    if (!state.drawing || state.tool !== \"draw\") return;\n    var point = toImagePoint(e.clientX, e.clientY);\n    if (!point) return;\n    if (state.mode === \"line\") {\n      state.draft = [state.draft[0], point];\n    } else if (shouldAdd(state.draft, point)) {\n      state.draft.push(point);\n    }\n    paint();\n  });\n\n  els.canvas.addEventListener(\"pointerup\", completeStroke);\n  els.canvas.addEventListener(\"pointercancel\", completeStroke);\n\n  if (window.ResizeObserver) {\n    new ResizeObserver(function () {\n      recomputeView();\n      paint();\n    }).observe(els.canvasWrap);\n  } else {\n    window.addEventListener(\"resize\", function () {\n      recomputeView();\n      paint();\n    });\n  }\n\n  renderUI();\n})();\n  <\/script>\n\n\t\t<\/div>\n\t<\/div>\n<\/div><\/div><\/div><\/div><\/div><script id=\"script-calculator_row\" data-row=\"script-calculator_row\" type=\"text\/javascript\" class=\"vc_controls\">UNCODE.initRow(document.getElementById(\"calculator_row\"));<\/script><\/div><\/div><\/div>\n<\/div>","protected":false},"excerpt":{"rendered":"Quantity Calculator Measure from a photo, or enter a known length directly \u2014 then estimate Packer and Fastener quantities. Upload [...]","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-180448","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/hb-global.shop\/th\/wp-json\/wp\/v2\/pages\/180448","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/hb-global.shop\/th\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/hb-global.shop\/th\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/hb-global.shop\/th\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/hb-global.shop\/th\/wp-json\/wp\/v2\/comments?post=180448"}],"version-history":[{"count":12,"href":"https:\/\/hb-global.shop\/th\/wp-json\/wp\/v2\/pages\/180448\/revisions"}],"predecessor-version":[{"id":180615,"href":"https:\/\/hb-global.shop\/th\/wp-json\/wp\/v2\/pages\/180448\/revisions\/180615"}],"wp:attachment":[{"href":"https:\/\/hb-global.shop\/th\/wp-json\/wp\/v2\/media?parent=180448"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}