Software EngineeringAlliance Girls High School
TERM 3 / ACTIVITY

LESSONS 3 + 4 · SAMPLE PROJECT

Opportunities Club · Component Tour

LESSON ACTIVITY · OPPORTUNITIES CLUB

One page.
Many small parts.

Take the Opportunities Club website apart, one component at a time, and see how HTML, CSS and JavaScript each do one job.

Open the finished page in a browser. Keep the explainer open for the details.

Lesson 3: layout + accessibilityLesson 4: JavaScript
01

THE FOLDER · 3 MIN

Three files.
Three jobs.

  • index.htmlStructure and content: landmarks, the form and eight cards.
  • style.cssLook and layout: colours, Flexbox, Grid and media queries.
  • app.jsBehaviour: show or hide cards when a topic is chosen.

lesson1.html and lesson1.css are the starting draft from Lessons 1–2. Compare them with index.html to see how far the page grows.

02

PAGE ANATOMY · 5 MIN

Every part
has a landmark.

<a class="skip-link" href="#main">Skip to main content</a>
<header class="site-header"> logo + <nav> </header>
<main id="main">
  <div class="hero"> … </div>
  <section id="finder"> form + status + cards </section>
  <div class="two-col"> #about + #join </div>
</main>
<footer class="site-footer"> … </footer>
<script src="app.js" defer></script>

One header, one main, one footer. defer runs app.js after the HTML has loaded.

03

COMPONENT 1 · SKIP LINK

Hidden until
you need it.

.skip-link { position: absolute; top: -4rem; }
.skip-link:focus { top: 1rem; }
  • WhoKeyboard and screen-reader users.
  • WhatJumps past the menu straight to #main.
  • TestReload, press Tab once, then Enter.
04

COMPONENT 2 · HEADER + NAV · FLEXBOX

Stacked on phones.
Side by side on laptops.

.header-inner { display: flex; flex-direction: column; align-items: center; }
.site-nav ul { display: flex; flex-wrap: wrap; gap: 0.25rem 1.25rem; }

@media (min-width: 40rem) {
  .header-inner { flex-direction: row; justify-content: space-between; }
}

Mobile-first: the small-screen rule comes first; the media query adds the wide layout. aria-label="Main" names the nav for screen readers.

05

COMPONENT 3 · HERO

Text that grows
with the screen.

.hero h1 {
  max-width: 18ch;                          /* short, readable lines */
  font-size: clamp(2rem, 1.2rem + 4vw, 3.5rem); /* min, ideal, max */
}

clamp() gives fluid type without a media query. ch measures text width in characters.

06

COMPONENT 4 · FINDER FORM

Every control
gets a label.

<form id="filter-form" class="filter-form">
  <div class="field">
    <label for="topic">Choose a topic</label>
    <select id="topic" name="topic">
      <option value="all">All topics</option>
      <option value="study">Study</option> …
    </select>
  </div>
  <button class="button" type="submit">Show results</button>
</form>

for matches id. CSS: .filter-form uses flex-wrap and .field uses flex: 1 1 14rem, so the button drops below on narrow screens.

07

COMPONENT 5 · STATUS MESSAGE

Say what
just happened.

<p id="results-message" class="status" role="status"></p>

.status { min-height: 1.6em; } /* no jump when text changes */
  • Empty in HTMLapp.js fills it in.
  • role="status"Screen readers announce the new text politely.
  • Example“Showing 2 of 8 opportunities.”
08

COMPONENT 6 · CARD GRID · CSS GRID

As many columns
as will fit.

.card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 20rem), 1fr));
  gap: 1rem;
}

Each column is at least 20rem and shares leftover space. min(100%, 20rem) stops overflow on tiny phones. No media query needed.

DevTools: select .card-grid, open the Grid overlay, then resize from 320px to full width.

09

COMPONENT 7 · CARD · THE REUSABLE PART

Copy one.
Get a new opportunity.

<article class="card" data-category="tech">
  <img src="images/coding-club.svg" alt="Two students beside a laptop showing lines of code" width="400" height="240">
  <div class="card-body">
    <div class="card-meta"><span class="chip">Tech</span></div>
    <h3>Coding club</h3>
    <p>Build small programs and websites with a friend.</p>
    <p class="card-when">Thursdays, after classes</p>
  </div>
</article>

data-category picks the colour in CSS and the filter in JavaScript. .card-when uses margin-top: auto, so every card’s last line sits at the bottom.

10

COMPONENTS 8 + 9 · ABOUT, JOIN, FOOTER

One column.
Then two.

.two-col { display: grid; gap: 2rem; }
@media (min-width: 600px) {
  .two-col { grid-template-columns: 1fr 1fr; }
}
  • AboutA section named by its heading with aria-labelledby.
  • JoinAn ordered list, because the steps happen in order.
  • FooterCloses the page with a thin top border.
11

BEHIND EVERY COMPONENT · STYLE.CSS

Set it once.
Use it everywhere.

:root { --focus: navy; --tech: forestgreen; --tech-tint: lightgreen; }
*, *::before, *::after { box-sizing: border-box; }
img { max-width: 100%; height: auto; display: block; }
[hidden] { display: none !important; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

Custom properties are CSS variables. [hidden] must win so JavaScript can hide cards that use display: flex.

12

APP.JS · STEPS 1–2 · VARIABLES + A FUNCTION

Find it.
Then ask a question.

const form = document.querySelector("#filter-form");
const topicSelect = document.querySelector("#topic");
const message = document.querySelector("#results-message");
const cards = document.querySelectorAll(".card");

function cardMatches(card, topic) {
  if (topic === "all") {
    return true;
  }
  return card.dataset.category === topic;
}

Inputs: a card and a topic. Output: true or false. data-category in HTML becomes dataset.category in JavaScript.

13

APP.JS · STEPS 3–5 · LOOP, EVENT, START

Check every card.
React to the form.

function showTopic(topic) {
  let visibleCount = 0;
  for (const card of cards) {
    if (cardMatches(card, topic)) { card.hidden = false; visibleCount = visibleCount + 1; }
    else { card.hidden = true; }
  }
  // then write "Showing X of 8" or "No opportunities…" into message
}

form.addEventListener("submit", function (event) {
  event.preventDefault();
  showTopic(topicSelect.value);
});
showTopic("all");
14

PAIR PRACTICE · 10 MIN

Change one thing.
Predict, then test.

  • Add a cardCopy one article, change the words. Does the message say 9 of 9?
  • VolunteeringGive your card data-category="volunteer". What happens to the empty message?
  • CapitalsChange "tech" to "Tech" on one card. Why does it vanish from the filter?

Console: try cards.length, then cardMatches(cards[0], "study").

15

CAPSTONE CHECKPOINT · MAKE IT YOURS

Same components.
Your content.

  • CardsSwap in your own opportunities, towns or quiz questions.
  • TopicsAdd a matching option and a .card[data-category] colour rule.
  • CheckKeyboard only, 200% zoom and 320px wide.

Read the full Opportunities Club explainer →

16
01/16

← → to navigate · F fullscreen

Slide 1 of 16