Help
RSS
API
Feed
Maltego
Contact
Domain > chantcounter.com
×
More information on this domain is in
AlienVault OTX
Is this malicious?
Yes
No
DNS Resolutions
Date
IP Address
2025-12-15
104.21.10.174
(
ClassC
)
Port 443
HTTP/1.1 200 OKDate: Mon, 15 Dec 2025 09:34:23 GMTContent-Type: text/html; charsetutf-8Transfer-Encoding: chunkedConnection: keep-aliveAccess-Control-Allow-Origin: *Cache-Control: public, max-age0, must-revalidatereferrer-policy: strict-origin-when-cross-originx-content-type-options: nosniffVary: accept-encodingReport-To: {group:cf-nel,max_age:604800,endpoints:{url:https://a.nel.cloudflare.com/report/v4?sSTBRh69rvM9vbu5J2FdCpNeNWqPiF5pvQtX9Z1dQqaCtqh6oWOQIXIJI0so6W%2F3jNIa57ShRN1GtCORLtHHdePIULzn9VuCqgjFU5biXJj0%3D}}Nel: {report_to:cf-nel,success_fraction:0.0,max_age:604800}Server: cloudflarecf-cache-status: DYNAMICCF-RAY: 9ae4f1e41af423a3-PDXalt-svc: h3:443; ma86400 !doctype html>html langen>head> !-- SEO --> title>Chant Counter – Free Online Japa Mala/Mantra Counter (108) with Custom Targets/title> meta namedescription contentCount mantra repetitions with the Chant Counter. Easily Count mantras. Supports showing mantras as well. 108-cycle counting, custom targets, night mode, fullscreen, export/import, and milestone tones. > meta charsetutf-8 /> meta nameviewport contentwidthdevice-width, initial-scale1, viewport-fitcover /> meta nametheme-color content#0f172a /> meta namemobile-web-app-capable contentyes /> meta nameapple-mobile-web-app-capable contentyes /> meta nameapple-mobile-web-app-status-bar-style contentblack-translucent /> link relmanifest hrefmanifest.webmanifest /> !-- Google Tag Manager --> script>(function(w,d,s,l,i){wlwl||;wl.push({gtm.start: new Date().getTime(),event:gtm.js});var fd.getElementsByTagName(s)0, jd.createElement(s),dll!dataLayer?&l+l:;j.asynctrue;j.src https://www.googletagmanager.com/gtm.js?id+i+dl;f.parentNode.insertBefore(j,f); })(window,document,script,dataLayer,GTM-5WDD2R8);/script> !-- End Google Tag Manager --> style> :root{ --bg:#0f172a; --text:#e5e7eb; --muted:#9ca3af; --accent:#22c55e; --accent-pressed:#16a34a; --danger:#f43f5e; --danger-pressed:#e11d48; --ring:#22c55e55; --radius:16px; --hit:44px; --tap-height:148px; --tap-font:36px; --undo-height:36px; --undo-font:13px; --target:#f59e0b; --live-label:#0b1220; --live-chip:#10b98122; --live-chip-border:#10b98155; } *{box-sizing:border-box} html,body{height:100%} body{ margin:0; font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif; background:linear-gradient(180deg,#0b1023,var(--bg) 40%,#0b1023); color:var(--text); -webkit-font-smoothing:antialiased; -webkit-tap-highlight-color:transparent; display:grid; grid-template-rows:1fr auto; min-height:100dvh; } body.night{background:#000!important;color:#9ca3af} body.night .wrap, body.night .card, body.night .btn-secondary, body.night .btn-danger, body.night .pill, body.night .icon-btn{background:#000!important;border:1px solid #0a0a0a!important;box-shadow:none!important;color:#9ca3af!important} body.night .btn-primary{background:#0a0a0a!important;color:#d1d5db!important;box-shadow:none!important} .night-dimmer{position:fixed;inset:0;pointer-events:none;opacity:0;background:#000;transition:opacity 200ms ease} .night-dimmer.show{opacity:var(--night-dim,0)} .wrap{ width:100%; display:grid; grid-template-rows:auto auto 1fr auto; gap:12px; padding:env(safe-area-inset-top) 16px env(safe-area-inset-bottom); max-width:640px;margin:0 auto; } header{display:flex;align-items:center;justify-content:space-between;padding-top:8px} header h1{font-size:clamp(18px,4.5vw,22px);margin:0} :fullscreen header h1, :-webkit-full-screen header h1{display:none} .header-actions{display:inline-flex;align-items:center;gap:8px} .pill{ display:inline-flex;align-items:center;gap:8px; background:#0b1220;border:1px solid #1f2937;border-radius:999px; padding:8px 12px;color:var(--muted);font-size:13px; } #status:empty{display:none} .icon-btn{ background:#0b1220;border:1px solid #1f2937;color:#9ca3af; border-radius:999px;height:36px;width:36px;display:inline-flex;align-items:center;justify-content:center;font-size:16px;cursor:pointer } .net-banner{ display:none;align-items:center;justify-content:center; gap:8px;padding:6px 10px;border-radius:12px;background:#1f2937;color:#fca5a5; border:1px solid #ef444422;font-size:13px; } .net-banner.show{display:flex} main{display:grid;align-content:start;gap:20px} .card{ background:radial-gradient(120% 120% at 50% 0%,#0f172a 0%, #0b1023 60%); border:1px solid #1f2937;border-radius:var(--radius); box-shadow:0 8px 24px #0006,inset 0 1px 0 #ffffff1a; padding:20px;display:grid;gap:16px;position:relative; } .skeleton{position:relative;overflow:hidden} @media (prefers-reduced-motion: no-preference){ .skeleton::after{ content:;position:absolute;inset:0;transform:translateX(-100%); background:linear-gradient(90deg,transparent,#ffffff11,transparent); animation:shimmer 1.2s infinite; } @keyframes shimmer{100%{transform:translateX(100%)}} } .meta-block{display:grid;gap:10px} .meta-line{display:grid;gap:6px} .meta-label{font-size:13px;color:var(--muted)} .meta-value{ font-size:16px;color:var(--text); background:#0b1220;border:1px solid #1f2937;border-radius:10px;padding:10px 12px; white-space:pre-wrap;word-break:break-word; } .meta-textarea{ width:100%;background:#0b1220;border:1px solid #1f2937;color:#e5e7eb; border-radius:12px;padding:10px 12px;font-size:16px;outline:none; resize:vertical;overflow:hidden;min-height:64px;max-height:none;line-height:1.4; } .counts-block{display:grid;gap:10px} .live-label{ text-align:center; font-size:13px; font-weight:700; letter-spacing:.06em; text-transform:uppercase; color:var(--muted); background:transparent; border:0; border-radius:0; padding:0; width:max-content; margin:0 auto -4px auto; } .big-number{ font-size:clamp(56px,24vw,108px); font-weight:900;line-height:1;letter-spacing:.6px;text-align:center; color:#22c55e; text-shadow:0 0 10px #22c55e33, 0 0 24px #22c55e22; display:inline-block; padding:12px 18px; border-radius:18px; background:var(--live-chip); border:1px solid var(--live-chip-border); margin:0 auto; } .line{ display:flex;align-items:baseline;justify-content:space-between;gap:12px } .line .k{color:var(--muted);font-size:14px} .line .v{ color:#22c55e; font-size:18px;font-weight:800; text-shadow:0 0 6px #22c55e22; display:inline-flex;align-items:baseline;gap:6px; } .line .v .current{color:#22c55e} .line .v .target{color:var(--target);font-weight:800} button{ appearance:none;border:0;color:#0a0f1a;border-radius:14px;padding:12px; min-height:var(--hit);font-size:15px;font-weight:700;cursor:pointer;user-select:none; transition:transform 80ms ease,background 120ms ease,opacity 120ms ease,box-shadow 120ms ease } .btn-primary{ background:linear-gradient(180deg,var(--accent),var(--accent-pressed));color:#fff; box-shadow:0 8px 20px #16a34a55,inset 0 -1px 0 #ffffff33; font-size:var(--tap-font);font-weight:900;border-radius:26px;min-height:var(--tap-height);padding:24px;letter-spacing:.6px; } .btn-secondary{background:#0b1220;color:#e5e7eb;border:1px solid #1f2937;min-height:36px;font-size:13px;padding:8px 10px} .btn-danger{background:linear-gradient(180deg,var(--danger),var(--danger-pressed));color:#fff;min-height:36px;font-size:13px;padding:8px 10px} button:active{transform:translateY(1px) scale(.995)} .press-wave{position:relative;overflow:hidden} .press-wave::after{ content:;position:absolute;inset:0;background:radial-gradient(circle at var(--x,50%) var(--y,50%), #ffffff22, transparent 45%); opacity:0;transition:opacity 280ms ease; } .press-wave.wave::after{opacity:1;animation:fadeout .35s forwards} @keyframes fadeout{to{opacity:0}} .actions-primary{display:grid;grid-template-columns:1fr;gap:8px} .undo-row{display:flex;justify-content:flex-end} .undo-pill{ background:#0b1220;border:1px solid #1f2937;color:#e5e7eb;border-radius:999px; padding:8px 12px;height:var(--undo-height);display:inline-flex;align-items:center;gap:8px;font-size:var(--undo-font) } footer{ margin-top:16px; padding:12px 16px calc(12px + env(safe-area-inset-bottom)); background:#0b1220;border-top:1px solid #1f2937; display:flex;flex-wrap:wrap;align-items:center;justify-content:center;gap:12px; } .meta{font-size:12px;color:var(--muted);text-align:center} .footer-links{display:flex;gap:16px;align-items:center;justify-content:center;flex-wrap:wrap} .footer-links a{color:#22c55e;text-decoration:none;font-weight:700} .footer-links a:hover{text-decoration:underline} .sheet-backdrop{position:fixed;inset:0;background:rgba(0,0,0,.5);opacity:0;pointer-events:none;transition:opacity 200ms} .sheet-backdrop.show{opacity:1;pointer-events:auto} .sheet{ position:fixed;left:0;right:0;bottom:0;background:#0f172a;border-top-left-radius:16px;border-top-right-radius:16px; box-shadow:0 -10px 30px rgba(0,0,0,.4);transform:translateY(100%);transition:transform 240ms; max-height:min(88dvh,720px);width:100%;max-width:640px;margin:0 auto; display:grid;grid-template-rows:auto 1fr auto; } .sheet.show{transform:translateY(0)} .sheet header{position:sticky;top:0;z-index:2;background:inherit;padding:14px 16px;border-bottom:1px solid #1f2937;display:flex;align-items:center;justify-content:space-between} .sheet header h3{margin:0;font-size:16px;color:#e5e7eb} .sheet .content{overflow:auto;padding:8px 16px} .sheet footer{position:sticky;bottom:0;z-index:2;background:inherit;padding:12px 16px calc(12px + env(safe-area-inset-bottom));border-top:1px solid #1f2937;display:flex;justify-content:space-between;align-items:center;gap:10px} .group{margin:10px 0 6px;color:#9ca3af;font-size:12px;text-transform:uppercase;letter-spacing:.08em} .row{display:grid;grid-template-columns:1fr auto;align-items:center;padding:10px 0;gap:12px;border-bottom:1px solid #1f2937} .row:last-child{border-bottom:0} .text-input,.textarea{width:100%;background:#0b1220;border:1px solid #1f2937;color:#e5e7eb;border-radius:12px;padding:10px 12px;font-size:16px;outline:none} .textarea{min-height:64px;max-height:480px;resize:vertical} .text-input:focus,.textarea:focus{box-shadow:0 0 0 4px var(--ring);border-color:#2a3b4d} .warn{color:#fca5a5;font-size:12px;line-height:1.4} .range-row{display:grid;grid-template-columns:1fr auto;align-items:center;gap:12px} inputtyperange{width:160px} .subnote{font-size:12px;color:#9ca3af;margin-top:6px} #toast{ position:fixed;left:50%;bottom:18px;transform:translateX(-50%); background:#0b1220;border:1px solid #1f2937;color:#e5e7eb;padding:10px 14px;border-radius:999px; font-size:14px;box-shadow:0 10px 30px rgba(0,0,0,.4);opacity:0;pointer-events:none;transition:opacity 200ms } .skeleton::after{ content:none !important; animation:none !important; } .about h2{margin:0;color:#e5e7eb;font-size:18px} .about h3{margin:12px 0 8px;color:#e5e7eb;font-size:16px} .about p{margin:6px 0 10px;color:#cbd5e1;line-height:1.55} .about ul{margin:6px 0 10px;padding-left:18px;color:#cbd5e1} .about li{margin:4px 0} .formula{font-family:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace; background:#0b1220;border:1px solid #1f2937;border-radius:10px;padding:10px;color:#e5e7eb} .size-readout{ background:#0b1220;border:1px solid #1f2937;color:#9ca3af; border-radius:999px;padding:6px 10px;font-size:12px;margin-left:8px } /* History list items */ .history-list{list-style:none;padding:0;margin:0} .history-item{ display:flex;justify-content:space-between;align-items:center; padding:10px 12px;border:1px solid #1f2937;border-radius:10px;background:#0b1220;margin:8px 0; } .history-date{color:#e5e7eb;font-weight:700} .history-count{color:#22c55e;font-weight:900} .history-chip{ margin-left:10px;padding:4px 8px;border-radius:999px;border:1px solid #1f2937;background:#0b1220;color:#9ca3af;font-size:12px;font-weight:700 } .no-history{color:#9ca3af;font-size:14px;padding:10px 0;text-align:center} /style>/head>body> !-- Google Tag Manager (noscript) --> noscript>iframe srchttps://www.googletagmanager.com/ns.html?idGTM-5WDD2R8 height0 width0 styledisplay:none;visibility:hidden>/iframe>/noscript> !-- End Google Tag Manager (noscript) --> div classwrap idappRoot> header> h1>Chant Counter/h1> div classheader-actions> button idhistoryBtn classicon-btn press-wave aria-labelOpen history>🕑/button> button idfullscreenBtn classicon-btn press-wave aria-labelEnter/exit full screen>⛶/button> button idsettingsBtn classicon-btn press-wave aria-labelOpen settings>⚙️/button> span classpill idstatus>/span> /div> /header> div idnetBanner classnet-banner rolestatus aria-livepolite> Offline mode. Actions will be queued and data is saved locally. /div> main> section classcard idmetaOnMain styledisplay:none;> div classmeta-block> div classmeta-line idcnLine styledisplay:none;> div classmeta-label>Counter Name/div> div classmeta-value idcnValue>/div> /div> div classmeta-line idmantraLine styledisplay:none;> div classmeta-label>Mantra/div> textarea idmantraValue classmeta-textarea placeholderEnter mantra...>/textarea> /div> div classmeta-line idnoteLine styledisplay:none;> div classmeta-label>Text Note/div> textarea idnoteValue classmeta-textarea placeholderEnter slokam/mantra text...>/textarea> /div> /div> /section> section classcard idcard> div classcounts-block idcountsBlock> div classlive-label aria-hiddentrue>Live Count/div> div classbig-number idtotalBig>0/div> div classline> div classk>Today/div> div classv> span classcurrent idtodayLine>0/span>/span classtarget idtodayTargetInline>/span> /div> /div> div classline> div classk> No of times span idcycleLabel>108/span> counts completed /div> div classv> span classcurrent idtimesCycles>0/span>/span classtarget idcyclesTargetInline>/span> /div> /div> div classline> div classk>Lifetime counts/div> div classv> span classcurrent idlifetimeLine>0/span>/span classtarget idlifetimeTargetInline>/span> /div> /div> /div> div classactions-primary> div classundo-row> button idundoBtn classundo-pill aria-labelUndo last count>Undo/button> /div> button idtap classbtn-primary aria-labelCount mantra>Count +1/button> /div> /section> section classcard about> h2>About this Chant Counter/h2> h3>What this tool does/h3> p> This tool tracks mantra repetitions with a large “Count +1” button, showing today’s total, lifetime total, and how many full cycles are completed based on a configurable cycle size (default 108). It supports targets, night mode, fullscreen, offline-first usage, vibration and tone feedback for milestones, and backup/restore via JSON export/import. /p> h3>Tips/h3> ul> li>Use Night Screen and Keep Screen On for extended sessions./li> li>Enable sound once to hear cycle/target tones; use Test Sound in Settings./li> li>Backup progress via Export JSON and restore via Import JSON./li> /ul> /section> /main> /div> footer> div classfooter-links> a hrefhttps://tools.pkarun.com target_blank relnoopener>More Tools/a> span classmeta>•/span> a hrefhttps://tools.pkarun.com/contact-us target_blank relnoopener>Contact Us/a> /div> /footer> !-- Backdrop shared by sheets --> div idsheetBackdrop classsheet-backdrop>/div> !-- Settings Sheet --> div idsettingsSheet classsheet roledialog aria-modaltrue aria-labelledbysettingsTitle> header> h3 idsettingsTitle>Settings/h3> button idcloseSettings classbtn-secondary press-wave>Close/button> /header> div classcontent> div classgroup>Display on Main/div> div classrow> div>Show Counter Name/div> button idbtnShowCounterName classbtn-secondary press-wave stylemin-width:180px>Counter Name: Off/button> /div> div classrow idrowCounterName styledisplay:none;grid-template-columns:1fr;> input idcounterName classtext-input placeholdere.g., Maha Mrityunjaya Japa /> /div> div classrow> div>Show Mantra/div> button idbtnShowMantra classbtn-secondary press-wave stylemin-width:180px>Mantra: Off/button> /div> div classrow idrowMantra styledisplay:none;grid-template-columns:1fr;> textarea idmantraSetting classtextarea placeholderEnter mantra...>/textarea> /div> div classrow> div>Show Text Note/div> button idbtnShowNote classbtn-secondary press-wave stylemin-width:180px>Text Note: Off/button> /div> div classrow idrowNote styledisplay:none;grid-template-columns:1fr;> textarea idnoteSetting classtextarea placeholderEnter slokam/mantra text...>/textarea> /div> div classgroup>Cycle/div> div classrow> div>How Many Counts for 1 Cycle or Mala (108)/div> input idcycleSize typenumber min1 step1 placeholderDefault 108 classtext-input stylewidth:140px /> /div> div classgroup>Targets (optional)/div> div classrow> div>End Counter target/div> input idendTarget typenumber min0 step1 placeholdere.g., 10 classtext-input stylewidth:180px /> /div> div classrow> div>Cycle-sets target (how many cycles)/div> input idsetsTarget typenumber min0 step1 placeholdere.g., 3 classtext-input stylewidth:140px /> /div> div classgroup>Tap +1 Size/div> div classrow range-row> div>Button height span classsize-readout idtapHeightReadout>148 px/span>/div> input idtapHeight typerange min96 max520 step4 /> /div> div classrow range-row> div>Button text size span classsize-readout idtapFontReadout>36 px/span>/div> input idtapFont typerange min22 max112 step1 /> /div> div classsubnote> The chip next to each label shows the current Count +1 size while sliding. /div> div classgroup>Utilities/div> div classrow> div>Keep Screen On Always/div> button idbtnWake classbtn-secondary press-wave stylemin-width:200px>Screen Always Off/button> /div> div classrow> div>Night Screen (AOD)/div> button idbtnNight classbtn-secondary press-wave stylemin-width:180px>Night Screen: Off/button> /div> div classrow idnightDimRow styledisplay:none;> div>Night Dim Level/div> input idnightDim typerange min0 max0.85 step0.05 value0.5 stylewidth:140px /> /div> div idnightHelper classsubnote styledisplay:none;> Tip: For Always-On Night Screen, also enable “Keep Screen On Always”. /div> div classrow> div>Fullscreen/div> button idfullscreenInSettings classbtn-secondary press-wave stylemin-width:120px>Toggle/button> /div> div classgroup>Sound/div> div classrow> div>Enable Sound (one-time)/div> button idenableSound classbtn-secondary press-wave stylemin-width:140px>Enable Sound/button> /div> div classrow> div>Test Sound/div> button idtestSound classbtn-secondary press-wave stylemin-width:120px>Play/button> /div> div classgroup>Backup/div> div classrow> div>Export JSON (backup)/div> button idexportJson classbtn-secondary press-wave stylemin-width:120px>Export/button> /div> div classrow> div>Import JSON (restore)/div> input idimportFile typefile acceptapplication/json /> /div> div classgroup>Actions/div> div classrow> div>Copy Progress/div> button idcopyProgress classbtn-secondary press-wave stylemin-width:120px>Copy/button> /div> div classrow> div>Reset Today/div> button idresetToday classbtn-secondary press-wave stylemin-width:120px>Reset Today/button> /div> div classgroup>Danger Zone/div> div classrow stylegrid-template-columns:1fr;> div classwarn> Reset All will erase EVERYTHING: counters (Total, Today, history), cycle size, all targets, display flags and values (Counter Name, Mantra, Text Note), Count +1 size, and Night/Keep Screen On settings. This cannot be undone. /div> /div> div classrow> div>Reset All (settings + counters)/div> button idresetAll classbtn-danger press-wave stylemin-width:160px>Reset All/button> /div> /div> footer> span idsettingsStatus classwarn>/span> button idcloseSettingsBottom classbtn-secondary press-wave>Close/button> /footer> /div> !-- History Sheet --> div idhistorySheet classsheet roledialog aria-modaltrue aria-labelledbyhistoryTitle> header> h3 idhistoryTitle>History/h3> button idcloseHistory classbtn-secondary press-wave>Close/button> /header> div classcontent> div classgroup>Daily totals/div> div idhistoryContainer> div classno-history idnoHistoryMsg styledisplay:none;>No history yet/div> ul idhistoryList classhistory-list>/ul> /div> /div> footer> span classwarn idhistoryStatus>/span> button idclearHistory classbtn-danger press-wave>Clear History (days)/button> /footer> /div> !-- WebAudio placeholder --> audio idgoalSound preloadauto styledisplay:none> source srcdata:audio/mpeg;base64,//uQZAAAAAAAAAAAAAAAAAAAA typeaudio/mpeg /> /audio> div idtoast classtoast rolestatus aria-livepolite>Done/div> div idnightDimmer classnight-dimmer>/div> script> // Press wave for settings/header/history controls function addPressWave(el){ el.classList.add(press-wave); el.addEventListener(pointerdown, (e)>{ const r el.getBoundingClientRect(); el.style.setProperty(--x, (e.clientX - r.left)+px); el.style.setProperty(--y, (e.clientY - r.top)+px); el.classList.add(wave); clearTimeout(el._waveT); el._waveT setTimeout(()> el.classList.remove(wave), 350); }); } function initPressWaves(){ document.querySelectorAll(#settingsSheet button, #historySheet button, header .icon-btn).forEach(addPressWave); } // Elements const els { status: document.getElementById(status), netBanner: document.getElementById(netBanner), metaOnMain: document.getElementById(metaOnMain), cnLine: document.getElementById(cnLine), cnValue: document.getElementById(cnValue), mantraLine: document.getElementById(mantraLine), mantraValue: document.getElementById(mantraValue), noteLine: document.getElementById(noteLine), noteValue: document.getElementById(noteValue), countsBlock: document.getElementById(countsBlock), totalBig: document.getElementById(totalBig), todayLine: document.getElementById(todayLine), timesCycles: document.getElementById(timesCycles), lifetimeLine: document.getElementById(lifetimeLine), cycleLabel: document.getElementById(cycleLabel), tap: document.getElementById(tap), undoBtn: document.getElementById(undoBtn), fullscreenBtn: document.getElementById(fullscreenBtn), settingsBtn: document.getElementById(settingsBtn), historyBtn: document.getElementById(historyBtn), sheetBackdrop: document.getElementById(sheetBackdrop), settingsSheet: document.getElementById(settingsSheet), historySheet: document.getElementById(historySheet), closeSettings: document.getElementById(closeSettings), closeSettingsBottom: document.getElementById(closeSettingsBottom), settingsStatus: document.getElementById(settingsStatus), closeHistory: document.getElementById(closeHistory), historyContainer: document.getElementById(historyContainer), historyList: document.getElementById(historyList), historyStatus: document.getElementById(historyStatus), noHistoryMsg: document.getElementById(noHistoryMsg), clearHistory: document.getElementById(clearHistory), btnShowCounterName: document.getElementById(btnShowCounterName), rowCounterName: document.getElementById(rowCounterName), counterName: document.getElementById(counterName), btnShowMantra: document.getElementById(btnShowMantra), rowMantra: document.getElementById(rowMantra), mantraSetting: document.getElementById(mantraSetting), btnShowNote: document.getElementById(btnShowNote), rowNote: document.getElementById(rowNote), noteSetting: document.getElementById(noteSetting), cycleSize: document.getElementById(cycleSize), endTarget: document.getElementById(endTarget), setsTarget: document.getElementById(setsTarget), tapHeight: document.getElementById(tapHeight), tapFont: document.getElementById(tapFont), tapHeightReadout: document.getElementById(tapHeightReadout), tapFontReadout: document.getElementById(tapFontReadout), btnWake: document.getElementById(btnWake), btnNight: document.getElementById(btnNight), nightDim: document.getElementById(nightDim), nightDimRow: document.getElementById(nightDimRow), nightHelper: document.getElementById(nightHelper), fullscreenInSettings: document.getElementById(fullscreenInSettings), enableSound: document.getElementById(enableSound), testSound: document.getElementById(testSound), copyProgress: document.getElementById(copyProgress), resetToday: document.getElementById(resetToday), resetAll: document.getElementById(resetAll), exportJson: document.getElementById(exportJson), importFile: document.getElementById(importFile), goalSound: document.getElementById(goalSound), toast: document.getElementById(toast), nightDimmer: document.getElementById(nightDimmer), cyclesTargetInline: document.getElementById(cyclesTargetInline), todayTargetInline: document.getElementById(todayTargetInline), lifetimeTargetInline: document.getElementById(lifetimeTargetInline), }; // Storage keys const STORAGE_VERSION v24; const storageKey chant.counter. + STORAGE_VERSION; const todayKey ()> chant.counter.today.+ new Date().toISOString().slice(0,10); const activeDaysKey chant.active.days. + STORAGE_VERSION; const shareQueueKey chant.share.queue. + STORAGE_VERSION; // WebAudio tone helpers let audioCtx null, masterGain null, audioUnlocked false; function currentVolume(){ return 0.9; } async function ensureCtx(){ try{ if(!audioCtx) audioCtx new (window.AudioContext || window.webkitAudioContext)(); if(audioCtx.state suspended){ await audioCtx.resume(); } if(!masterGain){ masterGain audioCtx.createGain(); masterGain.gain.value currentVolume(); masterGain.connect(audioCtx.destination); } return true; }catch{ return false; } } async function unlockAudioOnce(){ if(audioUnlocked) return true; const ok await ensureCtx(); if(!ok) return false; try{ const buf audioCtx.createBuffer(1, 1, audioCtx.sampleRate); const src audioCtx.createBufferSource(); src.buffer buf; src.connect(masterGain); src.start(0); audioUnlocked true; return true; }catch{ return false; } } function tone(duration0.14, freq880, typesquare, peak0.5, attack0.02){ if(!audioCtx || !masterGain) return; const now audioCtx.currentTime; const osc audioCtx.createOscillator(); const g audioCtx.createGain(); osc.type type; osc.frequency.setValueAtTime(freq, now); const amp Math.min(1, Math.max(0.02, peak*currentVolume())); g.gain.setValueAtTime(0.0001, now); g.gain.linearRampToValueAtTime(amp, now + attack); g.gain.exponentialRampToValueAtTime(0.0001, now + duration); osc.connect(g).connect(masterGain); osc.start(now); osc.stop(now + duration); } function playToneCycle(){ tone(0.10, 740, square, 0.48, 0.015); setTimeout(()>tone(0.12, 880, square, 0.54, 0.015), 95); } function playToneTarget(){ tone(0.14, 660, sine, 0.55, 0.02); setTimeout(()>tone(0.18, 990, sine, 0.6, 0.02), 140); } document.addEventListener(visibilitychange, async ()>{ if(document.visibilityState visible){ if(audioCtx && audioCtx.state ! running){ try{ await audioCtx.resume(); }catch{ try{ audioCtx.close(); }catch{} audioCtxnull; masterGainnull; audioUnlockedfalse; } } } }); // Default state const defaultState { display:{ showCounterName:false, showMantra:false, showNote:false, counterName:, mantra:, note: }, total:0, today:0, history:, cycle:108, targets:{ endTarget:0, setsTarget:0 }, ui:{ keepScreenOn:false, night:false, nightDim:0.5 }, tapSize:{ height:148, font:36 }, historyPerDay:{} }; let state loadState(); // Wake Lock let wakeLocknull; async function requestWakeLock(){ try{ if(wakeLock in navigator){ wakeLock await navigator.wakeLock.request(screen); wakeLock.addEventListener(release, ()>{ wakeLocknull; state.ui.keepScreenOnfalse; saveState(); render(); toastSettings(Screen Always Off); }); } else { toastSettings(Screen Always On not supported); } }catch{ toastSettings(Failed to toggle Screen Always On); } } async function applyWakeLock(enable){ try{ if(enable){ if(!wakeLock) await requestWakeLock(); } else{ if(wakeLock) await wakeLock.release(); wakeLocknull; } }catch{} } document.addEventListener(visibilitychange, async ()>{ if(document.visibilityStatevisible && state.ui.keepScreenOn && !wakeLock){ await requestWakeLock(); } }); // Helpers function clamp(v,min,max){ return Math.min(max, Math.max(min, v)); } function setTapSizeVars(){ document.documentElement.style.setProperty(--tap-height, state.tapSize.height + px); document.documentElement.style.setProperty(--tap-font, state.tapSize.font + px); els.tapHeightReadout.textContent state.tapSize.height + px; els.tapFontReadout.textContent state.tapSize.font + px; } function numIN(n){ const neg n 0; const s Math.floor(Math.abs(n)).toString(); if(s.length 3) return (neg?-:) + s; const last3 s.slice(-3); const other s.slice(0, -3); const grouped other.replace(/\B(?(\d{2})+(?!\d))/g, ,); return (neg?-:) + grouped + , + last3; } function toastSettings(text, ms1400){ els.toast.textContent text; els.toast.style.opacity 1; clearTimeout(toastSettings._t); toastSettings._t setTimeout(()> els.toast.style.opacity0, ms); } function vibrate(p20){ if(vibrate in navigator) navigator.vibrate(p); } function saveState(){ localStorage.setItem(storageKey, JSON.stringify(state)); localStorage.setItem(todayKey(), String(state.today)); localStorage.setItem(activeDaysKey, JSON.stringify(...getActiveDays())); } function loadState(){ try{ const base JSON.parse(localStorage.getItem(storageKey)) ?? defaultState; const todayStored Number(localStorage.getItem(todayKey())) || 0; const s { ...defaultState, ...base, today: todayStored, display: { ...defaultState.display, ...(base.display||{}) }, targets: { ...defaultState.targets, ...(base.targets||{}) }, ui: { ...defaultState.ui, ...(base.ui||{}) }, tapSize: { ...defaultState.tapSize, ...(base.tapSize||{}) }, historyPerDay: { ...(base.historyPerDay||{}) } }; s.total Number(s.total)||0; s.today Number(s.today)||0; s.cycle Math.max(1, Number(s.cycle)||108); s.targets.endTarget Math.max(0, Number(s.targets.endTarget)||0); s.targets.setsTarget Math.max(0, Number(s.targets.setsTarget)||0); s.ui.nightDim clamp(Number(s.ui.nightDim)||0.5, 0, 0.85); s.tapSize.height clamp(Number(s.tapSize.height)||148, 96, 520); s.tapSize.font clamp(Number(s.tapSize.font)||36, 22, 112); s.history Array.isArray(s.history) ? s.history : ; return s; }catch{ return { ...defaultState }; } } function getActiveDays(){ try{ return new Set(JSON.parse(localStorage.getItem(activeDaysKey)||)); }catch{ return new Set(); } } function markActiveToday(){ const d new Date().toISOString().slice(0,10); const days getActiveDays(); days.add(d); localStorage.setItem(activeDaysKey, JSON.stringify(...days)); } function renderSkeletonBrief(){ els.countsBlock.classList.add(skeleton); setTimeout(()> els.countsBlock.classList.remove(skeleton), 240); } function computeTargetCycles(){ if(state.targets.setsTarget > 0) return Math.floor(state.targets.setsTarget); if(state.targets.endTarget > 0 && state.cycle > 0) return Math.ceil(state.targets.endTarget / state.cycle); return 0; } // Format YYYY-MM-DD -> DD-MM-YYYY function formatDDMMYYYY(isoDate){ const parts isoDate.split(-); // YYYY, MM, DD if(parts.length ! 3) return isoDate; return parts2 + - + parts1 + - + parts0; } function renderHistoryList(){ const entries Object.entries(state.historyPerDay || {}); // Sort by date descending (ISO sorts lexicographically) entries.sort((a,b)> (a0 b0 ? 1 : (a0 > b0 ? -1 : 0))); // Limit to last 60 days for performance const recent entries.slice(0, 60); els.historyList.innerHTML ; if(recent.length 0){ els.noHistoryMsg.style.display block; return; } els.noHistoryMsg.style.display none; for(const isoDate, count of recent){ const li document.createElement(li); li.className history-item; const dateDiv document.createElement(div); dateDiv.className history-date; dateDiv.textContent formatDDMMYYYY(isoDate); const rightWrap document.createElement(div); rightWrap.style.display inline-flex; rightWrap.style.alignItems center; const countDiv document.createElement(div); countDiv.className history-count; countDiv.textContent String(count); const cyclesCompleted Math.floor((Number(count) || 0) / Math.max(1, Number(state.cycle) || 108)); const chip document.createElement(span); chip.className history-chip; chip.textContent Cycles: + cyclesCompleted; rightWrap.appendChild(countDiv); rightWrap.appendChild(chip); li.appendChild(dateDiv); li.appendChild(rightWrap); els.historyList.appendChild(li); } } function openSettings(){ els.sheetBackdrop.classList.add(show); els.settingsSheet.classList.add(show); els.settingsStatus.textContent ; updateButtonsFromState(); } function closeSettingsSheet(){ els.sheetBackdrop.classList.remove(show); els.settingsSheet.classList.remove(show); } function openHistory(){ els.sheetBackdrop.classList.add(show); els.historySheet.classList.add(show); els.historyStatus.textContent ; renderHistoryList(); } function closeHistorySheet(){ els.sheetBackdrop.classList.remove(show); els.historySheet.classList.remove(show); } function isFullscreen(){ return !!(document.fullscreenElement || document.webkitFullscreenElement); } async function enterFullscreen(){ const el document.documentElement; if(el.requestFullscreen) await el.requestFullscreen(); else if(el.webkitRequestFullscreen) await el.webkitRequestFullscreen(); } async function exitFullscreen(){ if(document.exitFullscreen) await document.exitFullscreen(); else if(document.webkitExitFullscreen) await document.webkitExitFullscreen(); } async function toggleFullscreen(){ try{ if(!isFullscreen()){ await enterFullscreen(); els.status.textContent; toastSettings(Entered fullscreen); } else { await exitFullscreen(); els.status.textContent; toastSettings(Exited fullscreen); } }catch{ els.status.textContent; toastSettings(Fullscreen error); } } function setTapSizeVarsAndUI(){ setTapSizeVars(); els.tapHeight.value state.tapSize.height; els.tapFont.value state.tapSize.font; } function render(){ setTapSizeVars(); renderSkeletonBrief(); // Numbers els.totalBig.textContent numIN(state.total); els.todayLine.textContent numIN(state.today); const times Math.floor(state.total / state.cycle); els.timesCycles.textContent numIN(times); els.lifetimeLine.textContent numIN(state.total); els.cycleLabel.textContent String(state.cycle); // Inline target overlays const endCountsTarget Math.max(0, Number(state.targets.endTarget)||0); const cyclesTarget computeTargetCycles(); els.todayTargetInline.textContent endCountsTarget > 0 ? numIN(endCountsTarget) : —; els.lifetimeTargetInline.textContent endCountsTarget > 0 ? numIN(endCountsTarget) : —; els.cyclesTargetInline.textContent cyclesTarget > 0 ? numIN(cyclesTarget) : —; // Meta visibility const showAny (state.display.showCounterName || state.display.showMantra || state.display.showNote); els.metaOnMain.style.display showAny ? block : none; els.cnLine.style.display state.display.showCounterName ? grid : none; els.cnValue.textContent state.display.counterName || ; els.mantraLine.style.display state.display.showMantra ? grid : none; els.mantraValue.value state.display.mantra || ; els.noteLine.style.display state.display.showNote ? grid : none; els.noteValue.value state.display.note || ; // Auto-fit mantra and note areas to show full text without scrolling autoFit(els.mantraValue); autoFit(els.noteValue); // Night mode and helpers document.body.classList.toggle(night, !!state.ui.night); document.documentElement.style.setProperty(--night-dim, state.ui.nightDim); els.nightDimRow.style.display state.ui.night ? grid : none; els.nightHelper.style.display (state.ui.night && !state.ui.keepScreenOn) ? block : none; els.status.textContent ; updateButtonsFromState(); } function updateButtonsFromState(){ els.btnShowCounterName.textContent Counter Name: + (state.display.showCounterName ? On : Off); els.rowCounterName.style.display state.display.showCounterName ? grid : none; els.btnShowMantra.textContent Mantra: + (state.display.showMantra ? On : Off); els.rowMantra.style.display state.display.showMantra ? grid : none; els.btnShowNote.textContent Text Note: + (state.display.showNote ? On : Off); els.rowNote.style.display state.display.showNote ? grid : none; els.btnWake.textContent state.ui.keepScreenOn ? Screen Always On : Screen Always Off; els.btnNight.textContent Night Screen: + (state.ui.night ? On : Off); els.cycleSize.value state.cycle || ; els.endTarget.value state.targets.endTarget || ; els.setsTarget.value state.targets.setsTarget || ; setTapSizeVarsAndUI(); els.nightDim.value String(state.ui.nightDim); els.counterName.value state.display.counterName || ; els.mantraSetting.value state.display.mantra || ; els.noteSetting.value state.display.note || ; // Bind auto-fit to settings textareas once bindAutoFitOnce(els.mantraSetting); bindAutoFitOnce(els.noteSetting); } function updateOnline(){ els.netBanner.classList.toggle(show, !navigator.onLine); } window.addEventListener(online, updateOnline); window.addEventListener(offline, updateOnline); async function checkMilestones(){ const times Math.floor(state.total / state.cycle); if (state.total % state.cycle 0){ vibrate(120,80,160); await unlockAudioOnce(); playToneCycle(); } if (state.targets.endTarget > 0 && state.total > state.targets.endTarget){ if ((state.total - 1) state.targets.endTarget){ vibrate(160,100,200); await unlockAudioOnce(); playToneTarget(); } } if (state.targets.setsTarget > 0 && times > state.targets.setsTarget){ if (Math.floor((state.total - 1)/state.cycle) state.targets.setsTarget){ vibrate(140,90,180); await unlockAudioOnce(); playToneTarget(); } } } // Auto-fit helper for textareas function autoFit(t){ if(!t) return; t.style.height auto; t.style.height t.scrollHeight + px; } function bindAutoFitOnce(t){ if(!t || t._boundAutoFit) return; t._boundAutoFit true; const h ()> autoFit(t); t.addEventListener(input, h); t.addEventListener(change, h); autoFit(t); } // Increment/Undo function increment(){ state.total + 1; state.today + 1; state.history.push(Date.now()); const iso new Date().toISOString().slice(0,10); state.historyPerDayiso (state.historyPerDayiso||0) + 1; markActiveToday(); saveState(); render(); vibrate(25); checkMilestones(); } function undo(){ if(state.history.length0) return; const lastTs state.history.pop(); state.total Math.max(0, state.total - 1); state.today Math.max(0, state.today - 1); const iso new Date().toISOString().slice(0,10); if(state.historyPerDayiso > 0) state.historyPerDayiso-1; saveState(); render(); } // Export/Import function exportJSON(){ const payload JSON.stringify(state, null, 2); const blob new Blob(payload, {type:application/json}); const url URL.createObjectURL(blob); const a document.createElement(a); a.hrefurl; a.downloadchant-counter-backup.json; a.click(); URL.revokeObjectURL(url); toastSettings(Exported JSON); } function importJSON(file){ if(!file){ toastSettings(No file selected); return; } const r new FileReader(); r.onload ()>{ try{ const obj JSON.parse(r.result); if(typeof obj ! object || obj null) throw new Error(Invalid JSON); state { ...defaultState, ...obj }; saveState(); toastSettings(Imported. Reloading...); setTimeout(()> location.reload(), 600); }catch{ toastSettings(Import failed); } }; r.readAsText(file); } // Sheets open/close helpers function openSheet(which){ els.sheetBackdrop.classList.add(show); which.classList.add(show); } function closeSheet(which){ which.classList.remove(show); if(!els.settingsSheet.classList.contains(show) && !els.historySheet.classList.contains(show)){ els.sheetBackdrop.classList.remove(show); } } // Events function initEvents(){ initPressWaves(); updateOnline(); els.tap.addEventListener(click, async ()>{ await unlockAudioOnce(); increment(); }); els.undoBtn.addEventListener(click, undo); els.fullscreenBtn.addEventListener(click, toggleFullscreen); els.settingsBtn.addEventListener(click, openSettings); els.historyBtn.addEventListener(click, openHistory); els.closeSettings.addEventListener(click, closeSettingsSheet); els.closeSettingsBottom.addEventListener(click, closeSettingsSheet); els.closeHistory.addEventListener(click, closeHistorySheet); els.sheetBackdrop.addEventListener(click, ()>{ closeSettingsSheet(); closeHistorySheet(); }); // Display toggles els.btnShowCounterName.addEventListener(click, ()>{ state.display.showCounterName !state.display.showCounterName; saveState(); render(); toastSettings(Counter Name: + (state.display.showCounterName?On:Off)); }); els.counterName.addEventListener(change, e>{ state.display.counterName e.target.value; saveState(); render(); toastSettings(Counter Name set); }); els.btnShowMantra.addEventListener(click, ()>{ state.display.showMantra !state.display.showMantra; saveState(); render(); toastSettings(Mantra: + (state.display.showMantra?On:Off)); }); els.mantraSetting.addEventListener(change, e>{ state.display.mantra e.target.value; saveState(); render(); toastSettings(Mantra updated); }); els.mantraValue.addEventListener(change, e>{ state.display.mantra e.target.value; saveState(); render(); toastSettings(Mantra updated); }); els.btnShowNote.addEventListener(click, ()>{ state.display.showNote !state.display.showNote; saveState(); render(); toastSettings(Text Note: + (state.display.showNote?On:Off)); }); els.noteSetting.addEventListener(change, e>{ state.display.note e.target.value; saveState(); render(); toastSettings(Note updated); }); els.noteValue.addEventListener(change, e>{ state.display.note e.target.value; saveState(); render(); toastSettings(Note updated); }); // Numeric inputs els.cycleSize.addEventListener(change, e>{ const v parseInt(e.target.value,10); state.cycle Math.max(1, Number.isFinite(v)?v:108); saveState(); render(); toastSettings(Cycle size set to + state.cycle); }); els.endTarget.addEventListener(change, e>{ const v parseInt(e.target.value,10); state.targets.endTarget Math.max(0, Number.isFinite(v)?v:0); saveState(); render(); toastSettings(End Counter target set to + state.targets.endTarget); }); els.setsTarget.addEventListener(change, e>{ const v parseInt(e.target.value,10); state.targets.setsTarget Math.max(0, Number.isFinite(v)?v:0); saveState(); render(); toastSettings(Cycle-sets target set to + state.targets.setsTarget); }); // Ranges els.tapHeight.addEventListener(input, e>{ state.tapSize.height clamp(parseInt(e.target.value,10)||148, 96, 520); saveState(); setTapSizeVars(); }); els.tapFont.addEventListener(input, e>{ state.tapSize.font clamp(parseInt(e.target.value,10)||36, 22, 112); saveState(); setTapSizeVars(); }); els.nightDim.addEventListener(input, e>{ state.ui.nightDim clamp(parseFloat(e.target.value), 0, 0.85); saveState(); render(); toastSettings(Night dim: + state.ui.nightDim); }); // Utilities els.btnWake.addEventListener(click, async ()>{ state.ui.keepScreenOn !state.ui.keepScreenOn; saveState(); render(); await applyWakeLock(state.ui.keepScreenOn); toastSettings(state.ui.keepScreenOn ? Screen Always On : Screen Always Off); }); els.btnNight.addEventListener(click, ()>{ state.ui.night !state.ui.night; saveState(); render(); if(state.ui.night && !state.ui.keepScreenOn){ els.settingsStatus.textContent Night Screen is ON. For AOD, also enable Keep Screen On Always.; setTimeout(()> els.settingsStatus.textContent, 2200); } toastSettings(Night Screen: + (state.ui.night?On:Off)); }); els.fullscreenInSettings.addEventListener(click, ()>{ toggleFullscreen(); }); // Sound els.enableSound.addEventListener(click, async ()>{ const ok await unlockAudioOnce(); toastSettings(ok ? Sound enabled : Unable to enable sound); }); els.testSound.addEventListener(click, async ()>{ const ok await unlockAudioOnce(); if(ok){ playToneTarget(); toastSettings(Sound test played); } else{ toastSettings(Sound test failed); } }); // Copy and backup els.copyProgress.addEventListener(click, async ()>{ const text buildShareText(); try{ await navigator.clipboard.writeText(text); toastSettings(Copied); }catch{ toastSettings(Copy failed); } }); els.exportJson.addEventListener(click, exportJSON); els.importFile.addEventListener(change, e> importJSON(e.target.files?.0)); // History: clear per-day history (does not touch total/timestamps; only daily summary) els.clearHistory.addEventListener(click, ()>{ if(confirm(Clear daily history (per-day totals)? This will not change your counters.)){ state.historyPerDay {}; saveState(); renderHistoryList(); toastSettings(History cleared); } }); // Reset Today els.resetToday.addEventListener(click, ()>{ if(confirm(Reset today count?)){ const iso new Date().toISOString().slice(0,10); state.today 0; state.historyPerDayiso 0; els.totalBig.textContent 0; saveState(); render(); setTimeout(()>{ els.totalBig.textContent 0; }, 0); vibrate(10); toastSettings(Today reset); } }); // Reset All els.resetAll.addEventListener(click, ()>{ const details - Counters: Total, Today, history\n + - Cycle size, Targets\n + - Display items & values\n + - Count button size, Night screen, Wake lock\n + - Insights data\n\nThis cannot be undone. Continue?; if(confirm(Reset All will erase EVERYTHING:\n\n + details)){ try{ localStorage.removeItem(storageKey); }catch{} try{ localStorage.removeItem(todayKey()); }catch{} try{ localStorage.removeItem(activeDaysKey); }catch{} try{ localStorage.removeItem(shareQueueKey); }catch{} state JSON.parse(JSON.stringify(defaultState)); saveState(); render(); vibrate(40); els.settingsStatus.textContentAll settings and counters reset.; setTimeout(()> els.settingsStatus.textContent, 2000); toastSettings(All reset); } }); if(serviceWorker in navigator){ navigator.serviceWorker.register(sw.js).catch(()>{}); } // Bind auto-fit to mantra and note on main bindAutoFitOnce(els.mantraValue); bindAutoFitOnce(els.noteValue); } // Share text builder function buildShareText(){ const times Math.floor(state.total / state.cycle); const lines; if(state.display.showCounterName && state.display.counterName) lines.push(state.display.counterName); if(state.display.showMantra && state.display.mantra) lines.push(Mantra: + (state.display.mantra.length>200?state.display.mantra.slice(0,197)+...:state.display.mantra)); if(state.display.showNote && state.display.note) lines.push(Note: + (state.display.note.length>200?state.display.note.slice(0,197)+...:state.display.note)); lines.push(Cycle size: + state.cycle); lines.push(Today: + numIN(state.today)); lines.push(No. of cycles completed: + numIN(times)); lines.push(Lifetime: + numIN(state.total)); if(state.targets.endTarget) lines.push(End target: + numIN(state.targets.endTarget)); if(state.targets.setsTarget) lines.push(Cycle target: + numIN(state.targets.setsTarget)); return lines.join(\n); } // Init initEvents(); render(); if(state.ui.keepScreenOn){ applyWakeLock(true); } /script>/body>/html>
View on OTX
|
View on ThreatMiner
Please enable JavaScript to view the
comments powered by Disqus.
Data with thanks to
AlienVault OTX
,
VirusTotal
,
Malwr
and
others
. [
Sitemap
]