You've built a page. It sits there, quiet. Today we'll teach it to do things — count, remember, react — with a language called JavaScript. And we'll do it in ten lines, with an example anyone from any tradition understands: a counter you can press, that remembers. The demo above is a tasbih — thirty-three, sixty-six, ninety-nine. Same pattern, same shape as anything else you'd build.

If HTML is the nouns of a web page and CSS is the adjectives, JavaScript is the verbs. Press, open, close, save, show, hide, count — these are all verbs. Learn four commands, and you can make most small apps yourself.

The one tag you need

Add this anywhere inside your <body>, usually near the bottom:

<script>
  console.log('The pen has been taught. Now the hand.')
</script>

Save, refresh, open the browser's DevTools with F12 (or Cmd+Option+I on macOS), and click the Console tab. You'll see the message. You just ran code — no compiler, no server; the browser read your words and executed them.

Example 1: a tasbih counter — ten lines, real interactivity

<button id="bead" type="button">SubhanAllah</button>
<div id="count">0</div>

<script>
  let n = 0
  const bead = document.querySelector('#bead')
  const countEl = document.querySelector('#count')
  bead.addEventListener('click', () => {
    n += 1
    countEl.textContent = n
  })
</script>

Eight lines of JavaScript. A complete, working interactive app. Let's read them together:

  • let n = 0 — a memory cell that starts at zero.
  • document.querySelector('#bead') — find the button whose id is bead. Same selector grammar as CSS.
  • addEventListener('click', …) — "when the user clicks, run this function."
  • n += 1 — increment.
  • countEl.textContent = n — replace the displayed number.

That's the whole vocabulary of interactive web pages: find an element, listen for an event, change something. You now know the recipe every framework is built on top of.

Example 2: mark every thirty-third press

The demo at the top of this post adds one small extension: every thirty-third click gets a visual pulse, and a ring of text says which tier of the tasbih we're in. Same shape, just a conditional:

if (n % 33 === 0) {
  countEl.classList.add('pulse')
  ringEl.textContent =
    n === 33 ? 'SubhanAllah × 33'
    : n === 66 ? 'Alhamdulillah × 33'
    : n === 99 ? 'Allahu Akbar × 33'
    : ''
  setTimeout(() => countEl.classList.remove('pulse'), 500)
}

Three ideas worth carrying:

  • The modulo operator % returns the remainder. n % 33 === 0 is true exactly at 33, 66, 99 — a cheap way to mark regular milestones in any counter.
  • classList.add / remove is JavaScript's best role. Flip a class; let CSS handle the visuals. The .pulse class in the demo is just color: #5d00ff; transition: color 0.25s; — no animation logic in the JS.
  • setTimeout(fn, ms) runs something later. It's the most common async function on the web. No need to learn async theory before you use it.

Example 3: remember across page reloads

The browser ships with a tiny key-value database called localStorage. It is the easiest backend you will ever meet — no server, no database file, no password. Persist your tasbih count so it survives a tab close:

let n = Number(localStorage.getItem('tasbih') || 0)
countEl.textContent = n

bead.addEventListener('click', () => {
  n += 1
  countEl.textContent = n
  localStorage.setItem('tasbih', n)
})

Click, close the tab, reopen — your count is still there. Ten lines of JavaScript just built an auto-saving counter with no backend at all.

Tricks worth carrying

  • querySelector understands any CSS selector. document.querySelector('.card:nth-child(2) button') works. You already know the selector language from CSS.
  • Prefer textContent over innerHTML. innerHTML renders HTML — fine for trusted content, risky for user input. textContent always treats the value as plain text. Safety first, cleverness second.
  • addEventListener is the only event API you need. Forget onclick="…" strings in HTML. One function, one place, cleaner code.
  • localStorage holds about 5 MB per site. Counters, todo lists, settings — none of them need a backend.
  • Arrow functions (() => {}) are shorter function declarations. They're everywhere in modern code — not magical, just concise.

What frameworks add — and what they don't replace

React, Vue, Svelte — these speed up two specific things: rendering lists that change a lot and reusing UI across many pages. If your project has neither problem, you don't need a framework. A ten-line <script> tag is perfectly serious engineering.

And the language itself — document.querySelector, addEventListener, arrow functions, localStorage — stays exactly the same underneath every framework. Learn this once, carry it forever.

Takeaways

  1. JavaScript runs inside any HTML file with a <script> tag — no setup needed.
  2. Four things cover most interactivity: find, listen, change, remember.
  3. The browser is your IDE. DevTools let you read, tweak, and debug live.
  4. Small apps deserve small code. Ten lines is enough for real value.

Next post: "How a URL actually works — the 0.3 second journey" — the six small protocols holding hands behind every page load.

Build small, build honest, and keep your code light.