Concepts 🚀 Project 🧮 Labs
🚀 Learn with Project / Stage 10 — Add CSS — cards layout, hover effects
🚀 Stage 10 of 13
← Previous — Stage 9
Add Jinja2 templates — the form page
←

Add CSS — cards layout, hover effects

One stylesheet. Cards with hover lift, colored accent, responsive form. The visual jump from 'tutorial' to 'product'.

🧠 The intuition CSS is where the project goes from 'looks like a homework demo' to 'looks like a real product'. 4 ideas carry 80% of the visual jump: 1. RESET -> * { margin: 0; padding: 0; box-sizing: border-box; } 2. CARDS -> div with border + border-radius + padding + hover transform 3. ACCENT COLOR -> one brand color (e.g. #a0c000) used everywhere 4. FLEX / GRID -> for layouts, never use float or tables That's it. No Tailwind, no Bootstrap. One 50-line stylesheet is enough.
✏️ Tiny task — write this yourself Create app/static/style.css with the 4 ideas above. The result-card class should have a hover effect that lifts it 2px and adds a shadow. Reload / and see the visual jump.
✅ Check — what you should see After reloading, the form should be in a centered card with a border. The result page should have a big colored number for the predicted score. Hovering over a card should lift it slightly.

🧮 Code files for this stage

📄 app/static/style.css css
* { margin: 5px; padding: 0; box-sizing: border-box; }
body {
  font-family: system-ui, sans-serif;
  background: #fafafa;
  color: #1a1a1a;
}
header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem 2rem;
  background: white;
  border-bottom: 1px solid #e5e5e5;
}
header p { color: #a0c000; font-weight: 600; }
nav ul { list-style: none; display: flex; gap: 1rem; }
nav a { color: #a0c000; text-decoration: none; }
main { max-width: 720px; margin: 2rem auto; padding: 0 1rem; }

/* ─── Card ───────────────────────────────────────────── */
.card, .result-card {
  background: white;
  border: 1px solid #e5e5e5;
  border-radius: 10px;
  padding: 2rem;
  transition: all 0.2s;
}
.card:hover, .result-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 26px rgba(0,0,0,0.08);
}

/* ─── Form ──────────────────────────────────────────── */
form { display: grid; gap: 1rem; }
label { display: flex; flex-direction: column; font-size: 0.9rem; color: #555; }
input {
  padding: 0.6rem;
  border: 1px solid #ddd;
  border-radius: 6px;
  font-size: 1rem;
}
input:focus { outline: 2px solid #a0c000; border-color: #a0c000; }
button {
  background: #a0c000;
  color: white;
  border: none;
  border-radius: 6px;
  padding: 0.8rem;
  font-size: 1rem;
  cursor: pointer;
  transition: background 0.2s;
}
button:hover { background: #8aab00; }

/* ─── Result ────────────────────────────────────────── */
.score-number {
  font-size: 4rem;
  font-weight: bold;
  color: #a0c000;
  text-align: center;
  margin: 1rem 0;
}
.band { text-align: center; color: #555; font-size: 1.1rem; }
.back-btn {
  display: inline-block;
  margin-top: 1rem;
  color: #a0c000;
  text-decoration: none;
}
📄 app/main.py (add static mount) python
# Add this near the top of main.py, after `app = FastAPI()`:
from fastapi.staticfiles import StaticFiles
app.mount('/static', StaticFiles(directory='app/static'), name='static')

# Now <link rel='stylesheet' href='/static/style.css'> in base.html works.

How it works

box-sizing: border-box is the most important CSS line — it makes padding NOT add to the element's width. Without it, a 200px card with 20px padding is actually 240px wide, which breaks layouts. transition: all 0.2s on cards is what makes the hover effect smooth instead of instant. translateY(-2px) moves the card UP 2px on hover — combined with the shadow, it looks like the card is being lifted. The accent color #a0c000 is used 6 times: header text, nav links, input focus ring, button background, score number, back link. ONE color, used consistently, is what makes a design feel intentional.

🚧 Routes — what's wired up at this stage No new routes. We add a STATIC FILE route — FastAPI serves /static/* from app/static/. Already wired up by mounting StaticFiles in main.py.
🧮 Is a response model necessary here? NO — use response_class=HTMLResponse

CSS is static. The /static/style.css route returns a file directly, no Pydantic involved.

⚠️ Common mistakes to avoid
  • Using float or tables for layout — always use flex/grid.
  • Adding 5 different accent colors — pick ONE and use it everywhere.
  • Forgetting box-sizing: border-box — layouts will be off by the padding amount and you'll go crazy debugging.
👀 What's next Next: Stage 11 adds a /model-info page that surfaces the training metrics + a feature-importance plot. Now the website explains ITSELF.
Next — Stage 11
Add a /model-info page — show metrics + feature importance
→