Skip to content

Finishing Touches and Course Recap

The app logic is complete. This lesson adds the CSS, walks through a complete feature test, and reviews the TypeScript concepts that appear in every file of the finished codebase.

*, *::before, *::after { box-sizing: border-box; }
body {
font-family: system-ui, sans-serif;
background: #f0f2f9;
color: #1a1a1a;
margin: 0;
padding: 0;
}
header {
background: #2d3a8c;
color: white;
padding: 1rem 2rem;
}
header h1 { margin: 0; font-size: 1.5rem; letter-spacing: .03em; }
main { max-width: 640px; margin: 2rem auto; padding: 0 1rem; }
.card {
background: white;
border-radius: 10px;
padding: 2rem;
box-shadow: 0 2px 8px rgba(0,0,0,.08);
}
.screen h2 { margin: 0 0 1.5rem; font-size: 1.25rem; }
.field { display: flex; flex-direction: column; gap: .25rem; margin-bottom: 1rem; }
.field label { font-size: .85rem; font-weight: 600; color: #555; }
.field select { padding: .5rem .75rem; border: 1px solid #ccc; border-radius: 6px; font-size: 1rem; }
.field select:focus { outline: 2px solid #2d3a8c; outline-offset: 1px; }
.btn-primary {
display: block; width: 100%; padding: .7rem 1.5rem;
background: #2d3a8c; color: white; border: none;
border-radius: 6px; font-size: 1rem; font-weight: 600; cursor: pointer; margin-top: 1rem;
}
.btn-primary:hover { background: #3d4fba; }
.loading { text-align: center; color: #666; font-style: italic; padding: 2rem 0; }
.question-header {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 1.25rem; flex-wrap: wrap; gap: .5rem;
}
.progress-text { font-size: .85rem; color: #666; }
.question-text { font-size: 1.1rem; line-height: 1.5; margin: 0 0 1.5rem; }
.answers-grid { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; margin-bottom: 1rem; }
.answer-btn {
padding: .75rem 1rem; background: #f0f2f9; border: 2px solid #dde0f0;
border-radius: 8px; font-size: .95rem; cursor: pointer; text-align: left;
line-height: 1.4; transition: background .15s, border-color .15s;
}
.answer-btn:hover:not(:disabled) { background: #e4e8f8; border-color: #2d3a8c; }
.answer-btn:disabled { cursor: default; }
.answer-btn.correct { background: #e6f4ea; border-color: #2d7a3a; color: #1a4d24; font-weight: 600; }
.answer-btn.incorrect { background: #fce8e8; border-color: #a00; color: #7a0000; }
.badge { display: inline-block; font-size: .75rem; padding: .2rem .6rem; background: #e8eaf6; color: #2d3a8c; border-radius: 999px; font-weight: 600; }
.badge--highlight { background: #fff3cd; color: #7a5000; }
.final-score { font-size: 1.4rem; font-weight: 700; color: #2d3a8c; margin: .5rem 0; }
.high-score-display { font-size: .9rem; color: #666; margin: .75rem 0 0; }
@media (max-width: 480px) {
.answers-grid { grid-template-columns: 1fr; }
}

Work through every feature before calling the app complete:

Start screen:

  • Page loads and shows the start screen — no console errors
  • A previous high score appears if one is stored in localStorage
  • Difficulty and question count selectors are present

Loading questions:

  • Click “Start Quiz” — the loading screen appears briefly while the API request is in flight
  • If the API fails (disconnect your network) — an alert appears and the start screen is restored
  • The question screen appears once questions load

Answering questions:

  • The question text, category badge, and progress counter display correctly
  • Four answer buttons appear, shuffled on each question
  • Clicking a correct answer turns it green; the wrong answer (if clicked) turns red
  • All buttons are disabled after answering — no double-clicks
  • “Next” button appears after an answer is selected

Navigation:

  • “Next” advances to the next question
  • On the last question, “Next” becomes “See Results”
  • “See Results” shows the result screen

Results:

  • Final score displays correctly
  • “New High Score!” badge appears on a new best
  • Best score is shown on the result screen
  • “Play Again” returns to the start screen with the updated high score

Persistence:

  • Complete a quiz — refresh the page — the high score is still shown
  • Open DevTools → Application → Local Storage — confirm aceit_highscore is present

If every item passes, AceIt is complete.

AceIt uses <script type="module">, which requires a real server — you can’t open index.html directly as a file:// URL. GitHub Pages serves the files over HTTPS, so it works perfectly.

1. Make sure dist/ is committed

The compiled JavaScript must be in the repository. Open .gitignore and confirm dist/ is not listed. If it is, remove that line.

2. Initialize the repository and push to GitHub

Terminal window
git init
git branch -m main
git add .
git commit -m "initial commit"
git remote add origin https://github.com/<your-username>/AceIt.git
git push -u origin main

Create the repository on GitHub first (no README, no .gitignore) so the push succeeds on the first try.

3. Enable GitHub Pages

  1. Open the repository on GitHub
  2. Go to Settings → Pages
  3. Under Branch, select main and leave the folder as / (root)
  4. Click Save

GitHub will show a banner with your live URL — typically https://<your-username>.github.io/AceIt/. The first deployment takes a minute or two.

4. Visit the live app

Open the URL. The start screen should load and the quiz should work exactly as it did locally. If you see a blank page, open DevTools → Console — a MIME type error on dist/main.js means that file wasn’t committed; check step 1.

AceIt is a real app. A user can open it, play a quiz against live API data, and return later to beat their high score. Every pattern you used is the same pattern you would use in a production TypeScript codebase.

You can play the finished app or browse the source on GitHub.

FileConcepts demonstrated
types.tsLiteral unions, interfaces, readonly, optional properties, discriminated union, enum
api.tsGeneric function fetchJson<T>, generic shuffle<T>, typed parameters and returns, Promise<T>, async/await, optional parameters
quiz.tsClass with private fields, readonly, typed getters, access modifiers, static methods, type predicate isHighScore, unknown from JSON.parse
ui.tsScreenId literal union, querySelectorAll<HTMLElement>, DOM type assertions, HighScore | null narrowing
main.tsQuizEngine | null state, instanceof Error catch narrowing, non-null assertion !, event delegation with closest<HTMLButtonElement>, contextual typing
aceit/
index.html ← HTML structure — four screens, unchanged throughout
favicon.svg ← browser tab icon
style.css ← layout and component styles
tsconfig.json ← TypeScript compiler configuration
types.ts ← all interfaces, type aliases, enums
api.ts ← fetchJson<T>, fetchQuestions, normalization
quiz.ts ← QuizEngine class, localStorage, type predicate
ui.ts ← DOM rendering functions
main.ts ← entry point: state, events, render flow
dist/ ← compiled JavaScript (committed so GitHub Pages can serve it)

This structure maps directly to how Angular, React, and Vue applications are organized: types, services (api, storage), domain logic (engine), UI components, and a root entry point.

The JavaScript Development Track continues with two framework courses that build on everything you did here.

Angular Foundations is the next stop. Angular uses TypeScript throughout and its patterns map directly onto what you built:

  • QuizEngine → an Angular service with @Injectable
  • ui.ts rendering functions → Angular components with typed templates
  • types.ts interfaces → shared models imported across the application
  • fetchJson<T> → Angular’s HttpClient with typed generics and RxJS

Start Angular Foundations →

React Foundations follows Angular. React is the most widely used frontend library in the industry — function components, hooks, and the Context API applied to a real deployed app. The TypeScript comfort you have built here makes React with TypeScript straightforward when you get there.

Start React Foundations →