Social post
A feed post: the author's photo with a follow button, a photo, like, comment, share and save, a like count that changes, a caption that opens with "more" and the top comment.
@rafal.makeshe/himPhotographer
Chasing blue hour on rooftops. Film on weekends, ceramics on Tuesdays. Prints ship in spring.
It is live: hover, focus and its own controls respond. Actions that would change data (approve, send, delete) are for your app to wire. The width is a real 390 px, 768 px or 1280 px box, shrunk to fit your screen.
<!-- Icons: Lucide (lucide.dev), ISC licence -->
<style>
.odl-profile-hero { box-sizing: border-box; inline-size: 100%; max-inline-size: 44rem; margin-inline: auto; container-type: inline-size; color: var(--ds-color-text-primary); font: var(--ds-text-body-md); text-wrap: pretty; }
.odl-profile-hero *, .odl-profile-hero *::before, .odl-profile-hero *::after { box-sizing: border-box; }
.odl-profile-hero .odl-card { --av: calc(var(--ds-size-control-lg) * 1.9); background: var(--ds-color-surface-raised); border: var(--ds-border-width-default) solid var(--ds-color-border-subtle);
border-radius: var(--ds-radius-container); box-shadow: var(--ds-elevation-raised); overflow: hidden; }
.odl-profile-hero .odl-cover { block-size: calc(var(--av) * 1.6); background: var(--ds-color-bg-neutral-subtle); }
.odl-profile-hero .odl-cover img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
.odl-profile-hero .odl-body { position: relative; display: grid; grid-template-columns: minmax(0, 1fr); grid-template-areas: "av" "name" "bio" "counts" "actions" "hl"; gap: var(--ds-space-stack-lg);
padding: 0 var(--ds-space-inset-lg) var(--ds-space-inset-lg); }
.odl-profile-hero .odl-ring { grid-area: av; display: grid; inline-size: var(--av); aspect-ratio: 1; margin-block-start: calc(var(--av) * -0.5); margin-block-end: calc(-1 * var(--ds-space-stack-sm)); padding: var(--ds-border-width-emphasis);
border-radius: var(--ds-radius-person); background: var(--ds-color-bg-accent-bold); }
.odl-profile-hero .odl-gap { display: grid; padding: calc(var(--ds-border-width-emphasis) * 2); border-radius: var(--ds-radius-person); background: var(--ds-color-surface-raised); }
.odl-profile-hero .odl-av { display: block; inline-size: 100%; block-size: auto; aspect-ratio: 1; border-radius: var(--ds-radius-person); object-fit: cover; background: var(--ds-color-bg-neutral-subtle); }
.odl-profile-hero .odl-name { grid-area: name; display: grid; gap: var(--ds-space-stack-xs); }
.odl-profile-hero h2 { margin: 0; font: var(--ds-text-title-lg); }
.odl-profile-hero .odl-handle { display: flex; flex-wrap: wrap; align-items: center; gap: var(--ds-space-stack-xs) var(--ds-space-inline-sm); margin: 0; color: var(--ds-color-text-secondary); font: var(--ds-text-label-lg); }
.odl-profile-hero .odl-tag { padding: var(--ds-space-2) var(--ds-space-8); border-radius: var(--ds-radius-control-sm); background: var(--ds-color-bg-neutral-subtle); color: var(--ds-color-text-secondary); font: var(--ds-text-label-sm); white-space: nowrap; }
.odl-profile-hero .odl-bio { grid-area: bio; display: grid; gap: var(--ds-space-stack-xs); margin: calc(-1 * var(--ds-space-stack-sm)) 0 0; }
.odl-profile-hero .odl-bio p { margin: 0; }
.odl-profile-hero .odl-link { display: inline-flex; align-items: center; gap: var(--ds-space-inline-sm); color: var(--ds-color-text-link); font: var(--ds-text-label-lg); font-weight: var(--ds-font-weight-label); min-block-size: var(--ds-size-target-touch); text-decoration: none; border-radius: var(--ds-radius-control-sm); }
.odl-profile-hero .odl-link:hover { color: var(--ds-color-text-primary); }
.odl-profile-hero .odl-link svg { inline-size: var(--ds-size-icon-sm); block-size: var(--ds-size-icon-sm); stroke-width: var(--ds-icon-stroke-md); }
.odl-profile-hero .odl-counts { grid-area: counts; display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); margin: 0; padding: 0; list-style: none;
border: var(--ds-border-width-default) solid var(--ds-color-border-subtle); border-radius: var(--ds-radius-container); }
.odl-profile-hero .odl-counts li + li { border-inline-start: var(--ds-border-width-default) solid var(--ds-color-border-subtle); }
.odl-profile-hero .odl-count { display: grid; justify-items: center; align-content: center; gap: var(--ds-space-2); inline-size: 100%; min-block-size: var(--ds-size-row-two-line); padding: var(--ds-space-inset-sm); border: 0; background: transparent; color: var(--ds-color-text-secondary);
font: var(--ds-text-label-md); cursor: pointer; border-radius: var(--ds-radius-nested); }
.odl-profile-hero .odl-count:hover { background: var(--ds-color-bg-neutral-subtle); }
.odl-profile-hero .odl-count b { color: var(--ds-color-text-primary); font: var(--ds-text-numeric-md); font-weight: var(--ds-font-weight-heading); font-variant-numeric: tabular-nums; }
.odl-profile-hero .odl-actions { grid-area: actions; display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto; gap: var(--ds-space-inline-sm); }
.odl-profile-hero .odl-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--ds-space-inline-sm); min-inline-size: var(--ds-size-target-touch); min-block-size: max(var(--ds-size-control-md), var(--ds-size-target-touch));
padding: 0 var(--ds-space-inset-md); border: var(--ds-border-width-default) solid transparent; border-radius: var(--ds-radius-control); background: var(--ds-color-bg-neutral-subtle); color: var(--ds-color-text-primary);
font: var(--ds-text-label-lg); font-weight: var(--ds-font-weight-label); white-space: nowrap; cursor: pointer; transition: background-color var(--ds-motion-duration-short) var(--ds-motion-easing-standard); }
.odl-profile-hero .odl-btn:hover { background: var(--ds-color-bg-neutral-subtle-hover); }
.odl-profile-hero .odl-btn svg { flex: none; inline-size: var(--ds-size-icon-sm); block-size: var(--ds-size-icon-sm); stroke-width: var(--ds-icon-stroke-md); }
.odl-profile-hero .odl-btn.is-icon { padding: 0; inline-size: max(var(--ds-size-control-md), var(--ds-size-target-touch)); }
.odl-profile-hero .odl-follow { background: var(--ds-color-bg-accent-bold); color: var(--ds-color-text-on-accent); }
.odl-profile-hero .odl-follow:hover { background: var(--ds-color-bg-accent-bold-hover); }
.odl-profile-hero .odl-follow svg { display: none; }
.odl-profile-hero .odl-follow[aria-pressed="true"] { background: var(--ds-color-bg-neutral-subtle); color: var(--ds-color-text-primary); }
.odl-profile-hero .odl-follow[aria-pressed="true"] svg { display: block; }
.odl-profile-hero .odl-hl { grid-area: hl; display: flex; gap: var(--ds-space-inline-md);
-webkit-mask-image: linear-gradient(to right, var(--ds-color-black) calc(100% - var(--ds-space-32)), transparent); mask-image: linear-gradient(to right, var(--ds-color-black) calc(100% - var(--ds-space-32)), transparent); margin: 0 calc(-1 * var(--ds-space-inset-lg)); padding: var(--ds-space-4) var(--ds-space-inset-lg) 0; list-style: none;
overflow-x: auto; scrollbar-width: none; overscroll-behavior-x: contain; }
.odl-profile-hero .odl-hl::-webkit-scrollbar { display: none; }
.odl-profile-hero .odl-hl button { flex: none; display: grid; justify-items: center; gap: var(--ds-space-stack-sm); inline-size: calc(var(--ds-size-control-lg) * 1.6); padding: var(--ds-space-2) 0; border: 0; border-radius: var(--ds-radius-detail);
background: transparent; color: var(--ds-color-text-primary); font: var(--ds-text-label-md); cursor: pointer; }
.odl-profile-hero .odl-hl span:last-child { max-inline-size: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.odl-profile-hero .odl-cv { display: grid; place-items: center; inline-size: calc(var(--ds-size-control-lg) * 1.3); aspect-ratio: 1; border-radius: var(--ds-radius-person); overflow: hidden;
border: calc(var(--ds-border-width-emphasis) * 1.5) solid var(--ds-color-surface-raised); box-shadow: 0 0 0 var(--ds-border-width-default) var(--ds-color-border-default); background: var(--ds-color-bg-neutral-subtle); }
.odl-profile-hero .odl-cv img { display: block; inline-size: 100%; block-size: 100%; object-fit: cover; }
.odl-profile-hero .odl-cv svg { inline-size: 40%; block-size: 40%; stroke-width: var(--ds-icon-stroke-md); }
.odl-profile-hero .odl-cv.is-new { background: var(--ds-color-surface-raised); color: var(--ds-color-text-primary); box-shadow: 0 0 0 var(--ds-border-width-default) var(--ds-color-border-strong); }
.odl-profile-hero button:focus-visible, .odl-profile-hero a:focus-visible { outline: var(--ds-focus-ring-width) solid var(--ds-color-border-focus); outline-offset: var(--ds-focus-ring-offset); }
.odl-profile-hero .odl-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
/* Wider: the buttons sit beside the photo, clear of the cover, and the counts beside the bio. */
@container (min-width: 36rem) {
.odl-profile-hero .odl-body { grid-template-columns: minmax(0, 1fr) minmax(0, 18rem); grid-template-areas: "av actions" "name counts" "bio counts" "hl hl"; column-gap: var(--ds-space-inline-md); padding-inline: var(--ds-space-inset-xl); padding-block-end: var(--ds-space-inset-xl); }
.odl-profile-hero .odl-actions { align-self: end; margin-block-start: var(--ds-space-stack-md); }
.odl-profile-hero .odl-counts { align-self: start; }
.odl-profile-hero .odl-hl { margin-inline: calc(-1 * var(--ds-space-inset-xl)); padding-inline: var(--ds-space-inset-xl); }
}
@media (prefers-reduced-motion: reduce) { .odl-profile-hero, .odl-profile-hero *, .odl-profile-hero *::before { transition: none !important; animation: none !important; } }
</style>
<section class="odl-profile-hero" aria-labelledby="odl-ph-name">
<div class="odl-card">
<div class="odl-cover"><img src="https://images.unsplash.com/photo-1609194246158-702022cc4f13?w=1408&h=400&fit=crop&auto=format&q=70" alt="Cover photo: a city skyline at sunset" width="704" height="200" loading="lazy" decoding="async"></div>
<div class="odl-body">
<span class="odl-ring" aria-hidden="true"><span class="odl-gap"><img class="odl-av" src="https://images.unsplash.com/photo-1705645930353-0e335311ef20?w=192&h=192&fit=crop&auto=format&q=70" alt="" width="96" height="96" loading="lazy" decoding="async"></span></span>
<div class="odl-name"><h2 id="odl-ph-name">Rafał Ostrowiak</h2>
<p class="odl-handle"><span>@rafal.makes</span><span class="odl-tag">he/him</span><span class="odl-tag">Photographer</span></p></div>
<div class="odl-bio"><p>Chasing blue hour on rooftops. Film on weekends, ceramics on Tuesdays. Prints ship in spring.</p>
<p><a class="odl-link" href="https://rafal.example/prints"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/></svg>rafal.example/prints</a></p></div>
<ul class="odl-counts">
<li><button type="button" class="odl-count"><b>214</b>posts</button></li>
<li><button type="button" class="odl-count"><b class="odl-fol">1,208</b>followers</button></li>
<li><button type="button" class="odl-count"><b>381</b>following</button></li>
</ul>
<div class="odl-actions">
<button type="button" class="odl-btn odl-follow" aria-pressed="false"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><span>Follow</span></button>
<button type="button" class="odl-btn">Message</button>
<button type="button" class="odl-btn is-icon" aria-label="Similar accounts"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><line x1="19" x2="19" y1="8" y2="14"/><line x1="22" x2="16" y1="11" y2="11"/></svg></button>
</div>
<ul class="odl-hl" aria-label="Highlights">
<li><button type="button"><span class="odl-cv" aria-hidden="true"><img src="https://images.unsplash.com/photo-1512531123205-560f5974e686?w=128&h=128&fit=crop&auto=format&q=70" alt="" width="64" height="64" loading="lazy" decoding="async"></span><span>Blue hour</span></button></li>
<li><button type="button"><span class="odl-cv" aria-hidden="true"><img src="https://images.unsplash.com/photo-1578429827733-272eb6d7972e?w=128&h=128&fit=crop&auto=format&q=70" alt="" width="64" height="64" loading="lazy" decoding="async"></span><span>Film</span></button></li>
<li><button type="button"><span class="odl-cv" aria-hidden="true"><img src="https://images.unsplash.com/photo-1520485521983-bfaa0bc6c80e?w=128&h=128&fit=crop&auto=format&q=70" alt="" width="64" height="64" loading="lazy" decoding="async"></span><span>Ceramics</span></button></li>
<li><button type="button"><span class="odl-cv" aria-hidden="true"><img src="https://images.unsplash.com/photo-1559310589-2673bfe16970?w=128&h=128&fit=crop&auto=format&q=70" alt="" width="64" height="64" loading="lazy" decoding="async"></span><span>Trips</span></button></li>
<li><button type="button"><span class="odl-cv is-new" aria-hidden="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="M12 5v14"/></svg></span><span>New</span></button></li>
</ul>
</div>
</div>
</section>
<script>
document.querySelectorAll(".odl-profile-hero").forEach((root) => {
if (root.dataset.odlReady) return; // wire once, even if the file is pasted twice
root.dataset.odlReady = "1";
const follow = root.querySelector(".odl-follow"), label = follow.querySelector("span"), count = root.querySelector(".odl-fol");
const base = 1208, fmt = new Intl.NumberFormat("en");
follow.addEventListener("click", () => {
const on = follow.getAttribute("aria-pressed") !== "true";
follow.setAttribute("aria-pressed", String(on)); label.textContent = on ? "Following" : "Follow";
count.textContent = fmt.format(base + (on ? 1 : 0));
});
});
</script>
A feed post: the author's photo with a follow button, a photo, like, comment, share and save, a like count that changes, a caption that opens with "more" and the top comment.
An album or playlist: a cover photo, title and artist, play, shuffle and save, then numbered tracks with their length, an explicit tag, a like heart and the playing one marked.
A one-to-one message thread with the other person's photo: bubbles on both sides grouped into runs, a quoted reply, a heart reaction, a read receipt, a typing indicator and a composer with attach and send that adds your message.