From Nothing to Pressing Room: A Git Tutorial
Prerequisites, and Step 0 — from nothing
You need: a terminal, git installed, a text editor, and a browser.
Optionally,
ImageMagick (convert)
for the image-optimization step — skip it and just use unoptimized
images if you don't want to install it, the page still works.
This guide gives you the literal, complete contents of every file, in
the order you'd naturally write them. Copy each code block into the
file named just above it. At the end of every step there's a
git commit — run it before moving on, so your history
matches the steps below one-to-one.
Start from a truly empty folder:
mkdir pressing-room && cd pressing-room
git init
mkdir styles images assets
That's it for Step 0 — there's nothing to commit yet since there's nothing to add. The next step creates your first real file.
Step 1 — Design tokens: styles/design-system.css
This is the foundation everything else builds on: every color, spacing
value, radius, and font as a CSS custom property, plus a small library
of reusable classes (.btn, .tag,
.card, .input…) built on top of them. Create
styles/design-system.css with exactly this:
/* Modernist — design-system tokens and component classes. This file is the source of truth for the system's look; retune it here and see readme.md. */
@import url('https://fonts.googleapis.com/css2?family=Archivo:wght@400;600;800&display=swap');
:root {
--color-bg: #f3f2f2;
--color-surface: #eae9e9;
--color-text: #201e1d;
--color-accent: #ec3013;
--color-accent-2: #e15b47;
--color-divider: color-mix(in srgb, #201e1d 40%, transparent);
--color-neutral-100: #f8f4f4;
--color-neutral-200: #eae7e7;
--color-neutral-300: #d7d3d3;
--color-neutral-400: #bab6b6;
--color-neutral-500: #9b9797;
--color-neutral-600: #7d7979;
--color-neutral-700: #605d5d;
--color-neutral-800: #444141;
--color-neutral-900: #2d2b2b;
--color-accent-100: #fff2ef;
--color-accent-200: #ffe0d9;
--color-accent-300: #ffc4b8;
--color-accent-400: #ff9783;
--color-accent-500: #ff563c;
--color-accent-600: #dd2b0f;
--color-accent-700: #ae1800;
--color-accent-800: #7c1405;
--color-accent-900: #4d170e;
--color-accent-2-100: #fff2ef;
--color-accent-2-200: #ffe0da;
--color-accent-2-300: #ffc4b9;
--color-accent-2-400: #ff9784;
--color-accent-2-500: #ef6853;
--color-accent-2-600: #c94b39;
--color-accent-2-700: #9e3526;
--color-accent-2-800: #71261b;
--color-accent-2-900: #471d16;
--font-heading: "Archivo", system-ui, sans-serif;
--font-heading-weight: 800;
--font-body: "Archivo", system-ui, sans-serif;
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
--radius-sm: 0;
--radius-md: 0;
--radius-lg: 0;
--shadow-sm: 0 1px 2px color-mix(in srgb, #2d2b2b 14%, transparent);
--shadow-md: 0 3px 10px color-mix(in srgb, #2d2b2b 16%, transparent);
--shadow-lg: 0 12px 32px color-mix(in srgb, #2d2b2b 22%, transparent);
}
body {
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-body);
}
h1, h2, h3, h4 { font-family: var(--font-heading); font-weight: var(--font-heading-weight); }
.grayscale{filter:grayscale(1) contrast(1.08)}
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; font-size: 0.9375rem; line-height: 1.55; font-weight: 400; }
h1, h2, h3, h4, h5, h6 {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
line-height: 1.12; letter-spacing: -0.015em; margin: 0 0 var(--space-2);
}
h1 { font-size: 2.625rem; }
h2 { font-size: 2rem; }
h3 { font-size: 1.5625rem; }
h4 { font-size: 1.25rem; }
h5 { font-size: 1rem; }
h6 { font-size: 0.8125rem; }
h6, .heading-label { letter-spacing: 0.11em; text-transform: uppercase; }
.heading-label { font-size: 0.6875rem; }
p { margin: 0 0 var(--space-3); }
a { color: var(--color-accent); text-underline-offset: 3px; }
img { display: block; max-width: 100%; }
figure { margin: 0; }
figcaption {
font-size: 0.6875rem; margin-top: var(--space-1);
color: color-mix(in srgb, var(--color-text) 55%, transparent);
}
.visually-hidden {
position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0;
}
.text-muted { color: color-mix(in srgb, var(--color-text) 55%, transparent); }
:focus { outline: none; }
:focus-visible { outline: 2px solid var(--color-accent); outline-offset: 2px; }
::selection { background: color-mix(in srgb, var(--color-accent) 30%, transparent); }
.hr {
height: 2px; border: 0; margin: var(--space-4) 0;
background: var(--color-divider);
}
.btn {
display: inline-flex; align-items: center; justify-content: center; gap: 0.375rem;
cursor: pointer; text-decoration: none;
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 0.875rem; line-height: 1.2; color: var(--color-text);
background: transparent; border: 1px solid transparent;
padding: var(--space-2) calc(var(--space-3) * 1.2);
border-radius: var(--radius-md);
}
.btn svg { display: block; }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn-primary { background: var(--color-accent); color: var(--color-bg); }
.btn-primary:hover { background: var(--color-accent-600); }
.btn-primary:active { background: var(--color-accent-700); }
.btn-secondary { border-color: var(--color-divider); }
.btn-secondary:hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.btn-secondary:active { background: color-mix(in srgb, var(--color-text) 14%, transparent); }
.btn-ghost { color: var(--color-accent); padding-inline: var(--space-1); }
.btn-ghost:hover { background: color-mix(in srgb, var(--color-accent) 10%, transparent); }
.btn-ghost:active { background: color-mix(in srgb, var(--color-accent) 18%, transparent); }
.btn-icon { width: 36px; height: 36px; padding: 0; }
.btn-block { width: 100%; margin-top: var(--space-2); justify-content: flex-start; text-align: left; }
.field > label {
display: block; font-size: 0.75rem; margin-bottom: 0.3125rem;
color: color-mix(in srgb, var(--color-text) 70%, transparent);
}
.input {
width: 100%; min-height: 36px; padding: 0.375rem 0.625rem; font: inherit;
font-size: 0.875rem; color: var(--color-text); caret-color: var(--color-accent);
background: var(--color-surface);
border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.input:hover { border-color: color-mix(in srgb, var(--color-text) 45%, transparent); }
.input:focus-visible { border-color: var(--color-accent); outline-offset: 0; }
textarea.input { min-height: 90px; resize: vertical; }
.radio { display: inline-flex; align-items: center; gap: 0.5rem; cursor: pointer; font-size: 0.875rem; }
.radio input, .seg-opt input {
position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none;
}
.radio .dot {
width: 16px; height: 16px; flex: none; border-radius: 50%;
border: 1.5px solid var(--color-divider);
}
.radio:hover .dot { border-color: var(--color-accent); }
.radio input:checked + .dot {
border-color: var(--color-accent); background: var(--color-accent);
box-shadow: inset 0 0 0 4px var(--color-bg);
}
.radio input:focus-visible + .dot { outline: 2px solid var(--color-accent); outline-offset: 2px; }
.seg {
display: inline-flex; overflow: hidden;
border: 1px solid var(--color-divider); border-radius: var(--radius-md);
}
.seg-opt {
display: inline-flex; align-items: center; gap: 0.375rem;
padding: 0.4375rem 0.75rem; font-size: 0.8125rem; cursor: pointer;
}
.seg-opt + .seg-opt { border-left: 1px solid var(--color-divider); }
.seg-opt:has(input:checked) { background: var(--color-accent); color: var(--color-bg); }
.seg-opt:not(:has(input:checked)):hover { background: color-mix(in srgb, var(--color-text) 7%, transparent); }
.seg-opt:has(input:focus-visible) { outline: 2px solid var(--color-accent); outline-offset: -2px; }
.card {
display: flex; flex-direction: column; gap: var(--space-2);
padding: var(--space-3); border-radius: var(--radius-md); background: var(--color-surface);
}
.card-kicker { font-size: 0.625rem; letter-spacing: 0.1em; text-transform: uppercase; color: var(--color-accent); }
.card-title {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 1.0625rem; line-height: 1.2;
}
.card-body { margin: 0; font-size: 0.8125rem; opacity: 0.8; flex: 1; }
.card-meta {
display: flex; align-items: center; gap: 0.375rem; font-size: 0.6875rem;
color: color-mix(in srgb, var(--color-text) 50%, transparent);
}
.elev-sm { box-shadow: var(--shadow-sm); }
.elev-md { box-shadow: var(--shadow-md); }
.elev-lg { box-shadow: var(--shadow-lg); }
.tag {
display: inline-flex; align-items: center; font-size: 0.6875rem;
letter-spacing: 0.02em; padding: 0.1875rem 0.625rem;
border-radius: calc(var(--radius-md) * 0.75);
}
.tag-accent { background: var(--color-accent-100); color: var(--color-accent-800); }
.tag-accent-2 { background: var(--color-accent-2-100); color: var(--color-accent-2-800); }
.tag-neutral { background: var(--color-neutral-100); color: var(--color-neutral-800); }
.tag-outline { border: 1px solid var(--color-accent); color: var(--color-accent); }
.nav {
display: flex; align-items: center; gap: var(--space-4);
padding: var(--space-3) var(--space-4);
border-bottom: 2px solid var(--color-divider);
}
.nav-brand {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 1.125rem; margin-right: auto;
}
.nav a { color: inherit; text-decoration: none; font-size: 0.875rem; }
.nav a:hover, .nav a[aria-current='page'] { color: var(--color-accent); }
.table { width: 100%; border-collapse: collapse; font-size: 0.875rem; }
.table th {
text-align: left; font-size: 0.6875rem; letter-spacing: 0.08em; text-transform: uppercase;
color: color-mix(in srgb, var(--color-text) 60%, transparent);
padding: var(--space-2); border-bottom: 2px solid var(--color-divider);
}
.table td {
padding: var(--space-2);
border-bottom: 1px solid var(--color-divider);
}
.table tbody tr:hover { background: color-mix(in srgb, var(--color-text) 4%, transparent); }
.dialog-backdrop {
position: fixed; inset: 0; display: grid; place-items: center;
padding: var(--space-4);
background: color-mix(in srgb, var(--color-neutral-900) 50%, transparent);
}
.dialog {
width: min(440px, 100%); display: flex; flex-direction: column; gap: var(--space-3);
padding: var(--space-4); border-radius: var(--radius-lg);
background: var(--color-surface); box-shadow: var(--shadow-lg);
}
.dialog-title {
font-family: var(--font-heading); font-weight: var(--font-heading-weight);
font-size: 1.25rem;
}
.dialog-body { font-size: 0.875rem; opacity: 0.85; }
.dialog-actions { display: flex; justify-content: flex-end; gap: var(--space-2); margin-top: var(--space-2); }
A few things worth noticing before you move on:
-
--radius-*are all0. This design system is deliberately sharp-cornered — no rounded buttons, cards, or images anywhere. Changing the whole site's corner style later is a one-line edit here. -
Every
remvalue traces back to apxdesign spec divided by 16 (the default root font size).0.875remis 14px,1.375remis 22px, and so on — rem instead of px so the whole site scales if a visitor bumps their browser's text size. -
.btn,.tag,.card,.inputare generic — nothing here mentions "hero" or "header" or "footer". Section-specific styling comes later, in its own file, and reuses these.
Commit it:
git add styles/design-system.css
git commit -m "feat: design-token foundation and base component classes"
Step 2 — Global reset and the logo mark
Create styles/base.css:
/* — global page setup — */
body { margin: 0; }
a { color: var(--color-text); text-decoration: none; }
a:hover { color: var(--color-accent-700); }
.page {
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-body);
min-height: 100vh;
}
This is deliberately tiny — it's the handful of rules that apply to
the whole page and don't belong to any one section, kept separate
from design-system.css (which is tokens and reusable
components, not page setup).
Next, the brand mark itself: two small SVGs, one for light backgrounds
and one for dark. Create assets/logo-mark.svg:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<rect width="64" height="64" fill="#ec3013"></rect>
<circle cx="32" cy="32" r="19" fill="none" stroke="#f3f2f2" stroke-width="10"></circle>
<circle cx="32" cy="32" r="4" fill="#f3f2f2"></circle>
</svg>
And assets/logo-mark-reversed.svg — same shape, inverted
colors, for the dark footer:
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" width="64" height="64">
<rect width="64" height="64" fill="#f3f2f2"></rect>
<circle cx="32" cy="32" r="19" fill="none" stroke="#201e1d" stroke-width="10"></circle>
<circle cx="32" cy="32" r="4" fill="#ec3013"></circle>
</svg>
The mark itself is simple on purpose: a filled square (the record
sleeve), a ring (the record), a dot (the spindle hole). If you're
hand-typing SVGs for the first time —
viewBox="0 0 64 64" defines a 64×64 coordinate
space; width/height on the tag itself just set
its default display size, and CSS can override that later wherever
it's used.
Commit both:
git add styles/base.css assets/
git commit -m "feat: global reset and brand mark SVGs"
Step 3 — The document shell and the header
Create index.html. This is the whole
<head>, plus the opening of the page and the header —
every later step appends more markup right before the final
</div></body></html>, which you won't
add until Step 9. Browsers render unclosed HTML fine, so you can preview
after every step; just don't treat the page as finished until Step
9's closing tags are in.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Pressing Room — Analog sound, digital ease</title>
<link rel="stylesheet" href="styles/design-system.css">
<link rel="stylesheet" href="styles/base.css">
<link rel="stylesheet" href="styles/header.css">
</head>
<body>
<div class="page">
<header class="site-header">
<div class="site-header__inner">
<a href="#" class="brand">
<img class="brand__icon" src="assets/logo-mark.svg" alt="" width="26" height="26">
<span class="brand__name">Pressing Room</span>
</a>
<label class="site-search">
<span class="visually-hidden">Search</span>
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="11" cy="11" r="7"></circle><path d="m20 20-3.6-3.6"></path></svg>
<input class="input" placeholder="Search artists, albums, labels">
</label>
<nav class="site-nav" aria-label="Main">
<a href="#">Home</a>
<a href="#">About</a>
<a href="#" class="is-active" aria-current="page">Shop</a>
<a href="#">Help</a>
</nav>
<a class="btn btn-primary" href="#">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="9" cy="20" r="1.4"></circle><circle cx="18" cy="20" r="1.4"></circle><path d="M2 3h2.2l2.6 12h11.4l2.2-8H6"></path></svg>
Your Cart (2)
</a>
</div>
</header>
Notice
<img class="brand__icon" ... alt="">
— the alt is deliberately empty, not missing. The logo sits
right next to the visible text "Pressing Room", so a screen
reader announcing the image's name too would just repeat it.
aria-hidden="true" on the two
<svg> icons does the same job for icons that are
purely decorative next to their own label.
<nav aria-label="Main"> names this
navigation region distinctly, since a page can have more than one
<nav> (the footer will have four more, later).
Now styles/header.css:
/* — header — */
.site-header {
position: sticky; top: 0; z-index: 20;
background: var(--color-bg);
border-bottom: 2px solid var(--color-divider);
}
.site-header__inner {
max-width: 1200px; margin: 0 auto; padding: 0.875rem 2rem;
display: flex; align-items: center; gap: 2rem; flex-wrap: wrap;
}
.brand {
display: flex; align-items: center; gap: 0.625rem;
margin-right: auto; color: var(--color-text);
}
.brand__icon { width: 26px; height: 26px; flex: none; display: block; }
.brand__name {
font-family: var(--font-heading); font-weight: 700; font-size: 1.0625rem;
letter-spacing: -0.01em; text-transform: uppercase;
}
.site-search {
display: flex; align-items: center; gap: 0.5625rem;
min-width: 190px; flex: 1 1 190px; max-width: 320px;
border: 2px solid var(--color-divider); padding: 0.375rem 0.75rem;
}
.site-search svg { flex: none; }
.site-search .input { border: 0; background: transparent; min-height: auto; padding: 0; font-size: 0.8125rem; }
.site-nav {
display: flex; align-items: center; gap: 1.625rem;
font-size: 0.75rem; letter-spacing: .08em; text-transform: uppercase;
}
.site-nav a { color: var(--color-text); }
.site-nav a.is-active {
color: var(--color-accent-700); border-bottom: 2px solid var(--color-accent); padding-bottom: 2px;
}
/* — responsive — */
@media (max-width: 768px) {
.site-header__inner { padding: 0.75rem 1.25rem; gap: 1rem; }
.site-search { order: 3; max-width: none; flex-basis: 100%; }
.site-nav { order: 4; flex-basis: 100%; justify-content: center; gap: 1rem; }
}
margin-right: auto on .brand is doing real
layout work — in a flex row, one auto margin eats all the
leftover space on that side, which is what pushes the search box, nav,
and cart button to the right without a fixed width anywhere. The
@media block at the bottom is the first real responsive
behavior in this project: under 768px, the search box and nav each claim
a full row of their own (flex-basis: 100%) via CSS
order, instead of squeezing into one cramped line.
Open index.html directly in a browser now (double-click the
file, or drag it into a tab) — you should see a working sticky header,
even though the rest of the page is blank.
git add index.html styles/header.css
git commit -m "feat: document shell and header section"
Step 4 — Real images, sourced and optimized
The finished page uses 11 real album covers. You'll need your own copies — covers aren't something this guide can hand you as text, and if you're building a public portfolio piece rather than a private exercise, use art you have the right to use (your own photos of your own records, or covers from an artist/label's official press kit) rather than assuming any download is fair game.
Save each one into images/ under this
exact filename — every later step's HTML points at
these paths:
| Filename | Artist — Album |
|---|---|
bruce-springsteen-born-in-the-usa.jpeg |
Bruce Springsteen — Born in the U.S.A. |
pink-floyd-dark-side-of-the-moon.jpeg |
Pink Floyd — The Dark Side of the Moon |
radiohead-ok-computer.jpeg |
Radiohead — OK Computer |
beatles-1967-1970.jpeg |
The Beatles — 1967-1970 |
frank-ocean-blond.jpg |
Frank Ocean — Blond |
tyler-the-creator-igor.jpg |
Tyler, The Creator — IGOR |
bon-iver-22-a-million.jpg |
Bon Iver — 22, A Million |
john-mayer-born-and-raised.jpg |
John Mayer — Born and Raised |
hozier-unreal-unearth-unending.jpg |
Hozier — Unreal Unearth: Unending |
frank-sinatra-nothing-but-the-best.jpeg |
Frank Sinatra — Nothing but the Best |
ralphie-choo-damor-traficante.jpeg |
Ralphie Choo — D'Amor Traficante |
Every slot on this page is a square, so a square (or near-square) source image looks best — most official cover art already is. Don't worry about matching resolution exactly.
Now the part that actually matters for a real repo: resize and compress before you commit. A cover straight off the internet is often 1500–2500px square and several hundred KB — the page never displays one larger than a few hundred pixels, so most of that is pure download weight. With ImageMagick installed:
cd images
for f in *.jpg *.jpeg; do
[ -f "$f" ] || continue
convert "$f" -resize '1000x1000>' -strip -sampling-factor 4:2:0 -quality 82 -interlace JPEG "$f"
done
cd ..
What each flag does:
-resize '1000x1000>' shrinks anything bigger
than 1000px on its long edge and leaves smaller images alone (the
trailing > means "only shrink, never
enlarge"); -strip removes embedded metadata (camera
info, color profiles) the browser doesn't need;
-quality 82 is JPEG compression that's very hard to see
at normal viewing sizes; -interlace JPEG makes it render
progressively (a blurry-then-sharp preview) instead of top-to-bottom on
a slow connection. On the real version of this project, that one command
cut total image weight by about two-thirds with no visible loss.
No ImageMagick and don't want to install it? Skip this and commit the images as-is — the page still works, it just ships more bytes than it needs to.
git add images/
git commit -m "feat: add real album cover images"
Step 5 — The hero
Add
<link rel="stylesheet"
href="styles/hero.css">
to index.html's <head>, right after
the header.css line. Then, right after
</header> (still inside
<div class="page">), add:
<section class="hero">
<div class="hero__split">
<div class="hero__panel-text">
<p class="tag tag-accent">New arrivals every Friday</p>
<div class="hero__headline">
<div class="hero__headline-rule" aria-hidden="true"></div>
<h1 class="hero__title">Analog sound<span class="hero__punct">,</span><br><span class="hero__band">digital ease.</span></h1>
</div>
<div class="hr"></div>
<p class="hero__copy">Fourteen thousand records on the shelves. Graded by hand, played before they ship.</p>
<div class="hero__actions">
<a class="btn btn-primary" href="#">Shop now</a>
<a class="btn btn-secondary" href="#">This week's picks</a>
</div>
</div>
<div class="hero__panel-art">
<div class="hero__frame">
<img class="hero__frame-cover" src="images/bruce-springsteen-born-in-the-usa.jpeg" alt="Born in the U.S.A. album cover" loading="lazy">
</div>
</div>
</div>
</section>
That <h1> is the only one on the whole page — every
page needs exactly one, and it's what identifies this as
the heading for the document. Its markup is more elaborate than
plain text for a reason: .hero__punct colors just the comma
a lighter weight, and .hero__band wraps "digital
ease." in a solid highlight — both are pure visual flourish, so
they're <span>s inside the heading, not separate
elements that would confuse a screen reader's reading of it as one
sentence.
Now styles/hero.css:
/* — hero (Split panel) — */
.hero { border-bottom: 2px solid var(--color-divider); }
.hero__split {
max-width: 1200px; margin: 0 auto;
display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.hero__panel-text { padding: 4.5rem 2rem; border-right: 2px solid var(--color-divider); }
.hero__headline {
display: grid; grid-template-columns: auto 1fr; gap: 0 1.375rem; margin: 0 0 1.25rem;
}
.hero__headline-rule { width: 2px; background: var(--color-accent); }
.hero__title {
font-size: clamp(2.375rem, 6vw, 4.625rem); line-height: 1.12; margin: 0; letter-spacing: -0.03em;
}
.hero__punct { color: var(--color-accent); font-weight: 400; font-size: 1.2em; line-height: 0; }
.hero__band {
background: var(--color-accent); color: var(--color-bg); padding: 0 0.12em;
box-decoration-break: clone; -webkit-box-decoration-break: clone;
}
.hero__split .hr { margin: 1.625rem 0; }
.hero__copy { margin: 0 0 1.625rem; max-width: 40ch; font-size: 0.9375rem; }
.hero__actions { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.hero__actions .btn { font-size: 0.9375rem; }
.hero__actions .btn-primary { padding: 0.75rem 1.625rem; }
.hero__actions .btn-secondary { padding: 0.75rem 1.375rem; }
.hero__panel-art {
padding: 4.5rem 2rem; display: grid; place-items: center; background: var(--color-neutral-200);
}
.hero__frame {
position: relative; width: 100%; max-width: 380px; aspect-ratio: 1;
border: 2px solid var(--color-divider); overflow: hidden; background: var(--color-bg);
}
.hero__frame-cover {
position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
transition: transform .4s ease;
}
.hero__frame:hover .hero__frame-cover { transform: scale(1.04); }
/* — responsive — */
@media (max-width: 768px) {
.hero__panel-text, .hero__panel-art { padding: 2.75rem 1.25rem; }
}
@media (max-width: 680px) {
.hero__panel-text { border-right: 0; border-bottom: 2px solid var(--color-divider); }
}
Two patterns worth understanding here, because they repeat in every section that follows:
-
The image-fill trick.
.hero__frameisposition: relativewith a fixedaspect-ratio: 1;.hero__frame-cover(the<img>) isposition: absolute; inset: 0withobject-fit: cover. That combination makes any source photo — whatever its real dimensions — fill the square frame exactly, cropped rather than stretched. -
The two-breakpoint responsive fix. At 768px the two panels stack, but a lone
border-righton the left panel would then draw a stray vertical line down the middle of a single-column layout. The second, narrower@media (max-width: 680px)block catches that and swaps it for aborder-bottominstead — the border always describes the boundary between the two panels, whichever direction they're currently stacked.
Commit:
git add index.html styles/hero.css
git commit -m "feat: hero section"
Step 6 — Featured pressings
Add
<link rel="stylesheet"
href="styles/featured.css">
to the <head>, after hero.css. Then add
this right after </section> that closes the hero:
<section class="featured">
<div class="featured__inner">
<div class="featured__head">
<h2 class="featured__title">Featured pressings</h2>
<div class="featured__nav">
<button class="btn btn-secondary btn-icon" aria-label="Previous"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m14 6-6 6 6 6"></path></svg></button>
<button class="btn btn-secondary btn-icon" aria-label="Next"><svg width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m10 6 6 6-6 6"></path></svg></button>
</div>
</div>
<div class="hr"></div>
<div class="featured__grid">
<article class="feature-card">
<div class="feature-card__art">
<img class="feature-card__cover" src="images/pink-floyd-dark-side-of-the-moon.jpeg" alt="The Dark Side of the Moon album cover" loading="lazy">
</div>
<div>
<span class="feature-card__artist">Pink Floyd</span>
<h3 class="feature-card__title">The Dark Side of the Moon</h3>
<div class="feature-card__meta-row">
<span class="tag tag-outline">Prog rock</span>
<span class="feature-card__meta">LP · 1973</span>
<span class="feature-card__price">$42</span>
</div>
</div>
</article>
<article class="feature-card">
<div class="feature-card__art">
<img class="feature-card__cover" src="images/radiohead-ok-computer.jpeg" alt="OK Computer album cover" loading="lazy">
</div>
<div>
<span class="feature-card__artist">Radiohead</span>
<h3 class="feature-card__title">OK Computer</h3>
<div class="feature-card__meta-row">
<span class="tag tag-outline">Alt rock</span>
<span class="feature-card__meta">LP · 1997</span>
<span class="feature-card__price">$38</span>
</div>
</div>
</article>
</div>
</div>
</section>
Each card title is <h3> — not
<h4>, even though it's visually smaller than you
might expect. The page's heading order so far is
h1 (hero) → h2 ("Featured
pressings") → h3 (each card). Levels only ever step
down by one; jumping straight to h4 here would skip a level
for no reason other than "it looked about that size," which is
exactly the mistake to avoid — font size is CSS's job, heading level
is structure's job, and they're allowed to disagree.
styles/featured.css:
/* — featured pressings — */
.featured { border-bottom: 2px solid var(--color-divider); }
.featured__inner { max-width: 1200px; margin: 0 auto; padding: 3.5rem 2rem; }
.featured__head {
display: flex; align-items: flex-end; justify-content: space-between;
gap: 1.25rem; flex-wrap: wrap; margin-bottom: 0.75rem;
}
.featured__title { font-size: clamp(1.625rem, 3.4vw, 2.5rem); margin: 0; letter-spacing: -0.02em; }
.featured__nav { display: flex; gap: 0.5rem; }
.featured .hr { margin-bottom: 0; }
.featured__grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.feature-card {
display: grid; grid-template-columns: 130px 1fr; gap: 1.375rem; align-items: start;
}
.feature-card:first-child { padding: 2rem 2rem 0 0; border-right: 2px solid var(--color-divider); }
.feature-card:last-child { padding: 2rem 0 0 2rem; }
.feature-card__art {
position: relative; aspect-ratio: 1; background: var(--color-neutral-200); overflow: hidden;
}
.feature-card__cover {
position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
transition: transform .4s ease;
}
.feature-card:hover .feature-card__cover { transform: scale(1.05); }
.feature-card__artist { font-size: 0.6875rem; letter-spacing: .1em; text-transform: uppercase; color: var(--color-accent-700); }
.feature-card__title { margin: 0.25rem 0 0.75rem; font-size: 1.5rem; letter-spacing: -0.01em; }
.feature-card__meta-row { display: flex; align-items: center; gap: 0.75rem; flex-wrap: wrap; }
.feature-card__meta { font-size: 0.75rem; letter-spacing: .06em; text-transform: uppercase; }
.feature-card__price { margin-left: auto; font-family: var(--font-heading); font-weight: 700; font-size: 1.25rem; }
/* — responsive — */
@media (max-width: 768px) {
.featured__inner { padding: 2.75rem 1.25rem; }
}
@media (max-width: 680px) {
.feature-card:first-child {
padding: 2rem 1.25rem 1.5rem; border-right: 0; border-bottom: 2px solid var(--color-divider);
}
.feature-card:last-child { padding: 1.5rem 1.25rem 2rem; }
}
:first-child and :last-child give the two
cards their asymmetric padding (a gap between them, flush against the
section's own edges on the outside) without needing extra classes —
and the same 680px stacking fix from Step 5 shows up again, for the same
reason.
git add index.html styles/featured.css
git commit -m "feat: featured pressings section"
Step 7 — Newsletter
Add
<link rel="stylesheet"
href="styles/newsletter.css">
to the <head>, after featured.css. Then,
right after the featured section's closing
</section>:
<section class="newsletter">
<div class="newsletter__inner">
<div>
<h2 class="newsletter__title">The Friday list</h2>
<p class="newsletter__copy">One email a week: what came in, what's left, and the three records we'd take home ourselves.</p>
</div>
<form class="newsletter__form">
<label class="visually-hidden" for="newsletter-email">Email address</label>
<input id="newsletter-email" name="email" type="email" class="input newsletter__input" placeholder="Email address" required>
<button class="btn btn-primary newsletter__submit" type="submit">Subscribe</button>
</form>
</div>
</section>
The <label class="visually-hidden"> is
worth pausing on: the input already has a placeholder, so
why add a label too? Because a placeholder isn't a real accessible
name — it disappears the moment you start typing, and some assistive
tech never reads it as a label in the first place.
.visually-hidden (defined back in
design-system.css, Step 1) hides it visually without hiding
it from screen readers: sighted users see the placeholder text doing the
same job, screen reader users get a real, permanent "Email
address" label. This form doesn't actually submit anywhere in
this project — that's fine, it's a mockup — but the markup is
written as if it will.
styles/newsletter.css:
/* — newsletter — */
.newsletter { border-bottom: 2px solid var(--color-divider); }
.newsletter__inner {
max-width: 1200px; margin: 0 auto; padding: 2.75rem 2rem;
display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 2.25rem; align-items: center;
}
.newsletter__title { font-size: clamp(1.5rem, 3vw, 2.125rem); margin: 0 0 0.5rem; letter-spacing: -0.02em; }
.newsletter__copy { margin: 0; max-width: 46ch; font-size: 0.9375rem; }
.newsletter__form { display: flex; gap: 0.75rem; flex-wrap: wrap; }
.newsletter__input { flex: 1 1 200px; min-height: 44px; }
.newsletter__submit { min-height: 44px; padding-inline: 1.5rem; font-size: 0.875rem; }
/* — responsive — */
@media (max-width: 768px) {
.newsletter__inner { padding: 2rem 1.25rem; }
}
This is the shortest section so far, and it's shortest for a reason:
it reuses .input and .btn-primary straight
from Step 1 almost unchanged. That's the design-tokens investment
paying off again — a form that looks consistent with the rest of the
site cost about six lines of section-specific CSS.
git add index.html styles/newsletter.css
git commit -m "feat: newsletter section"
Step 8 — In the racks
Add
<link rel="stylesheet"
href="styles/racks.css">
to the <head>, after newsletter.css.
Then, right after the newsletter section's closing
</section>:
<section class="racks">
<div class="racks__inner">
<div class="racks__head">
<h2 class="racks__title">In the racks</h2>
<div class="racks__filters">
<span class="tag tag-accent">All</span>
<span class="tag tag-outline">Vinyl</span>
<span class="tag tag-outline">CD</span>
<span class="tag tag-outline">Cassette</span>
</div>
</div>
<div class="hr"></div>
<div class="racks__grid">
<article class="record-card">
<div class="record-card__art">
<img class="record-card__cover" src="images/beatles-1967-1970.jpeg" alt="The Beatles 1967-1970 album cover" loading="lazy">
</div>
<div class="record-card__body">
<span class="record-card__artist">The Beatles</span>
<h3 class="record-card__title">1967-1970</h3>
<div class="record-card__meta-row">
<span class="record-card__meta">2×LP · 1973</span>
<span class="record-card__price">$36</span>
</div>
<span class="tag tag-outline record-card__genre">Rock</span>
</div>
</article>
<article class="record-card">
<div class="record-card__art">
<img class="record-card__cover" src="images/frank-ocean-blond.jpg" alt="Frank Ocean Blond album cover" loading="lazy">
</div>
<div class="record-card__body">
<span class="record-card__artist">Frank Ocean</span>
<h3 class="record-card__title">Blond</h3>
<div class="record-card__meta-row">
<span class="record-card__meta">LP · 2016</span>
<span class="record-card__price">$45</span>
</div>
<span class="tag tag-outline record-card__genre">R&B</span>
</div>
</article>
<article class="record-card">
<div class="record-card__art">
<img class="record-card__cover" src="images/tyler-the-creator-igor.jpg" alt="Tyler, The Creator IGOR album cover" loading="lazy">
</div>
<div class="record-card__body">
<span class="record-card__artist">Tyler, The Creator</span>
<h3 class="record-card__title">IGOR</h3>
<div class="record-card__meta-row">
<span class="record-card__meta">LP · 2019</span>
<span class="record-card__price">$34</span>
</div>
<span class="tag tag-outline record-card__genre">Hip-hop</span>
</div>
</article>
<article class="record-card">
<div class="record-card__art">
<img class="record-card__cover" src="images/bon-iver-22-a-million.jpg" alt="Bon Iver 22, A Million album cover" loading="lazy">
</div>
<div class="record-card__body">
<span class="record-card__artist">Bon Iver</span>
<h3 class="record-card__title">22, A Million</h3>
<div class="record-card__meta-row">
<span class="record-card__meta">LP · 2016</span>
<span class="record-card__price">$30</span>
</div>
<span class="tag tag-outline record-card__genre">Indie</span>
</div>
</article>
<article class="record-card">
<div class="record-card__art">
<img class="record-card__cover" src="images/john-mayer-born-and-raised.jpg" alt="John Mayer Born and Raised album cover" loading="lazy">
</div>
<div class="record-card__body">
<span class="record-card__artist">John Mayer</span>
<h3 class="record-card__title">Born & Raised</h3>
<div class="record-card__meta-row">
<span class="record-card__meta">LP · 2012</span>
<span class="record-card__price">$28</span>
</div>
<span class="tag tag-outline record-card__genre">Folk rock</span>
</div>
</article>
<article class="record-card">
<div class="record-card__art">
<img class="record-card__cover" src="images/hozier-unreal-unearth-unending.jpg" alt="Hozier Unreal Unearth: Unending album cover" loading="lazy">
</div>
<div class="record-card__body">
<span class="record-card__artist">Hozier</span>
<h3 class="record-card__title">Unreal Unearth: Unending</h3>
<div class="record-card__meta-row">
<span class="record-card__meta">LP · 2024</span>
<span class="record-card__price">$32</span>
</div>
<span class="tag tag-outline record-card__genre">Alt folk</span>
</div>
</article>
<article class="record-card">
<div class="record-card__art">
<img class="record-card__cover" src="images/frank-sinatra-nothing-but-the-best.jpeg" alt="Frank Sinatra Nothing But the Best album cover" loading="lazy">
</div>
<div class="record-card__body">
<span class="record-card__artist">Frank Sinatra</span>
<h3 class="record-card__title">Nothing but the Best</h3>
<div class="record-card__meta-row">
<span class="record-card__meta">LP · 2008</span>
<span class="record-card__price">$27</span>
</div>
<span class="tag tag-outline record-card__genre">Swing</span>
</div>
</article>
<article class="record-card">
<div class="record-card__art">
<img class="record-card__cover" src="images/ralphie-choo-damor-traficante.jpeg" alt="Ralphie Choo D'Amor Traficante album cover" loading="lazy">
</div>
<div class="record-card__body">
<span class="record-card__artist">Ralphie Choo</span>
<h3 class="record-card__title">D'Amor Traficante</h3>
<div class="record-card__meta-row">
<span class="record-card__meta">LP</span>
<span class="record-card__price">$33</span>
</div>
<span class="tag tag-outline record-card__genre">Alt pop</span>
</div>
</article>
</div>
</div>
</section>
Eight cards, one repeated shape — type the first one carefully, then
it's mechanical copy-and-edit for the rest (artist, title, format,
year, price, genre, image path, alt text). Same
<h3> rule as Step 6: these titles sit under the same
<h2>In the racks</h2>, so they're
<h3>, not <h4>.
styles/racks.css:
/* — in the racks — */
.racks { border-bottom: 2px solid var(--color-divider); }
.racks__inner { max-width: 1200px; margin: 0 auto; padding: 3.5rem 2rem; }
.racks__head {
display: flex; align-items: flex-end; justify-content: space-between;
gap: 1.25rem; flex-wrap: wrap; margin-bottom: 0.75rem;
}
.racks__title { font-size: clamp(1.625rem, 3.4vw, 2.5rem); margin: 0; letter-spacing: -0.02em; }
.racks__filters { display: flex; gap: 0.5rem; flex-wrap: wrap; }
.racks .hr { margin-bottom: 0; }
.racks__grid {
display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
border-left: 2px solid var(--color-divider);
}
.record-card {
padding: 1.5rem;
border-right: 2px solid var(--color-divider); border-bottom: 2px solid var(--color-divider);
}
.record-card__art {
position: relative; aspect-ratio: 1; background: var(--color-neutral-200); overflow: hidden;
}
.record-card__cover {
position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
transition: transform .35s ease;
}
.record-card:hover .record-card__cover { transform: scale(1.06); }
.record-card__artist {
display: block; margin-top: 1.125rem; font-size: 0.65625rem;
letter-spacing: .11em; text-transform: uppercase; color: var(--color-accent-700);
}
.record-card__title { margin: 0.3125rem 0 0.75rem; font-size: 1.1875rem; letter-spacing: -0.01em; }
.record-card__meta-row { display: flex; align-items: baseline; gap: 0.625rem; }
.record-card__meta { font-size: 0.71875rem; letter-spacing: .06em; text-transform: uppercase; }
.record-card__price { margin-left: auto; font-family: var(--font-heading); font-weight: 700; font-size: 1.125rem; }
.record-card__genre { margin-top: 0.875rem; }
/* — responsive — */
@media (max-width: 768px) {
.racks__inner { padding: 2.75rem 1.25rem; }
.racks__grid { grid-template-columns: repeat(2, 1fr); }
}
@media (max-width: 480px) {
.racks__grid { grid-template-columns: 1fr; }
}
Notice padding: 1.5rem lives on
.record-card itself, not on a sub-element — that's
deliberate: the cover image and the caption text below it need to share
one padded box so the whole card reads as a single, evenly-inset cell.
Putting the padding on the wrong element here is an easy, purely-visual
mistake (the image would bleed to the card's edge while the text
stayed inset) that nothing will error on — it'll just look subtly
wrong, which is exactly why it's worth typing this one carefully and
comparing against a live preview.
Also notice the ruled-grid trick: the grid container gets a
border-left, and every card gets its own
border-right + border-bottom — no
:last-child exceptions needed. That works at any column
count the responsive rules produce (3 across, 2 across, 1 across), which
is more robust than the :first-child/:last-child
approach Step 6 used for exactly two cards.
git add index.html styles/racks.css
git commit -m "feat: in the racks section"
Step 9 — About, footer, and closing the document
Last one. Add
<link rel="stylesheet"
href="styles/about.css">
and
<link rel="stylesheet"
href="styles/footer.css">
to the <head>, after racks.css. Then,
right after the racks section's closing
</section>, add the about section, the footer,
and the closing tags that finish the document — this is
the step where the file finally becomes fully valid HTML:
<section class="about">
<div class="about__inner">
<div>
<h2 class="about__title">About the shop</h2>
<p class="about__copy">Pressing Room started as two crates behind a coffee counter on Harrison Street. We now grade, clean and ship about four hundred records a week, and we still play every one before it goes out.</p>
<a class="btn btn-secondary" href="#">Read our story</a>
</div>
<div class="about__stats">
<div class="about__stat">
<span class="about__stat-value">14,200</span>
<span class="about__stat-label">records on the shelves</span>
</div>
<div class="about__stat">
<span class="about__stat-value">2009</span>
<span class="about__stat-label">opened on Harrison St</span>
</div>
<div class="about__stat">
<span class="about__stat-value">1 day</span>
<span class="about__stat-label">order to doorstep</span>
</div>
</div>
</div>
</section>
<footer class="site-footer">
<div class="site-footer__grid">
<div>
<div class="site-footer__brand-row">
<img class="site-footer__icon" src="assets/logo-mark-reversed.svg" alt="" width="26" height="26">
<span class="site-footer__brand-name">Pressing Room</span>
</div>
<p class="site-footer__address">1717 Harrison St, San Francisco<br>CA 94103, USA</p>
</div>
<nav class="site-footer__col" aria-label="Main menu">
<h3 class="heading-label">Main menu</h3>
<a href="#">Home</a>
<a href="#">About</a>
<a href="#">Shop</a>
<a href="#">Help</a>
</nav>
<nav class="site-footer__col" aria-label="Company">
<h3 class="heading-label">Company</h3>
<a href="#">The company</a>
<a href="#">Careers</a>
<a href="#">Press</a>
</nav>
<nav class="site-footer__col" aria-label="Discover">
<h3 class="heading-label">Discover</h3>
<a href="#">Grading guide</a>
<a href="#">Our history</a>
<a href="#">Trade-ins</a>
</nav>
<nav class="site-footer__col" aria-label="Find us on">
<h3 class="heading-label">Find us on</h3>
<a href="#">Instagram</a>
<a href="#">Discogs</a>
<a href="#">Bandcamp</a>
</nav>
</div>
<div class="site-footer__bottom">
<span>© 2026 Pressing Room</span>
<span class="site-footer__bottom-spacer">Shipping / Returns / Privacy</span>
</div>
</footer>
</div>
</body>
</html>
Two small, deliberate choices here:
-
The footer uses
assets/logo-mark-reversed.svg, not the samelogo-mark.svgfrom the header — the footer's background is dark, so it needs the color-inverted mark from Step 2, not the one built for a light background. -
Every footer link column is a
<nav>, each with its ownaria-label("Main menu", "Company"…). A page can have several<nav>regions; without distinct labels, a screen reader user jumping between navigation landmarks would hear "navigation, navigation, navigation" with no way to tell them apart.
styles/about.css:
/* — about — */
.about { border-bottom: 2px solid var(--color-divider); }
.about__inner {
max-width: 1200px; margin: 0 auto; padding: 3.75rem 2rem;
display: grid; grid-template-columns: repeat(auto-fit, minmax(290px, 1fr)); gap: 2.75rem;
}
.about__title { font-size: clamp(1.625rem, 3.6vw, 2.625rem); margin: 0 0 1rem; letter-spacing: -0.02em; }
.about__copy { max-width: 52ch; font-size: 0.9375rem; margin: 0 0 1.375rem; }
.about__stats { display: grid; gap: 0; align-content: start; }
.about__stat {
display: flex; align-items: baseline; gap: 1.25rem; padding: 0.875rem 0;
border-top: 2px solid var(--color-divider);
}
.about__stat:last-child { border-bottom: 2px solid var(--color-divider); }
.about__stat-value {
font-family: var(--font-heading); font-weight: 700; font-size: 2.125rem;
color: var(--color-accent-700); min-width: 8.125rem;
}
.about__stat-label { font-size: 0.75rem; letter-spacing: .06em; text-transform: uppercase; }
/* — responsive — */
@media (max-width: 768px) {
.about__inner { padding: 2.5rem 1.25rem; }
.about__stat-value { min-width: 5.5rem; }
}
styles/footer.css:
/* — footer — */
.site-footer { background: var(--color-text); color: var(--color-neutral-200); padding: 3.5rem 2rem 1.375rem; }
.site-footer__grid {
max-width: 1200px; margin: 0 auto;
display: grid; grid-template-columns: minmax(200px, 1.3fr) repeat(auto-fit, minmax(130px, 1fr)); gap: 2.5rem;
}
.site-footer__brand-row { display: flex; align-items: center; gap: 0.625rem; }
.site-footer__icon { width: 26px; height: 26px; flex: none; display: block; }
.site-footer__brand-name {
font-family: var(--font-heading); font-weight: 700; font-size: 1.0625rem;
text-transform: uppercase; color: var(--color-neutral-100);
}
.site-footer__address { margin: 1.25rem 0 0; font-size: 0.8125rem; line-height: 1.7; }
.site-footer__col { display: flex; flex-direction: column; gap: 0.5625rem; }
.site-footer__col .heading-label { margin: 0 0 0.375rem; color: var(--color-accent-300); }
.site-footer__col a { color: inherit; font-size: 0.8125rem; }
.site-footer__bottom {
max-width: 1200px; margin: 2.75rem auto 0; padding-top: 1.125rem;
border-top: 2px solid var(--color-neutral-700);
display: flex; gap: 1rem; flex-wrap: wrap; font-size: 0.75rem; letter-spacing: .05em; text-transform: uppercase;
}
.site-footer__bottom-spacer { margin-left: auto; }
/* — responsive — */
@media (max-width: 768px) {
.site-footer { padding: 2.75rem 1.25rem 1.25rem; }
.site-footer__grid { grid-template-columns: 1fr 1fr; gap: 2rem; }
}
@media (max-width: 480px) {
.site-footer__grid { grid-template-columns: 1fr; }
.site-footer__bottom-spacer { margin-left: 0; }
}
git add index.html styles/about.css styles/footer.css
git commit -m "feat: about section and footer, closing out the page"
Wrap-up
Check it properly now, served rather than opened as a bare file (some
things, like relative paths, behave slightly differently over
file://):
python3 -m http.server 8000
# then open http://localhost:8000 in a browser
You should see the full page: sticky header, split-panel hero with the red-band headline, two featured pressings, the newsletter strip, eight records in a bordered grid, the about stats, and the footer — matching the finished site this guide was written from.
git log --oneline should now read as ten honest, in-order
commits:
| # | Commit message |
|---|---|
| 1 | feat: global reset and brand mark SVGs |
| 2 | feat: design-token foundation and base component classes |
| 3 | feat: document shell and header section |
| 4 | feat: add real album cover images |
| 5 | feat: hero section |
| 6 | feat: featured pressings section |
| 7 | feat: newsletter section |
| 8 | feat: in the racks section |
| 9 | feat: about section and footer, closing out the page |
(Nine, not ten — Steps 1 and 2 as written above share one commit each with their own step, and Step 0 had nothing to commit. Your actual count may differ slightly depending on how you split things up, and that's fine — the point isn't hitting an exact number, it's that every commit is one understandable unit of work.)
A few things to try next, now that the whole thing exists:
-
Introduce a small, deliberate bug a few commits back (delete a closing
</div>, say), then usegit bisectto find it — practicing on a bug you planted yourself is much less stressful than the first time you do it for real. -
Push to a remote (
git remote add origin <url>,git push -u origin main) and open a pull request for your next change, even working solo — write the description as if a stranger has to review it. -
Try changing just
styles/design-system.css— swap--color-accent, or set every--radius-*back to something rounded — and watch how far that one file's change reaches across the whole site. That reach is the entire point of Step 1.