Skip to content

Commit 211ad5e

Browse files
Add word counter tool (#41)
- add a word counter tool with live paragraph, line, word, and character counts - hide paragraph and line counts until at least two are present - document the new tool ------ [Codex Task](https://chatgpt.com/codex/tasks/task_e_6923a3ac12d48325bc35e928e9e7e4cf)
1 parent 6a2c59d commit 211ad5e

2 files changed

Lines changed: 163 additions & 0 deletions

File tree

word-counter.docs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Count paragraphs, lines, words, and characters in any pasted text. Paragraph and line totals only appear when there are at least two of each, making it easy to scan longer passages.

word-counter.html

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="UTF-8">
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
7+
<title>Word Counter</title>
8+
<link rel="stylesheet" href="styles.css">
9+
<style>
10+
body {
11+
max-width: 960px;
12+
margin: 0 auto;
13+
padding: clamp(1.5rem, 3vw, 2.5rem) clamp(1.25rem, 3vw, 2.75rem) clamp(3rem, 4vw, 4rem);
14+
}
15+
16+
header {
17+
margin-bottom: clamp(1.25rem, 2.5vw, 2rem);
18+
}
19+
20+
main {
21+
display: grid;
22+
gap: 1.5rem;
23+
}
24+
25+
.tool-card {
26+
padding: clamp(1.25rem, 3vw, 2rem);
27+
}
28+
29+
.counts-row {
30+
display: flex;
31+
flex-wrap: wrap;
32+
gap: 0.75rem;
33+
margin-bottom: 1rem;
34+
align-items: stretch;
35+
}
36+
37+
.count-tile {
38+
flex: 1 1 160px;
39+
background: var(--bg-2);
40+
border: 1px solid var(--ui-2);
41+
border-radius: 10px;
42+
padding: 0.85rem 1rem;
43+
display: grid;
44+
gap: 0.25rem;
45+
}
46+
47+
.count-label {
48+
color: var(--tx-3);
49+
letter-spacing: 0.1px;
50+
}
51+
52+
.count-number {
53+
font-size: clamp(1.4rem, 3vw, 1.9rem);
54+
font-variant-numeric: tabular-nums;
55+
}
56+
57+
textarea {
58+
width: 100%;
59+
min-height: 220px;
60+
}
61+
62+
.hidden {
63+
display: none;
64+
}
65+
66+
@media (max-width: 640px) {
67+
body {
68+
padding: 1.25rem 1rem 2.5rem;
69+
}
70+
}
71+
</style>
72+
</head>
73+
74+
<body>
75+
<header>
76+
<h1>Word Counter</h1>
77+
<p class="lead">Paste any text to see live counts for paragraphs, lines, words, and characters.</p>
78+
</header>
79+
80+
<main>
81+
<section class="surface tool-card">
82+
<div class="counts-row" id="counts-row" aria-live="polite">
83+
<div class="count-tile" id="paragraphs-tile">
84+
<div class="count-number" id="paragraph-count">0</div>
85+
<div class="count-label">paragraphs</div>
86+
</div>
87+
<div class="count-tile" id="lines-tile">
88+
<div class="count-number" id="line-count">0</div>
89+
<div class="count-label">lines</div>
90+
</div>
91+
<div class="count-tile">
92+
<div class="count-number" id="word-count">0</div>
93+
<div class="count-label">words</div>
94+
</div>
95+
<div class="count-tile">
96+
<div class="count-number" id="character-count">0</div>
97+
<div class="count-label">characters</div>
98+
</div>
99+
</div>
100+
101+
<div class="form-group">
102+
<label for="text-input">Text</label>
103+
<textarea id="text-input" name="text-input" placeholder="Paste or type any text" spellcheck="false"></textarea>
104+
</div>
105+
</section>
106+
</main>
107+
108+
<script>
109+
(function () {
110+
const textInput = document.getElementById('text-input');
111+
const paragraphTile = document.getElementById('paragraphs-tile');
112+
const lineTile = document.getElementById('lines-tile');
113+
const paragraphCount = document.getElementById('paragraph-count');
114+
const lineCount = document.getElementById('line-count');
115+
const wordCount = document.getElementById('word-count');
116+
const characterCount = document.getElementById('character-count');
117+
118+
function countParagraphs(text) {
119+
if (!text.trim()) {
120+
return 0;
121+
}
122+
const paragraphs = text.split(/(?:\r?\n){2,}/).filter(block => block.trim().length > 0);
123+
return paragraphs.length;
124+
}
125+
126+
function countLines(text) {
127+
if (text === '') {
128+
return 0;
129+
}
130+
return text.split(/\r?\n/).length;
131+
}
132+
133+
function countWords(text) {
134+
if (!text.trim()) {
135+
return 0;
136+
}
137+
return text.trim().split(/\s+/).length;
138+
}
139+
140+
function updateCounts() {
141+
const text = textInput.value;
142+
const paragraphs = countParagraphs(text);
143+
const lines = countLines(text);
144+
const words = countWords(text);
145+
const characters = text.length;
146+
147+
paragraphCount.textContent = paragraphs.toLocaleString();
148+
lineCount.textContent = lines.toLocaleString();
149+
wordCount.textContent = words.toLocaleString();
150+
characterCount.textContent = characters.toLocaleString();
151+
152+
paragraphTile.classList.toggle('hidden', paragraphs < 2);
153+
lineTile.classList.toggle('hidden', lines < 2);
154+
}
155+
156+
textInput.addEventListener('input', updateCounts);
157+
updateCounts();
158+
})();
159+
</script>
160+
</body>
161+
162+
</html>

0 commit comments

Comments
 (0)