GODRICH

Maximalist Web Design: 9 Collage Landing Sections

Maximalist web design fills the screen instead of emptying it, stacking color fields, oversized type, and stickers until the page feels full.

Boards full of these screens are easy to save and hard to start. The problem is never the number of elements — it's that nothing decides how they overlap. Scroll the one-pager below and count how many things were actually chosen by hand. Three.

Open the full page

The grid below pulls that page apart into its nine parts. The order isn't by popularity — it's the order you'd actually assemble a collage in: lay the frames, add the index, arrange the clippings, press the stamps, wrap the bands, draw the underline, stack the cards, cut the edge, and sign the bottom.

Auto-plays · click a tile to jump to its section · all nine in one zip

01Stacked frame hero

Three gradient-filled photo frames sway out of phase with one another, and only the title card pasted over them rises. Move the front and the back the same way and the whole stack flattens into one plane.

z-index 겹침반대 위상radius 4
.hcol__frame {
  border: 6px solid #fff;            /* the white margin does the photo-border job */
  animation-name: frameSway;
  animation-duration: 2s;
  animation-iteration-count: infinite;
}
.hcol__frame--sky  { --rot: 8deg;  animation-delay: -.7s; }
.hcol__frame--lime { --rot: -5deg; animation-delay: -1.4s; }

@keyframes frameSway {
  0%, 100% { transform: translateY(0) rotate(var(--rot)); }
  50%      { transform: translateY(8px) rotate(calc(var(--rot) + 5deg)); }
}

02Magazine index nav

Put a two-digit page number above every menu item and the bar turns into a table of contents. The lemon chip sits on a tab for a beat before jumping to the next one — without that pause, nothing tells you which tab you're on.

고정 폭 탭쪽번호 세리프radius 4
.ncol { --tab: 68px; --step: 72px; }   /* measured: 68px tab + 4px gap */
.ncol__tab  { flex: 0 0 var(--tab); }  /* equal-share flex would break the hop distance */

@keyframes chipHop {
  0%, 10%      { transform: translateX(0) rotate(-3deg); }
  13.3%        { transform: translateX(calc(var(--step) * .5)) translateY(-11px) rotate(5deg); }
  16.6%, 26.6% { transform: translateX(var(--step)) rotate(-3deg); }
  50%, 60%     { transform: translateX(calc(var(--step) * 3)) rotate(-3deg); }
  100%         { transform: translateX(0) rotate(-3deg); }
}

03Cut-out frame card grid

Each card straightens from its pasted angle to zero and tilts back. Offsetting them by 0.24s is what keeps the row reading as three separate clippings rather than one block.

시차 0.24s각도 복원radius 4
.ccol__card--coral { --rot: -7deg; }
.ccol__card--sky   { --rot: 5deg;  animation-delay: -.24s; }
.ccol__card--grape { --rot: -4deg; animation-delay: -.48s; }

@keyframes cardStraighten {
  0%, 100% { transform: rotate(var(--rot)); box-shadow: 0 5px 0 rgba(36, 29, 46, .16); }
  45%      { transform: rotate(0deg) translateY(-8px); box-shadow: 0 13px 0 rgba(36, 29, 46, .12); }
}

04Rotated stamp badges

One dashed border is enough to turn a circle into a rubber stamp. Each badge squashes to 0.55 and springs back to 1.16, and the tilt survives only because rotate is repeated inside every transform value.

scale 0.55→1.16점선 테두리radius 50%
.scol__badge--lemon { --rot: -12deg; background: #ffd93d; }
.scol__badge--lime  { --rot: 8deg;   animation-delay: -.5s; }

@keyframes stampPress {
  0%, 55%, 100% { transform: rotate(var(--rot)) scale(1); }
  8%            { transform: rotate(var(--rot)) scale(.55); }
  20%           { transform: rotate(var(--rot)) scale(1.16); }
  32%           { transform: rotate(var(--rot)) scale(1); }
}

05Marquee text band

Duplicate the word set, slide exactly one set's width, and the end of a lap is pixel-identical to the first frame. Running the two bands in opposite directions makes them read as pushing against each other.

두 벌 복제margin-right 간격translateX(-50%)
.mcol__track {
  display: flex;
  width: max-content;
  animation-name: stripFlow;
  animation-duration: 2s;
  animation-timing-function: linear;
  animation-iteration-count: infinite;
}
.mcol__track--back { animation-name: stripFlowBack; }
.mcol__bit { margin-right: 24px; }     /* a flex gap would throw the -50% off */

@keyframes stripFlow { from { transform: translateX(0); } to { transform: translateX(-50%); } }

06Hand-drawn underline quote

Send stroke-dashoffset from positive to zero and on into negative, and a single SVG path draws from the left, then erases from the left. Nothing ever runs backward, so the loop has no visible restart.

stroke-dashoffsetSVG pathradius 4
.tcol__line {
  fill: none;
  stroke: #d93a1e;
  stroke-width: 4;
  stroke-linecap: round;
  stroke-dasharray: 230;               /* comfortably longer than the measured path */
  animation-name: inkDraw;
  animation-duration: 2s;
  animation-iteration-count: infinite;
}
@keyframes inkDraw {
  0%   { stroke-dashoffset: 230; }
  45%  { stroke-dashoffset: 0; }
  70%  { stroke-dashoffset: 0; }
  100% { stroke-dashoffset: -230; }
}

07Overlapping pricing layers

A negative margin overlaps the two cards and z-index settles the order. When the front card lifts, its shadow has to grow from 5px to 17px or the stack stops reading as layers.

음수 margin 겹침그림자 확장radius 4
.pcol__plan--base { z-index: 1; transform: rotate(-3deg); }
.pcol__plan--pick {
  z-index: 2;
  margin-left: -20px;                  /* the front card covers the back card's corner */
  margin-bottom: 16px;
}
@keyframes planLift {
  0%, 100% { transform: rotate(2deg) translateY(0);     box-shadow: 0 5px 0 rgba(36, 29, 46, .16); }
  50%      { transform: rotate(2deg) translateY(-12px); box-shadow: 0 17px 0 rgba(36, 29, 46, .12); }
}

08Stamp CTA band

The bottom edge is scalloped out with two mask layers, and which half of the circle you keep decides everything. The button tilts as it presses, and its shadow thins from 14px to 2px on the way down.

radial-gradient 마스크그림자=두께radius 4
.bcol {
  mask:
    radial-gradient(circle 8px at 50% 0, #000 98%, #0000 100%) 0 100% / 16px 8px repeat-x,
    linear-gradient(#000 0 0) 0 0 / 100% calc(100% - 8px) no-repeat;
}
@keyframes stampPush {
  0%, 100% { transform: translateY(-6px) rotate(-2deg); box-shadow: 0 14px 0 rgba(36, 29, 46, .35); }
  50%      { transform: translateY(0) rotate(1.5deg);   box-shadow: 0 2px 0 rgba(36, 29, 46, .35); }
}

09Collage footer

Let a huge serif word run off the edge and the eye stops reading it as text and starts reading it as a surface. The four chips in front bob half a second apart, flipping the sign of their tilt on the way up.

배경 워드마크시차 0.5sradius 4
.fcol__word {
  position: absolute;
  left: -12px; bottom: -16px;
  color: rgba(36, 29, 46, .1);         /* 10% keeps it a surface, not a headline */
  font-size: 62px;
  white-space: nowrap;
}
@keyframes chipBob {
  0%, 100% { transform: translateY(0) rotate(var(--rot)); }
  50%      { transform: translateY(-9px) rotate(calc(var(--rot) * -1)); }
}

Custom tailwind.config

The value worth centralizing here isn't color — it's angle. Pick a fresh tilt for every element and the overlap reads as a mistake instead of a decision, so the config turns four angles into named utilities and stops there.

theme: {
  extend: {
    colors: {
      cream: "#fbf3e4", sheet: "#ffffff", ink: "#241d2e", slate: "#57506a",
      coral: "#d93a1e", sky: "#2d6fb5", grape: "#6b3fa0",
      lemon: "#ffd93d", lime: "#b6e34f", blush: "#ffb3c7",
    },
    borderRadius: { chip: "4px", control: "12px", card: "16px", pill: "999px" },
    boxShadow: {
      paste: "0 5px 0 rgba(36,29,46,.16)",   /* resting on the surface = thickness */
      stamp: "0 4px 0 rgba(36,29,46,.2)",
      lift:  "0 16px 0 rgba(36,29,46,.12)",  /* only while it's raised */
    },
    rotate: { a: "-3deg", b: "2deg", c: "-1.5deg", d: "5deg" },
  },
},
Role Token When
Pasted angle rotate-a through rotate-d Frames, cards, badges, bands — all of them
Resting thickness shadow-paste Anything sitting on the surface
Raised shadow-lift Only during the lift

Vibe-coding prompt

With the config in place, one block of instructions is all your AI editor needs. Putting the angle list ahead of the color list is the part that matters. The full, untrimmed text ships as prompt.md inside the zip.

Use exactly four angles: -3deg, 2deg, -1.5deg, 5deg. Never invent one outside that list.
Anything resting on the surface gets a zero-blur shadow, 0 5px 0 rgba(36,29,46,.16);
swap it for 0 16px 0 only while the element is raised. One background tone,
cream (#fbf3e4); one surface tone, sheet (#ffffff). White text goes on coral, sky, and
grape only; lemon, lime, and blush carry ink text only. Use no photographs — a photo slot
is a two-stop linear-gradient inside a 6px white border.

The screenshot below is the generated page/index.html, captured as a single full-page shot at a 1280px viewport.

Result

Where it breaks (the trap)

The edge of the CTA band cost the most time. To scallop the bottom I laid down a radial-gradient mask, and the first version — circle 8px at 50% 0, #0000 98%, #000 100% — kept the outside of each circle, hanging a row of sharp downward triangles off the band. The poster made it obvious: those were teeth, not waves. Flipping the color stops to #000 98%, #0000 100% keeps the inside instead, and the half-circles finally dangled the right way. Read a mask as "what stays," never as "what gets removed."

The marquee was the second trap. I tilted the two bands -2.5 and 2 degrees and overlapped them with a negative margin-top, and the render came back with the lower band sitting across the baseline of the upper band's letters. A rotated element occupies more vertical space than its pre-rotation height, so an overlap computed from the unrotated box doesn't hold. Narrowing the lower band to 88% and pushing it right, so only the band edges meet, made both rows readable again. While I was in there I re-measured the track with Playwright's offsetWidth; getBoundingClientRect reports the post-rotation box and is useless for this. Upper track 796px against a 398px set, lower track 524px against 262px — both exactly half, so one lap of translateX(-50%) leaves a 0px seam. The files with both traps fixed are inside the zip, which opens with the password s3yjtjja.

Accessibility and reduced-motion

The more colors a palette carries, the less you can judge contrast by eye. All ten were run through the relative-luminance formula from the WCAG 2.1 minimum contrast requirement. Body text needs 4.5:1.

Color Value On cream On white With ink text on it
Ink #241d2e 14.76:1 16.28:1
Slate #57506a 6.90:1 7.61:1
Coral #d93a1e 4.16:1 4.59:1 3.55:1
Sky #2d6fb5 4.70:1 5.18:1 3.14:1
Grape #6b3fa0 6.70:1 7.38:1 2.20:1
Lemon #ffd93d 1.25:1 1.38:1 11.82:1
Lime #b6e34f 1.35:1 1.49:1 10.92:1
Blush #ffb3c7 1.52:1 1.68:1 9.71:1

Two rules fall out of that table. Coral clears the bar as text on white at 4.59:1 but lands at 4.16:1 on the cream background, so every coral word across the nine parts lives inside a white card. Lemon, lime, and blush go the other way: unusable as text anywhere, yet over 9:1 once ink sits on them, which is why they only ever appear as the surface of a badge or a band.

For motion, the media query from MDN's prefers-reduced-motion page is in all nine stylesheets. With reduce turned on, the swaying and the scrolling stop, but the pasted angles stay. The tilt built with rotate from MDN's transform page is the identity of this style, and zeroing it out would hand the visitor a different design rather than a calmer one.

For a lighter take on the same bright palette, the paper cutout landing kit is the closest neighbor, and the about page explains what this site ships with every post.

FAQ

Won't six colors on one screen look messy?

Messiness comes from overlapping roles, not from the count. Coral, sky, and grape are text-bearing surfaces here; lemon, lime, and blush never carry text. Once the roles are split, the eye groups six colors into two families and stops counting.

Why cap the angles at four?

Because tilt is a list, not a value. Hand out -2, -7, and 3 degrees element by element and a reader files it under "misaligned" rather than "deliberately tilted." Four angles in rotation look like one hand pasted everything down.

Do the frames look empty without real photos?

They would if you left the slot blank, but a two-stop gradient inside a 6px white border reads as something being framed. Dropping in a real image later means changing background and nothing else, so nobody has to wait for asset delivery to review the layout.

Enter the archive password

The password is inside this article. You will find it as you read.