15 — About Section
🖼️ Chapter 15 — About Section
The About section is a classic image + text layout — on desktop the two sit side by side, on mobile they stack. It's also where two of the trickiest positioning concepts in the whole project come together: a floating badge that only works because of a position: relative / position: absolute containing-block relationship, and a checklist built entirely from a single ::before pseudo-element.
What we're building
Here's the HTML we're targeting (already written in Chapter 12):
<section class="about" id="about">
<div class="container about-inner">
<!-- LEFT: image block -->
<div class="about-image-wrap reveal">
<div class="about-img">🎨</div>
<div class="about-badge">
<span class="badge-num">8+</span> Years of craft
</div>
</div>
<!-- RIGHT: text block -->
<div class="about-text reveal">
<p class="section-label">Our story</p>
<h2 class="section-title">Crafted with intention</h2>
<p>We've spent eight years turning complex briefs...</p>
<ul class="about-checklist">...</ul>
<a class="btn btn-primary">Meet the team</a>
</div>
</div>
</section>
Two direct children inside .about-inner: the image wrap (left) and the text block (right). Grid will place them automatically.
The CSS — outer section
First we style the .about section itself — background colour and generous vertical padding. We use clamp() so spacing breathes on all screen sizes:
/* ── Step 9 · About ──────────────────────────────── */
.about {
padding: clamp(4rem, 10vw, 7rem) 0;
background: var(--clr-bg); /* white — contrasts with the dark stats bar above */
}
--clr-dark (#0d1117). A white About section creates a strong contrast break that signals a new page "chapter" to the reader.
The CSS — two-column grid
.about-inner gets a two-column grid. Both columns are equal width (1fr 1fr) and vertically centered with align-items: center so the text doesn't float to the top when the image is taller.
.about-inner {
display: grid;
grid-template-columns: 1fr 1fr; /* image | text, equal halves */
gap: clamp(2.5rem, 6vw, 5rem);
align-items: center;
}
/* Mobile: stack columns */
@media (max-width: 899px) {
.about-inner {
grid-template-columns: 1fr; /* image above, text below */
}
}
The gap uses clamp() — wider on large screens where the columns have more room to breathe, narrower on tablet.
Why align-items: center?
Without it, grid children stretch to the tallest cell height by default. If the text column is taller than the image, the image would pin to the top — misaligned. center keeps both columns vertically centered regardless of which side is taller.
gap on the grid container is cleaner than adding margin-right to one column. Gap disappears automatically when the grid collapses to a single column — no media query needed to clear the gap.
Live demo — grid structure
Toggle the grid outlines on to see exactly how the two columns sit. Toggle "Mobile" to watch the single-column stack.
Our story
Crafted with intention
We've spent eight years turning complex briefs into clear, purposeful design — for brands that mean business.
Meet the team.about-image-wrap and .about-img elements. The image wrap becomes position: relative — the foundation the absolute-positioned badge will attach to next.
The key concept: position: relative as a containing block
The most important line in this step is one you won't see change anything visually — yet. Setting position: relative on the image wrap creates a containing block for the badge that's coming next.
When you set position: absolute on a child element, the browser walks up the DOM tree looking for the nearest ancestor that has a position other than static. That ancestor becomes the child's reference frame for top, right, bottom, and left. If there's no positioned ancestor, the badge anchors to the viewport — almost certainly not what we want.
Next, we'll set bottom: -1rem; right: -1rem on this badge — those values will be relative to the image wrap, not the page.
The CSS
/* ── Step 9 · About image wrap ───────────────────── */
.about-image-wrap {
position: relative; /* containing block for the badge */
border-radius: var(--radius);
}
No overflow: hidden here — we need the badge to be able to peek outside the wrap boundary.
.about-img {
aspect-ratio: 4 / 3; /* locks the proportions */
border-radius: var(--radius);
background: var(--clr-bg-alt); /* light grey when no real photo */
overflow: hidden; /* clips a real <img> to the border-radius */
display: flex;
align-items: center;
justify-content: center;
font-size: clamp(3rem, 8vw, 5rem); /* scales the emoji placeholder */
transition: transform 0.4s ease; /* subtle zoom on hover */
}
.about-img:hover {
transform: scale(1.03); /* gentle zoom — image stays clipped */
}
overflow: hidden on .about-img (not the wrap) clips the zoom effect and any real photo to the rounded corners.
Why aspect-ratio: 4 / 3?
Without a fixed height or aspect ratio, a grid cell with no content height would collapse. aspect-ratio tells the browser: "make the height 75% of the width, always." This works for both the emoji placeholder and a real <img> tag — you swap the background for an image later without touching the layout.
.about-img {
background: #f0f0f0;
/* no height set → collapses to 0 */
}
.about-img {
aspect-ratio: 4 / 3;
background: #f0f0f0;
/* height derived from width ✓ */
}
<img src="team.jpg" alt="..."> and add object-fit: cover; width: 100%; height: 100%; to the img. The aspect-ratio and overflow: hidden on the parent handle the rest.
Live demo — image wrap
Hover the image to see the scale transition. Toggle the annotation to label the key CSS properties.
Our story
Crafted with intention
Eight years turning complex briefs into clear, purposeful design — for brands that mean business.
Meet the teamWhat overflow: hidden does — and where to put it
This is a subtle but important detail that trips up beginners:
.about-imggetsoverflow: hidden— this clips the zoom effect and a real photo to the rounded corners of the image itself..about-image-wrapdoes NOT getoverflow: hidden— if it did, the badge we're adding next (which will peek outside the wrap boundary) would be clipped and invisible.
overflow: hidden to the wrap to "fix" the border-radius — then wondering why the absolutely-positioned badge disappears. Clip on the image, not the container.
Part 1 — The floating badge
The badge is a small dark card that hovers at the bottom-right corner of the image, partially overlapping it. It uses position: absolute anchored to the position: relative wrap we set above.
How the coordinates work
With bottom: -1.1rem and right: -1.1rem, the badge starts below and to the right of the wrap's boundary — the negative values pull it outside the box. This creates the overlapping "sticker" effect.
/* ── About badge — anchored to image wrap ─────── */
.about-badge {
position: absolute;
bottom: -1.1rem; /* pull below the image wrap */
right: -1.1rem; /* pull right of the image wrap */
background: var(--clr-dark); /* #0d1117 — dark card */
color: var(--clr-bg);
border-radius: var(--radius);
padding: 0.75rem 1.1rem;
display: flex;
align-items: center;
gap: 0.5rem;
font-weight: 600;
font-size: 0.9rem;
white-space: nowrap; /* never wrap to two lines */
box-shadow: 0 8px 28px rgba(0,0,0,0.2);
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.about-badge:hover {
transform: translateY(-3px);
box-shadow: 0 14px 36px rgba(0,0,0,0.28);
}
.badge-num {
font-size: 1.5rem;
font-weight: 800;
color: var(--clr-accent); /* accent blue for the number */
line-height: 1;
}
.about-image-wrap has position: relative. Without that, the badge would anchor to the nearest positioned ancestor further up the tree — likely the whole page — and appear in the wrong place entirely.
Part 2 — The checklist with ::before
The checklist uses list-style: none to remove the default bullet, then adds a custom circular checkmark via the ::before pseudo-element on each li.
.about-checklist {
list-style: none; /* remove default bullet */
padding: 0;
margin: 1rem 0;
display: flex;
flex-direction: column;
gap: 0.6rem;
}
.about-checklist li {
display: flex;
align-items: center;
gap: 0.75rem;
font-size: 0.95rem;
color: var(--clr-text);
}
.about-checklist li::before {
content: '✓'; /* the checkmark character */
display: inline-flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
border-radius: 50%;
background: rgba(79,142,247,0.12); /* tinted blue circle */
color: var(--clr-accent);
font-size: 0.72rem;
font-weight: 900;
flex-shrink: 0; /* never squish the circle */
transition: background 0.2s, color 0.2s, transform 0.2s;
}
.about-checklist li:hover::before {
background: var(--clr-accent); /* solid on hover */
color: #fff;
transform: scale(1.15);
}
flex-shrink: 0 on the ::before stops the circle collapsing if the text beside it is long.
display: inline-flex on a pseudo-element? ::before is inline by default, so width and height have no effect. Switching it to inline-flex (or flex) makes it a proper box you can size and centre content inside.
Live demo — complete About section
The full section with grid, image, badge, text, and checklist all together. Hover the badge and checklist items to see transitions.
Our story
Crafted with intention
Eight years turning complex briefs into clear, purposeful design — for brands that mean business.
- Strategy-first approach to every project
- Pixel-perfect execution, on time
- Transparent process, zero surprises
Step 9 — complete
- Grid skeleton — 2-col, centered, fluid gap, mobile stack ✓
- Image wrap —
position: relative, aspect ratio, hover zoom ✓ - Badge —
position: absolutewith negative offsets, hover lift ✓ - Checklist —
::beforecircle checkmarks, hover fill ✓
auto-fit grid of cards, each with a star rating, blockquote, and avatar.