Build Your Portfolio
Beginner

Card Details: Images, Labels, Little Touches

The layout is right; now each card needs to feel finished. Crop images without stretching them, lay readable labels over pictures, align numbers so they don't jiggle, and size icons properly. Small rules, big difference.

Card Details: Images, Labels, Little Touches

There's a moment in every project where the layout is right but the page still looks like a draft. The image is squashed. The white label vanishes against a white background. The clock's digits shuffle sideways every second.

None of these is a big problem. Each takes two or three lines to fix. But together they're the difference between "a student project" and "wait, you made this?" This lesson goes card by card.

The About card

/* ---------- 6. About ---------- */
.about {
  padding: 20px 24px;
  border: 2px solid var(--blue);
  display: flex;
  flex-direction: column;
}

.about-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding-bottom: 10px;
  border-bottom: 2px solid var(--blue);
  margin-bottom: 12px;
}

.about-body {
  display: flex;
  align-items: center;
  gap: 12px;
  flex: 1;
  min-height: 0;
}

.about-body img {
  width: 88px;
  height: auto;
  flex-shrink: 0;
}

Two things worth a closer look:

  • align-items: baseline on the heading row lines up "About Her" and "について" on the bottom of their letters, even though one is much bigger than the other. center would make the small text float halfway up the big one.
  • flex: 1; min-height: 0 lets the body fill the rest of the card and shrink if the card is short. Without min-height: 0, a flex child refuses to get smaller than its content and spills out of the card.

Image cards: three layers

Each look card is a picture with a label floating over a soft blue fade:

Three panels side by side: the image filling the card, a gradient from transparent to blue over the bottom half, and the white label Hoodie Ver, combined with plus signs
Picture, fade, label. Three layers make text readable on any image.

Layer 1: the image, cropped to fit.

.look {
  overflow: hidden;
  text-decoration: none;
  background: var(--blue-soft);
}

.look img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: top;
  transition: transform 400ms ease;
}

The card is a fixed shape, but the images aren't. A square picture in a tall card has to be cropped, stretched, or letterboxed. object-fit chooses:

The same cat-ear portrait three ways: object-fit cover fills the box and crops the edges, contain shows everything with empty bands, fill stretches it out of shape
cover for photos in cards, contain for logos and drawings, fill almost never.

We use cover, plus object-position: top, so if anything gets cropped it's the bottom of the picture, never the character's face. overflow: hidden on the card clips the image to the rounded corners.

Layer 2: the fade.

.look::after {
  content: "";
  position: absolute;
  inset: 50% 0 0;
  background: linear-gradient(to top, var(--blue) 10%, transparent);
}

::after creates an extra, empty element at the end of the card, without touching the HTML. inset: 50% 0 0 is shorthand for top: 50%; right: 0; bottom: 0; left: 0: it covers the bottom half. The gradient goes from solid blue at the bottom to transparent in the middle.

This is the trick to readable text on photos. You can't know what color the image will be behind the label. So you put a color there yourself.

Layer 3: the label.

.look-label {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 14px;
  z-index: 1;
  text-align: center;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--paper);
}

z-index: 1 lifts the label above the ::after fade. A little hover touch, .look:hover img { transform: scale(1.05); }, zooms the picture slightly when you point at it.

The Smool card is a simple line drawing, so cropping would cut it off. It switches to contain:

.look-smool img {
  object-fit: contain;
  background: var(--paper);
}

Birthday and clock

The date card pins its cake and countdown to corners, using the position: relative every card already has:

.date-countdown {
  position: absolute;
  right: 16px;
  bottom: 16px;
  font-size: 0.75rem;
  opacity: 0.9;
}

.date-cake {
  position: absolute;
  top: 14px;
  right: 16px;
  font-size: 2rem;
}

The clock gets one small rule with a big effect:

.clock-time {
  font-size: 2.2rem;
  font-weight: 700;
  color: var(--blue-deep);
  line-height: 1;
  font-variant-numeric: tabular-nums; /* digits don't jiggle as they change */
}

In most fonts a "1" is narrower than an "8". When the clock ticks from 8:11 to 8:18, the whole time shifts sideways. tabular-nums switches to digits that are all the same width, the way they're printed in tables. The clock stays perfectly still as it changes. You'll see it working in the next lesson.


View More, the dock and the credits

The View More card uses flexbox in a column to push the text to the top and the arrow to the bottom:

.more {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  /* …colors and font… */
}

.more-arrow {
  display: grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: var(--paper);
  color: var(--blue);
}

display: grid; place-items: center is the shortest way in CSS to center one thing inside another, horizontally and vertically.

The dock finally fixes the giant YouTube icon:

.dock a {
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border-radius: 50%;
  color: var(--blue-deep);
}

.dock svg {
  width: 22px;
  height: 22px;
  fill: currentColor;
}
  • The link is 44 × 44px, the minimum comfortable tap size. The icon inside is 22px. Same idea as the slider dots: a big target, a small visual.
  • fill: currentColor paints the SVG with the link's text color. Change the link's color and the icon follows, including on hover.

Finally, the credits panel floats above its bar:

.credits-panel {
  position: absolute;
  right: 0;
  bottom: calc(100% + 12px);
  width: min(340px, 90vw);
  /* …white card styles… */
  z-index: 10;
}

bottom: calc(100% + 12px) places the panel's bottom edge 12px above the top of the bar, so it opens upwards. The panel starts with the HTML hidden attribute, so it's invisible until lesson 9's JavaScript opens it.


Checkpoint

Every card styled: blue hero with portrait, About card with sticker, Cat and Hoodie image cards with labels on blue fades, the birthday card, an empty clock showing --:--, Smool Ver, View More with its arrow, the icon dock and the credits bar
Visually complete. The clock still says --:-- because there's no JavaScript yet.

The page now looks like the design. The only giveaway is the clock: it still says --:--, and the birthday card says "Birthday soon". Time to bring them to life.

If something looks wrong

  • A label is invisible. Check it has z-index: 1, and that .look has position: relative (from .card).
  • Images are stretched. You need object-fit: cover and both width: 100% and height: 100% on the image.
  • The images poke out of the rounded corners. Add overflow: hidden to .look.

What to remember

  • object-fit: cover crops images to any shape; object-position picks which part survives.
  • To make text readable on a photo, add your own background under it with a gradient pseudo-element.
  • tabular-nums stops changing numbers from jiggling.
  • place-items: center centers anything in one line.
  • Size the tap target and the visual separately; fill: currentColor makes icons follow the text color.

Next, the first JavaScript: A Live Clock and a Countdown.