Skip to content

Repository files navigation

DIM 9 Listening Exercises — Next.js

A mobile-first Next.js app for practicing English listening with interactive audio.

Features

  • 🎧 Polished audio player — big play button, ±10s skip, and playback speed (0.75×, 1×, 1.25×, 1.5×) for language learners
  • 📱 Mobile-first UX — full-screen list on phones, sticky sidebar on desktop
  • 🔍 Instant search — filter by exercise number or transcript text
  • ⌨️ Keyboard-friendlyEsc returns from detail to list on mobile, visible focus rings throughout
  • Accessible — proper ARIA labels, semantic HTML, tap targets ≥ 44×44px
  • 🎨 Consistent design language — Inter font, indigo accent, subtle shadows

Setup

1. Create a Next.js app (if you don't already have one)

npx create-next-app@latest dim9-listening \
  --typescript --tailwind --app \
  --src-dir=false --import-alias="@/*" \
  --no-eslint
cd dim9-listening

2. Drop these files in

Copy the files from this bundle into the matching paths:

app/
  layout.tsx        # replaces the default
  page.tsx          # replaces the default
  globals.css       # replaces the default
components/
  AudioPlayer.tsx   # new
lib/
  data.ts           # new — where you put your exercises
tailwind.config.ts  # replaces the default
postcss.config.js   # replaces the default (unchanged from default, included for completeness)

3. Run it

npm run dev

Open http://localhost:3000.


Adding exercises

Edit lib/data.ts:

export const exercises: Exercise[] = [
  {
    id: 1,
    text: `Transcript for exercise 1.

Paragraphs are separated by a blank line — they'll render as
separate <p> elements with proper spacing.`,
  },
  { id: 2, text: 'Short transcript for exercise 2…' },
  // …
];

The audio URL is generated from the id:

export function getAudioUrl(id: number): string {
  return `https://abiturient.az/storage/mp3/${id}_eng_TT_9-illik_2025.mp3`;
}

If you host the MP3s elsewhere, change this function — that's the only place the URL pattern lives.


Architecture

app/
├─ layout.tsx      # root layout, loads Inter + JetBrains Mono via next/font
├─ page.tsx        # main client component — list + detail in one view
└─ globals.css     # Tailwind directives + minor base tweaks

components/
└─ AudioPlayer.tsx # self-contained audio player (play/pause, seek, skip, speed)

lib/
└─ data.ts         # Exercise type, exercises array, getAudioUrl()

State management is plain useState — no context, no external library. The page keeps one piece of state (selectedId) and derives everything else via useMemo. The audio player is self-contained: pass it an src, it handles its own playback state.

Responsive behavior: below md (768 px), the list and the detail view are mutually exclusive — selecting an exercise swaps the view. From md up, both are visible side-by-side with a sticky sidebar.


Extending

URL-based routing (shareable exercise links)

Swap useState for useSearchParams + router.push to reflect selectedId in the URL (?ex=5). Keeps things a single page but makes exercises linkable.

Adding word-level sync

If the source data ever includes word timings (e.g. JSON with [{ word, startMs, endMs }]), you can highlight words in the transcript as they're spoken by listening to the audio's timeupdate event and matching against the timings array.

Offline caching

Add a service worker (via next-pwa or a custom one) to cache the audio files after first play — makes the app usable without signal on the metro.

Dark mode

Tailwind's dark: variants work out of the box if you add darkMode: 'class' to tailwind.config.ts and a theme toggle. The indigo accent already plays well in dark mode; just swap bg-slate-50dark:bg-slate-900 and bg-whitedark:bg-slate-800 in the main surfaces.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages