{"id":629,"date":"2025-07-16T03:08:06","date_gmt":"2025-07-16T03:08:06","guid":{"rendered":"https:\/\/dr-nutrition.ca\/?page_id=629"},"modified":"2026-07-09T00:31:47","modified_gmt":"2026-07-09T00:31:47","slug":"breaths-per-minute-calc","status":"publish","type":"page","link":"https:\/\/dr-nutrition.ca\/?page_id=629","title":{"rendered":"Breaths per minute Calc"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"629\" class=\"elementor elementor-629\">\n\t\t\t\t<div class=\"elementor-element elementor-element-6ff1811a e-flex e-con-boxed e-con e-parent\" data-id=\"6ff1811a\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-47f52019 elementor-widget elementor-widget-text-editor\" data-id=\"47f52019\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"text-editor.default\">\n\t\t\t\t\t\t\t\t\t\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-316d316 e-flex e-con-boxed e-con e-parent\" data-id=\"316d316\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-105468d elementor-widget elementor-widget-html\" data-id=\"105468d\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n<meta charset=\"UTF-8\">\r\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1, viewport-fit=cover\">\r\n<title>Breath \u2014 Bovine Respiration Counter<\/title>\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.googleapis.com\">\r\n<link rel=\"preconnect\" href=\"https:\/\/fonts.gstatic.com\" crossorigin>\r\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@500;700;800&family=Inter:wght@400;500;600&display=swap\" rel=\"stylesheet\">\r\n<style>\r\n  *{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}\r\n\r\n  :root{\r\n    --bg:#0F1419;\r\n    --surface:#181E25;\r\n    --surface-2:#212933;\r\n    --line:#2C3742;\r\n    --ink:#F2EFE9;\r\n    --muted:#8A97A5;\r\n    --muted-2:#5E6C79;\r\n\r\n    \/* signal ramp \u2014 the meaning lives in these *\/\r\n    --cool:#4FB0C6;\r\n    --calm:#3DD68C;\r\n    --mild:#F5C542;\r\n    --high:#F5852A;\r\n    --severe:#F0483E;\r\n\r\n    --pad:clamp(16px,4vw,28px);\r\n    --safe-t:env(safe-area-inset-top);\r\n    --safe-b:env(safe-area-inset-bottom);\r\n    --focus:#7FD1E3;\r\n  }\r\n\r\n  html{-webkit-text-size-adjust:100%}\r\n  html,body{overflow-x:hidden;max-width:100%}\r\n  body{\r\n    font-family:'Inter',-apple-system,BlinkMacSystemFont,sans-serif;\r\n    background:\r\n      radial-gradient(120% 80% at 50% -10%, #1a2530 0%, var(--bg) 55%) fixed;\r\n    color:var(--ink);\r\n    line-height:1.5;\r\n    -webkit-font-smoothing:antialiased;\r\n    min-height:100dvh;\r\n    padding:\r\n      calc(var(--pad) + var(--safe-t)) var(--pad)\r\n      calc(var(--pad) + var(--safe-b) + 8px);\r\n  }\r\n\r\n  .wrap{max-width:1040px;margin:0 auto;display:flex;flex-direction:column;gap:22px}\r\n\r\n  \/* ---- header ---- *\/\r\n  .eyebrow{\r\n    font-family:'Space Grotesk',sans-serif;\r\n    font-size:.72rem;font-weight:600;letter-spacing:.22em;text-transform:uppercase;\r\n    color:var(--muted);display:flex;align-items:center;gap:10px;\r\n  }\r\n  .eyebrow::before{content:\"\";width:26px;height:1px;background:var(--muted-2)}\r\n  h1{\r\n    font-family:'Space Grotesk',sans-serif;\r\n    font-weight:700;letter-spacing:-.02em;\r\n    font-size:clamp(1.7rem,6vw,2.4rem);margin-top:6px;line-height:1.05;\r\n  }\r\n  h1 em{font-style:normal;color:var(--calm)}\r\n  .sub{color:var(--muted);font-size:.95rem;margin-top:6px;max-width:52ch}\r\n\r\n  \/* ---- layout ---- *\/\r\n  .grid{display:grid;grid-template-columns:1fr;gap:22px}\r\n  @media(min-width:760px){.grid{grid-template-columns:minmax(0,1.1fr) minmax(0,.9fr);align-items:start}}\r\n\r\n  .card{\r\n    background:linear-gradient(180deg,var(--surface) 0%, #141a20 100%);\r\n    border:1px solid var(--line);\r\n    border-radius:22px;\r\n    padding:clamp(18px,4vw,26px);\r\n  }\r\n\r\n  \/* ---- cow id ---- *\/\r\n  .idrow{display:flex;align-items:center;gap:12px;flex-wrap:wrap}\r\n  .idrow label{font-size:.8rem;color:var(--muted);font-weight:500;letter-spacing:.02em}\r\n  .idrow input{\r\n    flex:1;min-width:150px;\r\n    background:var(--surface-2);border:1px solid var(--line);color:var(--ink);\r\n    border-radius:12px;padding:12px 14px;font-size:1rem;font-family:'JetBrains Mono',monospace;\r\n    letter-spacing:.02em;\r\n  }\r\n  .idrow input::placeholder{color:var(--muted-2)}\r\n  .idrow input:focus{outline:2px solid var(--focus);outline-offset:1px;border-color:transparent}\r\n\r\n  \/* ---- gauge ---- *\/\r\n  .gaugewrap{display:flex;flex-direction:column;align-items:center;gap:4px;margin-top:4px}\r\n  .gauge{\r\n    position:relative;\r\n    width:min(80vw, 56vh, 360px);      \/* scales to the shorter side, so it fits portrait AND landscape *\/\r\n    aspect-ratio:1;\r\n    touch-action:manipulation;cursor:pointer;user-select:none;-webkit-user-select:none;\r\n    border-radius:50%;\r\n    overflow:hidden;                    \/* keep the ripple + glow inside the dial \u2014 was causing side-scroll *\/\r\n    container-type:size;                \/* readout text sizes to the dial, never overflows it *\/\r\n  }\r\n  .gauge:focus-visible{outline:3px solid var(--focus);outline-offset:6px}\r\n  .gauge svg{width:100%;height:100%;display:block;overflow:visible}\r\n  .track{fill:none;stroke:var(--line);stroke-width:14;stroke-linecap:round}\r\n  .value{fill:none;stroke-width:14;stroke-linecap:round;\r\n    transition:d .18s ease, stroke .3s ease;\r\n    filter:drop-shadow(0 0 10px currentColor)}\r\n  .tick{stroke:var(--muted-2);stroke-width:2}\r\n  .ticklbl{fill:var(--muted-2);font-family:'JetBrains Mono',monospace;font-size:11px;font-weight:500}\r\n\r\n  .readout{\r\n    position:absolute;inset:0;display:flex;flex-direction:column;\r\n    align-items:center;justify-content:center;pointer-events:none;text-align:center;\r\n  }\r\n  .bpm{\r\n    font-family:'JetBrains Mono',monospace;font-weight:800;\r\n    font-size:clamp(2.4rem,30cqw,4.6rem);line-height:.9;letter-spacing:-.02em;\r\n    font-variant-numeric:tabular-nums;transition:color .3s ease;\r\n  }\r\n  .bpm-unit{font-family:'Space Grotesk',sans-serif;font-size:clamp(.6rem,6.5cqw,.82rem);font-weight:600;\r\n    letter-spacing:.22em;text-transform:uppercase;color:var(--muted);margin-top:3px}\r\n  .band{\r\n    font-family:'Space Grotesk',sans-serif;font-weight:600;\r\n    font-size:clamp(.76rem,7cqw,.95rem);padding:0 8px;\r\n    margin-top:8px;letter-spacing:.01em;transition:color .3s ease;min-height:1.2em}\r\n  .taphint{color:var(--muted);font-size:.82rem;margin-top:2px;transition:opacity .3s}\r\n\r\n  \/* ripple *\/\r\n  .ripple{position:absolute;top:50%;left:50%;width:20px;height:20px;border-radius:50%;\r\n    transform:translate(-50%,-50%) scale(1);pointer-events:none;\r\n    border:2px solid currentColor;opacity:.55;animation:ring .6s ease-out forwards}\r\n  @keyframes ring{to{transform:translate(-50%,-50%) scale(11);opacity:0}}\r\n\r\n  \/* ---- meta strip ---- *\/\r\n  .meta{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin-top:20px}\r\n  .stat{background:var(--surface-2);border:1px solid var(--line);border-radius:14px;\r\n    padding:12px 10px;text-align:center}\r\n  .stat .n{font-family:'JetBrains Mono',monospace;font-weight:700;font-size:1.4rem;\r\n    font-variant-numeric:tabular-nums}\r\n  .stat .k{font-size:.68rem;color:var(--muted);letter-spacing:.14em;text-transform:uppercase;\r\n    margin-top:3px;font-family:'Space Grotesk',sans-serif;font-weight:500}\r\n\r\n  \/* ---- target chips ---- *\/\r\n  .chips{display:flex;gap:8px;align-items:center;margin-top:20px;flex-wrap:wrap}\r\n  .chips .lbl{font-size:.75rem;color:var(--muted);letter-spacing:.1em;text-transform:uppercase;\r\n    font-family:'Space Grotesk',sans-serif;font-weight:500;margin-right:2px}\r\n  .chip{\r\n    background:var(--surface-2);border:1px solid var(--line);color:var(--muted);\r\n    border-radius:999px;padding:9px 16px;font-family:'JetBrains Mono',monospace;font-weight:600;\r\n    font-size:.9rem;cursor:pointer;min-height:44px;transition:.15s}\r\n  .chip[aria-pressed=\"true\"]{background:var(--ink);color:#10161c;border-color:var(--ink)}\r\n  .chip:focus-visible{outline:2px solid var(--focus);outline-offset:2px}\r\n\r\n  \/* ---- actions ---- *\/\r\n  .actions{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin-top:20px}\r\n  .btn{\r\n    border:1px solid var(--line);background:var(--surface-2);color:var(--ink);\r\n    border-radius:14px;padding:16px;font-size:1.02rem;font-weight:600;cursor:pointer;\r\n    min-height:56px;font-family:'Space Grotesk',sans-serif;letter-spacing:.01em;\r\n    transition:.15s;touch-action:manipulation}\r\n  .btn:active{transform:scale(.98)}\r\n  .btn:focus-visible{outline:2px solid var(--focus);outline-offset:2px}\r\n  .btn-primary{background:var(--calm);border-color:var(--calm);color:#08130d}\r\n  .btn-primary:disabled{background:var(--surface-2);border-color:var(--line);\r\n    color:var(--muted-2);cursor:not-allowed;transform:none}\r\n\r\n  \/* ---- reference legend ---- *\/\r\n  .legend-title{font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:1.05rem;\r\n    margin-bottom:4px}\r\n  .legend-note{color:var(--muted);font-size:.85rem;margin-bottom:16px}\r\n  .lg{display:flex;align-items:flex-start;gap:12px;padding:11px 0;border-top:1px solid var(--line)}\r\n  .lg:first-of-type{border-top:none}\r\n  .dot{width:12px;height:12px;border-radius:50%;margin-top:5px;flex:none;\r\n    box-shadow:0 0 8px currentColor}\r\n  .lg .r{font-family:'JetBrains Mono',monospace;font-weight:700;font-size:.9rem;flex:none;\r\n    width:70px}\r\n  .lg .t{font-weight:600;font-size:.9rem}\r\n  .lg .d{color:var(--muted);font-size:.82rem}\r\n\r\n  \/* ---- log ---- *\/\r\n  .loghead{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:12px}\r\n  .loghead h3{font-family:'Space Grotesk',sans-serif;font-weight:600;font-size:1.05rem}\r\n  .copybtn{background:none;border:1px solid var(--line);color:var(--muted);border-radius:10px;\r\n    padding:8px 12px;font-size:.8rem;cursor:pointer;font-family:'Space Grotesk',sans-serif;\r\n    font-weight:500;min-height:40px}\r\n  .copybtn:hover{color:var(--ink);border-color:var(--muted)}\r\n  .copybtn:disabled{opacity:.4;cursor:not-allowed}\r\n  .empty{color:var(--muted-2);font-size:.88rem;padding:18px 0;text-align:center;\r\n    border:1px dashed var(--line);border-radius:12px}\r\n  .entry{display:flex;align-items:center;gap:12px;padding:11px 0;border-top:1px solid var(--line)}\r\n  .entry:first-child{border-top:none}\r\n  .entry .ebpm{font-family:'JetBrains Mono',monospace;font-weight:700;font-size:1.15rem;\r\n    width:52px;text-align:right;font-variant-numeric:tabular-nums}\r\n  .entry .einfo{flex:1;min-width:0}\r\n  .entry .eid{font-weight:600;font-size:.9rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\r\n  .entry .emeta{color:var(--muted);font-size:.76rem;font-family:'JetBrains Mono',monospace}\r\n  .entry .del{background:none;border:none;color:var(--muted-2);cursor:pointer;font-size:1.3rem;\r\n    padding:6px 8px;line-height:1;border-radius:8px;min-height:36px}\r\n  .entry .del:hover{color:var(--severe)}\r\n\r\n  \/* toast *\/\r\n  .toast{position:fixed;left:50%;bottom:calc(20px + var(--safe-b));transform:translateX(-50%) translateY(20px);\r\n    background:var(--ink);color:#10161c;padding:12px 20px;border-radius:999px;font-weight:600;\r\n    font-size:.9rem;font-family:'Space Grotesk',sans-serif;opacity:0;pointer-events:none;\r\n    transition:.25s;z-index:50;box-shadow:0 8px 30px rgba(0,0,0,.4)}\r\n  .toast.show{opacity:1;transform:translateX(-50%) translateY(0)}\r\n\r\n  .disclaimer{color:var(--muted-2);font-size:.78rem;line-height:1.55;text-align:center;\r\n    max-width:60ch;margin:0 auto}\r\n\r\n  \/* small phones (iPhone 13 portrait \u2248 390px): tighten so the dial + controls fit *\/\r\n  @media(max-width:520px){\r\n    .wrap{gap:16px}\r\n    .sub{font-size:.9rem}\r\n    .meta,.chips,.actions{margin-top:16px}\r\n    .card{border-radius:20px}\r\n  }\r\n\r\n  \/* short landscape (phone on its side): shrink the dial, keep controls reachable *\/\r\n  @media(max-height:520px) and (orientation:landscape){\r\n    body{padding-top:calc(14px + var(--safe-t))}\r\n    h1{font-size:1.5rem}\r\n    .sub{display:none}\r\n    .gauge{width:min(52vh,300px)}\r\n  }\r\n\r\n  @media(prefers-reduced-motion:reduce){\r\n    *{animation-duration:.01ms!important;transition-duration:.01ms!important}\r\n  }\r\n<\/style>\r\n<\/head>\r\n<body>\r\n<div class=\"wrap\">\r\n\r\n  <header>\r\n    <div class=\"eyebrow\">Field respiration counter<\/div>\r\n    <h1>Tap each <em>breath<\/em>.<\/h1>\r\n    <p class=\"sub\">Watch the flank and tap the dial on every full breath. The rate reads live and shifts colour as it climbs through the heat-load bands.<\/p>\r\n  <\/header>\r\n\r\n  <div class=\"grid\">\r\n\r\n    <!-- LEFT: the instrument -->\r\n    <section class=\"card\">\r\n      <div class=\"idrow\">\r\n        <label for=\"cowId\">Animal \/ pen<\/label>\r\n        <input id=\"cowId\" type=\"text\" inputmode=\"text\" autocomplete=\"off\"\r\n               placeholder=\"e.g. 1147 or Pen 3\" aria-label=\"Animal or pen identifier\">\r\n      <\/div>\r\n\r\n      <div class=\"gaugewrap\">\r\n        <div class=\"gauge\" id=\"gauge\" role=\"button\" tabindex=\"0\"\r\n             aria-label=\"Tap on each breath to count. Press Enter or Space to register a breath.\">\r\n          <svg viewBox=\"0 0 300 300\" aria-hidden=\"true\">\r\n            <path class=\"track\" id=\"track\"><\/path>\r\n            <path class=\"value\" id=\"arc\" style=\"color:var(--calm);stroke:var(--calm)\"><\/path>\r\n            <g id=\"ticks\"><\/g>\r\n          <\/svg>\r\n          <div class=\"readout\">\r\n            <div class=\"bpm\" id=\"bpm\" style=\"color:var(--muted-2)\">\u2013\u2013<\/div>\r\n            <div class=\"bpm-unit\">breaths \/ min<\/div>\r\n            <div class=\"band\" id=\"bandLabel\" style=\"color:var(--muted)\">Ready<\/div>\r\n          <\/div>\r\n        <\/div>\r\n        <div class=\"taphint\" id=\"taphint\">Tap to begin<\/div>\r\n      <\/div>\r\n\r\n      <div class=\"meta\">\r\n        <div class=\"stat\"><div class=\"n\" id=\"count\">0<\/div><div class=\"k\">Breaths<\/div><\/div>\r\n        <div class=\"stat\"><div class=\"n\" id=\"elapsed\">0.0<\/div><div class=\"k\">Seconds<\/div><\/div>\r\n        <div class=\"stat\"><div class=\"n\" id=\"stability\">\u2014<\/div><div class=\"k\">Confidence<\/div><\/div>\r\n      <\/div>\r\n\r\n      <div class=\"chips\" role=\"group\" aria-label=\"Sample length\">\r\n        <span class=\"lbl\">Sample<\/span>\r\n        <button class=\"chip\" data-target=\"0\" aria-pressed=\"true\">Live<\/button>\r\n        <button class=\"chip\" data-target=\"10\" aria-pressed=\"false\">10<\/button>\r\n        <button class=\"chip\" data-target=\"15\" aria-pressed=\"false\">15<\/button>\r\n        <button class=\"chip\" data-target=\"20\" aria-pressed=\"false\">20<\/button>\r\n      <\/div>\r\n\r\n      <div class=\"actions\">\r\n        <button class=\"btn\" id=\"resetBtn\">Reset<\/button>\r\n        <button class=\"btn btn-primary\" id=\"saveBtn\" disabled>Save reading<\/button>\r\n      <\/div>\r\n    <\/section>\r\n\r\n    <!-- RIGHT: reference + log -->\r\n    <div style=\"display:flex;flex-direction:column;gap:22px\">\r\n      <section class=\"card\">\r\n        <div class=\"legend-title\">Heat-load bands<\/div>\r\n        <div class=\"legend-note\">Resting adult dairy cattle typically run 26\u201350 bpm. Elevated rate is an early heat-stress signal.<\/div>\r\n        <div class=\"lg\"><span class=\"dot\" style=\"color:var(--cool)\"><\/span><span class=\"r\">&lt; 20<\/span><span><span class=\"t\">Below resting<\/span><br><span class=\"d\">Recheck the animal and your count.<\/span><\/span><\/div>\r\n        <div class=\"lg\"><span class=\"dot\" style=\"color:var(--calm)\"><\/span><span class=\"r\">20\u201360<\/span><span><span class=\"t\">Normal \/ calm<\/span><br><span class=\"d\">Comfortable, thermally neutral.<\/span><\/span><\/div>\r\n        <div class=\"lg\"><span class=\"dot\" style=\"color:var(--mild)\"><\/span><span class=\"r\">61\u201380<\/span><span><span class=\"t\">Mild heat load<\/span><br><span class=\"d\">Check water access and airflow.<\/span><\/span><\/div>\r\n        <div class=\"lg\"><span class=\"dot\" style=\"color:var(--high)\"><\/span><span class=\"r\">81\u2013120<\/span><span><span class=\"t\">High heat stress<\/span><br><span class=\"d\">Active cooling advised.<\/span><\/span><\/div>\r\n        <div class=\"lg\"><span class=\"dot\" style=\"color:var(--severe)\"><\/span><span class=\"r\">&gt; 120<\/span><span><span class=\"t\">Severe \u2014 panting<\/span><br><span class=\"d\">Urgent cooling; open-mouth breathing likely.<\/span><\/span><\/div>\r\n      <\/section>\r\n\r\n      <section class=\"card\">\r\n        <div class=\"loghead\">\r\n          <h3>Session log<\/h3>\r\n          <button class=\"copybtn\" id=\"copyBtn\" disabled>Copy all<\/button>\r\n        <\/div>\r\n        <div id=\"logBody\">\r\n          <div class=\"empty\">Saved readings appear here. They clear when you close the page \u2014 copy them out for your report.<\/div>\r\n        <\/div>\r\n      <\/section>\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <p class=\"disclaimer\">A field aid for counting flank movements on cattle, not a diagnostic device. Band thresholds are general guidance \u2014 confirm against your own protocols and clinical judgement.<\/p>\r\n<\/div>\r\n\r\n<div class=\"toast\" id=\"toast\"><\/div>\r\n\r\n<script>\r\n(() => {\r\n  \"use strict\";\r\n\r\n  \/\/ ---- band model ----\r\n  const BANDS = [\r\n    {max:20,  color:'#4FB0C6', label:'Below resting'},\r\n    {max:60,  color:'#3DD68C', label:'Normal \/ calm'},\r\n    {max:80,  color:'#F5C542', label:'Mild heat load'},\r\n    {max:120, color:'#F5852A', label:'High heat stress'},\r\n    {max:1e9, color:'#F0483E', label:'Severe \u2014 panting'}\r\n  ];\r\n  const bandFor = v => BANDS.find(b => v <= b.max);\r\n\r\n  \/\/ ---- gauge geometry (0deg = top, clockwise; 90deg gap at bottom) ----\r\n  const CX=150, CY=150, R=118, MIN_A=225, SPAN=270, MAXV=140;\r\n  const polar=(deg)=>{const a=(deg-90)*Math.PI\/180;return{x:CX+R*Math.cos(a),y:CY+R*Math.sin(a)};};\r\n  const angleFor=v=>MIN_A+(Math.min(Math.max(v,0),MAXV)\/MAXV)*SPAN;\r\n  function arcPath(a0,a1){\r\n    const s=polar(a0),e=polar(a1),large=(a1-a0)>180?1:0;\r\n    return `M ${s.x.toFixed(2)} ${s.y.toFixed(2)} A ${R} ${R} 0 ${large} 1 ${e.x.toFixed(2)} ${e.y.toFixed(2)}`;\r\n  }\r\n  const $=id=>document.getElementById(id);\r\n  $('track').setAttribute('d', arcPath(MIN_A, MIN_A+SPAN));\r\n\r\n  \/\/ ticks at band boundaries\r\n  (function(){\r\n    const g=$('ticks');\r\n    [60,80,120].forEach(v=>{\r\n      const a=angleFor(v), deg=(a-90)*Math.PI\/180;\r\n      const inner=R-11, outer=R+4;\r\n      const x1=CX+inner*Math.cos(deg), y1=CY+inner*Math.sin(deg);\r\n      const x2=CX+outer*Math.cos(deg), y2=CY+outer*Math.sin(deg);\r\n      const line=document.createElementNS('http:\/\/www.w3.org\/2000\/svg','line');\r\n      line.setAttribute('x1',x1.toFixed(1));line.setAttribute('y1',y1.toFixed(1));\r\n      line.setAttribute('x2',x2.toFixed(1));line.setAttribute('y2',y2.toFixed(1));\r\n      line.setAttribute('class','tick');g.appendChild(line);\r\n      const lx=CX+(R+16)*Math.cos(deg), ly=CY+(R+16)*Math.sin(deg);\r\n      const txt=document.createElementNS('http:\/\/www.w3.org\/2000\/svg','text');\r\n      txt.setAttribute('x',lx.toFixed(1));txt.setAttribute('y',(ly+4).toFixed(1));\r\n      txt.setAttribute('text-anchor','middle');txt.setAttribute('class','ticklbl');\r\n      txt.textContent=v;g.appendChild(txt);\r\n    });\r\n  })();\r\n\r\n  \/\/ ---- state ----\r\n  let taps=[];          \/\/ timestamps (ms)\r\n  let target=0;         \/\/ 0 = live rolling\r\n  let locked=false;     \/\/ sample complete\r\n  let raf=null;\r\n  let wakeLock=null;\r\n  const readings=[];\r\n\r\n  const el={\r\n    gauge:$('gauge'),arc:$('arc'),bpm:$('bpm'),bandLabel:$('bandLabel'),\r\n    taphint:$('taphint'),count:$('count'),elapsed:$('elapsed'),stability:$('stability'),\r\n    saveBtn:$('saveBtn'),resetBtn:$('resetBtn'),copyBtn:$('copyBtn'),\r\n    cowId:$('cowId'),logBody:$('logBody'),toast:$('toast')\r\n  };\r\n\r\n  \/\/ ---- wake lock (keep screen on while counting) ----\r\n  async function requestWake(){\r\n    try{ if('wakeLock' in navigator && !wakeLock){ wakeLock=await navigator.wakeLock.request('screen');\r\n      wakeLock.addEventListener('release',()=>{wakeLock=null;}); } }catch(_){}\r\n  }\r\n  function releaseWake(){ try{ wakeLock&&wakeLock.release(); }catch(_){} wakeLock=null; }\r\n  document.addEventListener('visibilitychange',()=>{\r\n    if(document.visibilityState==='visible' && taps.length && !locked) requestWake();\r\n  });\r\n\r\n  \/\/ ---- computation ----\r\n  function computeBpm(useAll){\r\n    if(taps.length<2) return null;\r\n    const ivals=[];\r\n    for(let i=1;i<taps.length;i++) ivals.push(taps[i]-taps[i-1]);\r\n    const window = useAll ? ivals : ivals.slice(-8);\r\n    const avg = window.reduce((a,b)=>a+b,0)\/window.length;\r\n    if(avg<=0) return null;\r\n    return 60000\/avg;\r\n  }\r\n\r\n  function confidenceLabel(){\r\n    const n=taps.length;\r\n    if(n<2) return '\u2014';\r\n    if(locked) return 'Locked';\r\n    if(n<4) return 'Low';\r\n    if(n<7) return 'Fair';\r\n    return 'Good';\r\n  }\r\n\r\n  function render(){\r\n    const bpm = computeBpm(locked); \/\/ rolling while live, all-intervals when locked\r\n    const n = taps.length;\r\n    el.count.textContent = n;\r\n\r\n    if(taps.length){\r\n      const span=((taps[taps.length-1]-taps[0])\/1000);\r\n      el.elapsed.textContent = span.toFixed(1);\r\n    } else {\r\n      el.elapsed.textContent = '0.0';\r\n    }\r\n    el.stability.textContent = confidenceLabel();\r\n\r\n    if(bpm==null){\r\n      el.bpm.textContent='\u2013\u2013';\r\n      el.bpm.style.color='var(--muted-2)';\r\n      el.arc.setAttribute('d','');\r\n      el.bandLabel.textContent = n===1 ? 'One more breath\u2026' : 'Ready';\r\n      el.bandLabel.style.color='var(--muted)';\r\n      el.saveBtn.disabled=true;\r\n      return;\r\n    }\r\n    const rounded=Math.round(bpm);\r\n    const band=bandFor(rounded);\r\n    el.bpm.textContent=rounded;\r\n    el.bpm.style.color=band.color;\r\n    el.bandLabel.textContent = locked ? band.label+' \u00b7 locked' : band.label;\r\n    el.bandLabel.style.color=band.color;\r\n    el.arc.style.stroke=band.color;\r\n    el.arc.style.color=band.color;\r\n    el.arc.setAttribute('d', arcPath(MIN_A, angleFor(rounded)));\r\n    el.saveBtn.disabled=false;\r\n  }\r\n\r\n  \/\/ ---- tap handling ----\r\n  function ripple(color){\r\n    const r=document.createElement('span');\r\n    r.className='ripple'; r.style.color=color||'var(--calm)';\r\n    el.gauge.appendChild(r);\r\n    setTimeout(()=>r.remove(),620);\r\n  }\r\n  function tick(){ render(); if(taps.length && !locked) raf=requestAnimationFrame(tick); }\r\n\r\n  function addBreath(){\r\n    if(locked) return;\r\n    const now=performance.now();\r\n    if(taps.length===0){ requestWake(); }\r\n    taps.push(now);\r\n    el.taphint.style.opacity = taps.length<2 ? '1' : '0';\r\n    if(taps.length<2) el.taphint.textContent='keep tapping\u2026';\r\n\r\n    const preview = computeBpm(false);\r\n    ripple(preview!=null ? bandFor(Math.round(preview)).color : 'var(--calm)');\r\n    if(navigator.vibrate) navigator.vibrate(12);\r\n\r\n    \/\/ target reached -> lock\r\n    if(target>0 && taps.length>=target){\r\n      locked=true;\r\n      cancelAnimationFrame(raf); raf=null;\r\n      releaseWake();\r\n      el.taphint.textContent='sample complete \u2014 save or reset';\r\n      el.taphint.style.opacity='1';\r\n      if(navigator.vibrate) navigator.vibrate([20,40,20]);\r\n      render();\r\n      return;\r\n    }\r\n    if(!raf) raf=requestAnimationFrame(tick);\r\n    render();\r\n  }\r\n\r\n  el.gauge.addEventListener('pointerdown', e=>{ e.preventDefault(); addBreath(); });\r\n  el.gauge.addEventListener('keydown', e=>{\r\n    if(e.key==='Enter'||e.key===' '){ e.preventDefault(); addBreath(); }\r\n  });\r\n\r\n  function reset(){\r\n    taps=[]; locked=false;\r\n    cancelAnimationFrame(raf); raf=null;\r\n    releaseWake();\r\n    el.taphint.textContent='Tap to begin';\r\n    el.taphint.style.opacity='1';\r\n    el.stability.textContent='\u2014';\r\n    render();\r\n  }\r\n  el.resetBtn.addEventListener('click', reset);\r\n\r\n  \/\/ ---- target chips ----\r\n  document.querySelectorAll('.chip').forEach(chip=>{\r\n    chip.addEventListener('click', ()=>{\r\n      document.querySelectorAll('.chip').forEach(c=>c.setAttribute('aria-pressed','false'));\r\n      chip.setAttribute('aria-pressed','true');\r\n      target=parseInt(chip.dataset.target,10);\r\n      \/\/ if switching mid-sample and already past a smaller target, re-evaluate\r\n      if(target>0 && taps.length>=target){ locked=true; cancelAnimationFrame(raf); raf=null; }\r\n      else { locked=false; if(taps.length && !raf) raf=requestAnimationFrame(tick); }\r\n      render();\r\n    });\r\n  });\r\n\r\n  \/\/ ---- save \/ log ----\r\n  function fmtClock(d){\r\n    return d.toLocaleTimeString([], {hour:'2-digit',minute:'2-digit'});\r\n  }\r\n  function renderLog(){\r\n    if(readings.length===0){\r\n      el.logBody.innerHTML='<div class=\"empty\">Saved readings appear here. They clear when you close the page \u2014 copy them out for your report.<\/div>';\r\n      el.copyBtn.disabled=true; return;\r\n    }\r\n    el.copyBtn.disabled=false;\r\n    el.logBody.innerHTML='';\r\n    readings.forEach(r=>{\r\n      const row=document.createElement('div'); row.className='entry';\r\n      row.innerHTML=\r\n        `<div class=\"ebpm\" style=\"color:${r.color}\">${r.bpm}<\/div>\r\n         <div class=\"einfo\">\r\n           <div class=\"eid\">${r.id||'Unlabelled'}<\/div>\r\n           <div class=\"emeta\">${r.band} \u00b7 ${r.breaths} breaths \u00b7 ${r.clock}<\/div>\r\n         <\/div>\r\n         <button class=\"del\" aria-label=\"Delete reading\">\u00d7<\/button>`;\r\n      row.querySelector('.del').addEventListener('click',()=>{\r\n        const i=readings.indexOf(r); if(i>-1) readings.splice(i,1); renderLog();\r\n      });\r\n      el.logBody.appendChild(row);\r\n    });\r\n  }\r\n  el.saveBtn.addEventListener('click',()=>{\r\n    const bpm=computeBpm(locked); if(bpm==null) return;\r\n    const rounded=Math.round(bpm), band=bandFor(rounded);\r\n    readings.unshift({\r\n      id:el.cowId.value.trim(), bpm:rounded, band:band.label, color:band.color,\r\n      breaths:taps.length, clock:fmtClock(new Date())\r\n    });\r\n    renderLog();\r\n    showToast(`Saved ${rounded} bpm${el.cowId.value.trim()?' \u00b7 '+el.cowId.value.trim():''}`);\r\n    if(navigator.vibrate) navigator.vibrate(30);\r\n    el.cowId.value=''; reset();\r\n  });\r\n\r\n  el.copyBtn.addEventListener('click', async ()=>{\r\n    const date=new Date().toLocaleDateString();\r\n    const lines=[`Respiration readings \u2014 ${date}`,''];\r\n    readings.slice().reverse().forEach(r=>{\r\n      lines.push(`${(r.id||'Unlabelled').padEnd(12)}  ${String(r.bpm).padStart(3)} bpm   ${r.band}   (${r.breaths} breaths, ${r.clock})`);\r\n    });\r\n    const text=lines.join('\\n');\r\n    try{ await navigator.clipboard.writeText(text); showToast('Copied to clipboard'); }\r\n    catch(_){\r\n      \/\/ fallback: selectable prompt\r\n      const ta=document.createElement('textarea');\r\n      ta.value=text; ta.style.position='fixed'; ta.style.top='-999px';\r\n      document.body.appendChild(ta); ta.select();\r\n      try{ document.execCommand('copy'); showToast('Copied to clipboard'); }\r\n      catch(e){ showToast('Copy blocked \u2014 select the log manually'); }\r\n      ta.remove();\r\n    }\r\n  });\r\n\r\n  \/\/ ---- toast ----\r\n  let toastT=null;\r\n  function showToast(msg){\r\n    el.toast.textContent=msg; el.toast.classList.add('show');\r\n    clearTimeout(toastT); toastT=setTimeout(()=>el.toast.classList.remove('show'),1900);\r\n  }\r\n\r\n  render();\r\n})();\r\n<\/script>\r\n<\/body>\r\n<\/html>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Breath \u2014 Bovine Respiration Counter Field respiration counter Tap each breath. Watch the flank and tap the dial on every full breath. The rate reads live and shifts colour as it climbs through the heat-load bands. Animal \/ pen \u2013\u2013<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-629","page","type-page","status-publish","hentry"],"brizy_media":[],"_links":{"self":[{"href":"https:\/\/dr-nutrition.ca\/index.php?rest_route=\/wp\/v2\/pages\/629","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/dr-nutrition.ca\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/dr-nutrition.ca\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/dr-nutrition.ca\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dr-nutrition.ca\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=629"}],"version-history":[{"count":5,"href":"https:\/\/dr-nutrition.ca\/index.php?rest_route=\/wp\/v2\/pages\/629\/revisions"}],"predecessor-version":[{"id":2483,"href":"https:\/\/dr-nutrition.ca\/index.php?rest_route=\/wp\/v2\/pages\/629\/revisions\/2483"}],"wp:attachment":[{"href":"https:\/\/dr-nutrition.ca\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=629"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}