{
  "schemaVersion": 1,
  "slug": "word-counter-calculator",
  "category": "productivity",
  "kind": "implementation",
  "independentValidation": false,
  "implementationHash": "af6e7027edc16cacf8d1ee1b353d42682c60171aa3b94403463830a964227715",
  "provenance": "Generated from the calculation contract used by the published page. Publication is not external validation.",
  "sourceFiles": [
    "data/calculators/word-counter-calculator.json",
    "src/engine.js"
  ],
  "runtimeSource": "/sources/engine.txt",
  "runtimeHash": "5c888d8898afbdee36267bb9977829e1e2ac568f6677c8ee87469c139864645c",
  "scope": "The compute function records implemented equations, constants, lookup tables and custom rules. Inputs record defaults and allowed values; outputs record precision and units. Defaults are examples, not recommendations. General background references are not evidence for this specific algorithm.",
  "publishedFormula": {
    "formula": "reading minutes = words ÷ words/min; speaking minutes = words ÷ words/min",
    "vars": [
      [
        "N_w",
        "Word count"
      ],
      [
        "N_s",
        "Sentence count"
      ],
      [
        "S",
        "Pasted text; S_w = its whitespace-separated tokens, c_i = its i-th character"
      ],
      [
        "w_p",
        "Words per page"
      ],
      [
        "v_read",
        "Reading speed (wpm)"
      ],
      [
        "v_speak",
        "Speaking speed (wpm)"
      ]
    ],
    "note": "Pasted text uses whitespace tokens, except supported CJK scripts use Unicode word boundaries; characters count grapheme clusters. With no pasted text, totals use rough English-based estimates (5.1 characters/word, 18 words/sentence and 120 words/paragraph). Reading or speaking minutes = word count ÷ selected words/minute rate; zero words gives no time estimate."
  },
  "externalEvidence": null,
  "contract": {
    "compute": "const raw=String(text||'');const trimmed=raw.trim();const script=/[\\u3040-\\u30ff]/.test(trimmed)?'ja':/[\\uac00-\\ud7af]/.test(trimmed)?'ko':/[\\u0e00-\\u0e7f]/.test(trimmed)?'th':/[\\u3400-\\u9fff]/.test(trimmed)?'zh':null;const wordCount=s=>{if(script&&typeof Intl.Segmenter==='function')return Array.from(new Intl.Segmenter(script,{granularity:'word'}).segment(s)).filter(x=>x.isWordLike).length;return s.split(/\\s+/).filter(Boolean).length};const countChars=s=>typeof Intl.Segmenter==='function'?Array.from(new Intl.Segmenter('und',{granularity:'grapheme'}).segment(s)).length:Array.from(s).length;const hasText=raw.length>0;const actualWords=hasText?wordCount(trimmed):words;const characters=hasText?countChars(raw):Math.round(actualWords*5.1);const charactersNoSpaces=hasText?countChars(raw.replace(/\\s/g,'')):Math.round(actualWords*4.1);const sentences=hasText?(trimmed?Math.max(1,(trimmed.match(/[.!?。！？]+/g)||[]).length):0):(actualWords>0?Math.max(1,Math.round(actualWords/18)):0);const paragraphs=hasText?(trimmed?Math.max(1,trimmed.split(/\\n\\s*\\n/).filter(Boolean).length):0):(actualWords>0?Math.max(1,Math.round(actualWords/120)):0);return{wordCount:actualWords,characters,charactersNoSpaces,sentences,paragraphs,pages:actualWords/wordsPerPage,readingMinutes:actualWords>0?actualWords/readingSpeed:null,speakingMinutes:actualWords>0?actualWords/speakingSpeed:null};",
    "inputs": [
      {
        "name": "text",
        "type": "text",
        "default": "",
        "step": 1
      },
      {
        "name": "words",
        "type": "number",
        "default": 800,
        "min": 0,
        "max": 500000,
        "step": 10
      },
      {
        "name": "wordsPerPage",
        "type": "number",
        "default": 250,
        "min": 50,
        "max": 1000,
        "step": 10,
        "slider": true
      },
      {
        "name": "readingSpeed",
        "type": "number",
        "default": 220,
        "min": 50,
        "max": 1000,
        "step": 10,
        "slider": true
      },
      {
        "name": "speakingSpeed",
        "type": "number",
        "default": 140,
        "min": 50,
        "max": 300,
        "step": 5,
        "slider": true
      }
    ],
    "outputs": [
      {
        "name": "wordCount",
        "precision": 0,
        "unit": "words"
      },
      {
        "name": "characters",
        "precision": 0,
        "unit": "chars"
      },
      {
        "name": "charactersNoSpaces",
        "precision": 0,
        "unit": "chars"
      },
      {
        "name": "sentences",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "paragraphs",
        "precision": 0,
        "unit": ""
      },
      {
        "name": "pages",
        "precision": 1,
        "unit": "pages"
      },
      {
        "name": "readingMinutes",
        "precision": 2,
        "unit": "min",
        "optional": true
      },
      {
        "name": "speakingMinutes",
        "precision": 2,
        "unit": "min",
        "optional": true
      }
    ],
    "interpret": null
  }
}
