Skip to main content

πŸ› οΈ Weekend Project: CSS

Time to stop reading CSS and start shipping it. Over one focused weekend you'll build a small multi-page site β€” a mini "problem-solving" microsite β€” that puts every Module 5 technique to work: layout, glassmorphism, 3D card flips, transitions, filters, and a scroll-reactive nav. You'll build it in milestones, check yourself against a clear bar, and finish with something portfolio-worthy.

🎯 Learning Objectives

By the end of this build, you will be able to:

  • Assemble a multi-page site with a shared, sticky, scroll-reactive navigation bar
  • Compose advanced visual effects β€” glassmorphism, gradient text, layered shadows, and CSS filters β€” into a cohesive look
  • Build an interactive 3D flip-card component with transform-style: preserve-3d and backface-visibility
  • Apply a mobile-first, responsive grid and honor prefers-reduced-motion for accessibility
  • Ship in milestones and self-assess against a "what good looks like" bar

Estimated Time: One weekend (6–10 hours)  β€’  Difficulty: Intermediate

Hands-on: This entire lesson is the exercise β€” build the site milestone by milestone using the starter code provided.

In This Lesson

The Brief

Your client is you. Build a small, good-looking educational microsite that explains a four-step problem-solving method β€” George PΓ³lya's classic framework from his 1945 book How to Solve It. The topic is just a vehicle: it gives you exactly four repeatable content blocks (perfect for a card grid) plus a home page, so you can focus your energy on the CSS.

πŸ“– Why PΓ³lya?

PΓ³lya's method β€” Understand β†’ Plan β†’ Execute β†’ Review β€” happens to be the same loop you'll use to build this site. Four steps means four flip cards. It's tidy content that shows off your layout skills without you having to invent a product.

Minimum pages to produce:

PagePurposeStar CSS technique
index.htmlHero + overview of the four stepsGlassmorphism hero, gradient text
steps.htmlThe four steps as interactive cards3D flip cards
apply.htmlHow the method maps to web devResponsive card grid + hover filters
about.htmlWho PΓ³lya was; further readingTypography & layered shadows

⚠️ Scope guard

Four pages, one shared stylesheet. Resist the urge to add a fifth page or a JS framework. The grade here is craft on a small surface, not size. If you finish early, deepen the polish β€” don't widen the scope.

The Build Roadmap

You'll ship in five milestones. Each one leaves you with something that works in the browser, so you're never more than one milestone away from a demoable site. Build in this order β€” later milestones lean on the design tokens and layout you set up first.

flowchart LR M1[M1 Β· Scaffold & tokens] --> M2[M2 Β· Nav & hero] M2 --> M3[M3 Β· 3D flip cards] M3 --> M4[M4 Β· Grid & effects] M4 --> M5[M5 Β· Polish & ship] M5 -.->|review & refine| M1

Notice the dotted line back to the start: that's PΓ³lya's Review step. After a first pass, loop back and raise the weakest area rather than piling on new features.

πŸ’‘ Work mobile-first

Write your base styles for a narrow phone screen, then add complexity at wider breakpoints with min-width media queries. It's far easier to grow a simple layout than to cram a desktop one into a phone.

Milestone 1 β€” Scaffold & Design Tokens

Goal: four HTML files that share one stylesheet, and a set of CSS custom properties (design tokens) so every color, radius, and shadow is defined in one place. Tokens are what make a site feel cohesive instead of assembled from random values.

Folder structure

polya-site/
β”œβ”€β”€ index.html
β”œβ”€β”€ steps.html
β”œβ”€β”€ apply.html
β”œβ”€β”€ about.html
└── css/
    └── style.css

Design tokens (top of style.css)

Define everything as variables on :root. Bonus: a dark-mode override costs almost nothing once your colors are tokens.

:root {
  /* Brand */
  --brand: #0070f3;
  --brand-2: #00c8ff;
  --gradient: linear-gradient(45deg, var(--brand), var(--brand-2));

  /* Surface & text */
  --bg: #f4f7fb;
  --surface: #ffffff;
  --ink: #1e293b;
  --ink-soft: #64748b;

  /* Shape & depth */
  --radius: 1rem;
  --shadow-sm: 0 5px 15px rgb(0 0 0 / 0.10);
  --shadow-lg: 0 20px 40px rgb(0 0 0 / 0.15);

  /* Rhythm */
  --space: clamp(1rem, 2vw, 2rem);
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #0f172a;
    --surface: #1e293b;
    --ink: #e2e8f0;
    --ink-soft: #94a3b8;
    --shadow-sm: 0 5px 15px rgb(0 0 0 / 0.40);
    --shadow-lg: 0 20px 40px rgb(0 0 0 / 0.50);
  }
}

*, *::before, *::after { box-sizing: border-box; }

body {
  margin: 0;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--ink);
  background: var(--bg);
  line-height: 1.6;
}

βœ… Milestone 1 is done when…

All four pages open, link to the same css/style.css, share the same background and font, and you can change the whole site's accent color by editing a single --brand value.

Milestone 2 β€” Navigation & Hero

Goal: a shared sticky navigation bar that condenses on scroll, plus a glassmorphism hero on the home page with gradient-text heading.

Anatomy of the glassmorphism hero A background image layer sits behind a frosted translucent card. The card holds a gradient-text title and lifts on hover. A sticky navigation bar spans the top. background-image (cover, centered) sticky nav Β· backdrop-filter: blur() Β· shrinks on scroll Gradient Title frosted glass card Β· box-shadow depth translateY(-10px) on hover
Figure 1 β€” The hero is three stacked layers: a background image, a translucent blurred card, and gradient text. The nav bar floats on top and reacts to scroll.

Sticky, scroll-reactive nav

Put this markup at the top of every page (identical, so the site feels like one place). The .scrolled class is toggled by a tiny bit of JS.

<nav class="site-nav">
  <a class="site-brand" href="index.html">How to Solve It</a>
  <ul class="site-links">
    <li><a href="index.html">Home</a></li>
    <li><a href="steps.html">The Steps</a></li>
    <li><a href="apply.html">Apply It</a></li>
    <li><a href="about.html">About</a></li>
  </ul>
</nav>
.site-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 1rem;
  padding: 1rem var(--space);
  background: rgb(255 255 255 / 0.8);
  backdrop-filter: blur(10px);
  box-shadow: var(--shadow-sm);
  transition: padding 0.3s ease;
}
.site-nav.scrolled { padding-block: 0.4rem; }

.site-links {
  display: flex;
  gap: 1.5rem;
  list-style: none;
  margin: 0;
  padding: 0;
}
.site-links a {
  position: relative;
  color: var(--ink);
  text-decoration: none;
  font-weight: 500;
}
/* underline that grows from the left */
.site-links a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -4px;
  height: 2px;
  width: 0;
  background: var(--gradient);
  transition: width 0.3s ease;
}
.site-links a:hover::after,
.site-links a[aria-current="page"]::after { width: 100%; }
// tiny progressive enhancement β€” condense the nav after scrolling
const nav = document.querySelector('.site-nav');
window.addEventListener('scroll', () => {
  nav.classList.toggle('scrolled', window.scrollY > 40);
}, { passive: true });

Glassmorphism hero + gradient text

.hero {
  min-height: 70vh;
  display: grid;
  place-items: center;
  padding: var(--space);
  background: var(--gradient); /* swap for an image once you have one */
  background-size: cover;
  background-position: center;
}
.hero-card {
  max-width: 40rem;
  padding: clamp(1.5rem, 4vw, 3rem);
  text-align: center;
  border-radius: var(--radius);
  background: rgb(255 255 255 / 0.15);
  backdrop-filter: blur(10px);
  border: 1px solid rgb(255 255 255 / 0.25);
  box-shadow: var(--shadow-lg);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.hero-card:hover {
  transform: translateY(-10px);
  box-shadow: 0 30px 60px rgb(0 0 0 / 0.25);
}
.hero-title {
  margin: 0 0 0.5rem;
  font-size: clamp(2rem, 6vw, 3.5rem);
  /* gradient text: paint the gradient, then clip it to the glyphs */
  background: var(--gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

⚠️ Gradient-text gotcha

You need both -webkit-background-clip: text and the standard background-clip: text, plus color: transparent. Miss the transparent color and the text paints solid over the gradient β€” you'll see nothing special.

βœ… Milestone 2 is done when…

The nav is identical on all pages, sticks to the top, and visibly shrinks when you scroll. The hero card looks frosted, the title shows the gradient, and the current page's nav link keeps its underline.

Milestone 3 β€” The 3D Flip Cards

Goal: the steps.html page shows PΓ³lya's four steps as cards that flip in 3D to reveal detail on the back. This is the showpiece β€” get it crisp.

Three properties do the heavy lifting: perspective on the container gives depth, transform-style: preserve-3d lets the two faces live in the same 3D space, and backface-visibility: hidden stops the back from showing through the front.

<div class="flip" tabindex="0">
  <div class="flip-inner">
    <div class="flip-face flip-front">
      <span class="flip-num">1</span>
      <h3>Understand</h3>
    </div>
    <div class="flip-face flip-back">
      <p>Name what's known, unknown, and the goal β€” in your own words.</p>
    </div>
  </div>
</div>
.flip {
  width: 100%;
  aspect-ratio: 3 / 4;
  perspective: 1000px; /* depth of the 3D scene */
}
.flip-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
}
/* flip on hover AND keyboard focus for accessibility */
.flip:hover .flip-inner,
.flip:focus-visible .flip-inner { transform: rotateY(180deg); }

.flip-face {
  position: absolute;
  inset: 0;
  display: grid;
  place-content: center;
  gap: 0.5rem;
  padding: 1.5rem;
  border-radius: var(--radius);
  backface-visibility: hidden;
  box-shadow: var(--shadow-sm);
}
.flip-front { background: var(--surface); }
.flip-back {
  background: var(--brand);
  color: #fff;
  transform: rotateY(180deg); /* pre-rotate so it faces us after the flip */
}
.flip-num {
  font-size: 4rem;
  font-weight: 800;
  line-height: 1;
  background: var(--gradient);
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
}

πŸ’‘ Why tabindex="0" and :focus-visible?

A hover-only interaction is invisible to keyboard and touch users. Making the card focusable and flipping it on focus means anyone can reveal the back. Small change, big accessibility win.

βœ… Milestone 3 is done when…

Four cards sit in a responsive row, each flips smoothly on hover and when tabbed to, the back face is readable (no mirrored text bleeding through), and there's no flicker at the halfway point of the flip.

Milestone 4 β€” Content Grid & Effects

Goal: the apply.html page uses an auto-fitting responsive grid of example cards, each with a lift-on-hover, a gradient top-bar that wipes in, and an image that de-saturates until you hover it.

Responsive grid with zero media queries

auto-fit plus minmax() gives you a grid that reflows on its own β€” one column on a phone, several on a wide screen β€” without a single breakpoint.

.examples {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
  gap: var(--space);
  padding: var(--space);
}
.example {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.example:hover {
  transform: translateY(-10px) scale(1.02);
  box-shadow: var(--shadow-lg);
}
/* accent bar that wipes in from the left */
.example::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 5px;
  background: var(--gradient);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.3s ease;
}
.example:hover::before { transform: scaleX(1); }

/* filter: desaturate until hover */
.example img {
  width: 100%;
  height: 12rem;
  object-fit: cover;
  filter: grayscale(60%);
  transition: filter 0.3s ease;
}
.example:hover img { filter: grayscale(0%); }

⚠️ Animate cheap properties

Prefer animating transform and opacity β€” the browser can offload them to the GPU. Animating width, height, top, or margin forces expensive layout recalculation ("layout thrash") and stutters on lower-end devices.

βœ… Milestone 4 is done when…

Cards reflow from one column on mobile to three-plus on desktop with no horizontal scroll, each card lifts and reveals its accent bar on hover, and images snap from muted to full color. Resize the window to prove the grid adapts.

Milestone 5 β€” Polish, A11y & Ship

Goal: take the working site from "it functions" to "it's finished." This is PΓ³lya's Review step β€” the difference between a demo and a portfolio piece.

Respect reduced motion

Some users get motion sickness from big transitions. Honor their OS setting β€” this one media query is the single most important accessibility upgrade for an effects-heavy site.

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

Provide fallbacks for backdrop-filter

Where a browser lacks backdrop-filter, a plain translucent background still reads fine. Detect support and enhance rather than assume.

.hero-card { background: rgb(255 255 255 / 0.6); } /* readable fallback */

@supports (backdrop-filter: blur(10px)) {
  .hero-card {
    background: rgb(255 255 255 / 0.15);
    backdrop-filter: blur(10px);
  }
}

Final polish pass

  • Check color contrast β€” body text should hit WCAG AA (4.5:1). Gradient text over a busy image is the usual offender.
  • Tab through every page with the keyboard: can you reach and trigger every interactive element? Is the focus outline visible?
  • Run Lighthouse and WAVE; fix anything red.
  • Test at 360 px, 768 px, and 1280 px widths.

βœ… Milestone 5 is done when…

The site is fully keyboard-navigable, honors reduced motion, degrades gracefully without backdrop-filter, passes a Lighthouse accessibility check, and looks intentional at three screen widths. Now it's shippable.

What Good Looks Like

Use this bar to grade yourself honestly. "Good" isn't "used the most effects" β€” it's restraint plus polish. A calm site with three effects done cleanly beats a noisy one with ten done sloppily.

AreaNeeds workGoodExcellent
Cohesion Random colors & spacing per page Shared tokens; consistent look One accent change re-themes the whole site
Effects Effects fight for attention Each effect supports the content Effects feel invisible β€” they just work
Responsive Horizontal scroll on mobile Reflows cleanly at all widths Layout improves, not just survives, on wide screens
Accessibility Hover-only, low contrast Keyboard-usable, AA contrast Honors reduced motion + graceful fallbacks
Code Copy-pasted values everywhere Tokens + clear class names Commented, DRY, easy to extend

πŸ’‘ The one-sentence test

Show the site to someone for five seconds, then hide it and ask what it was about. If they can answer, your design served the content. If they only remember "it was flashy," dial the effects back.

Ship Checklist

Before you call it done, walk this list top to bottom. Each item maps to a milestone above.

πŸ“‹ Final checklist

  • ☐ Four pages share one stylesheet and one set of :root tokens
  • ☐ Sticky nav is identical on every page and condenses on scroll
  • ☐ Current page is marked with aria-current="page"
  • ☐ Hero uses glassmorphism + gradient text with a readable fallback
  • ☐ Four flip cards work on hover and keyboard focus
  • ☐ Example grid reflows via auto-fit / minmax() β€” no horizontal scroll
  • ☐ Animations use transform/opacity, not layout properties
  • ☐ prefers-reduced-motion is respected
  • ☐ Body text meets WCAG AA contrast (4.5:1)
  • ☐ Lighthouse accessibility score is green
  • ☐ Verified at 360 / 768 / 1280 px widths

🎯 Quick Quiz

Question 1: Which trio of CSS properties makes a 3D flip card work?

Question 2: Why prefer animating transform over width or margin?

Question 3: What's the single most important accessibility upgrade for this effects-heavy site?

Summary & Quiz

πŸŽ‰ Key Takeaways

  • Ship in milestones β€” every step leaves you a working, demoable site.
  • Design tokens on :root are what make a multi-page site feel like one product.
  • The flip-card trio β€” perspective, preserve-3d, backface-visibility β€” is the reusable pattern behind most 3D UI.
  • auto-fit + minmax() gives responsive grids with no media queries.
  • Polish is accessibility: keyboard support, contrast, fallbacks, and prefers-reduced-motion.
  • Good design is restraint β€” effects should serve the content, never upstage it.

πŸ“š Further Reading

πŸš€ What's Next?

You've squeezed everything out of hand-tuned CSS layout and effects. Next module we graduate to a modern layout engine built for exactly this kind of one-dimensional arrangement: Flexbox Layout Concepts. Much of the nav and card work you just did by hand gets dramatically simpler with it.

πŸŽ‰ You shipped a site!

Keep this project in your portfolio β€” it's concrete proof you can turn CSS knowledge into a polished, accessible, real thing.