Book a callGitHub10
Ximena QuintanaActive now

Today 09:41

Are you still up for the flea market on Saturday?

The ceramics stall opens at 8 and the good bowls go fast

Replying to Ximena The ceramics stall opens at 8 and the good bowls go fast

8 is brutal but fine. I'll bring coffee

Meet by the fountain?

Perfect. Oat milk for me please

You reacted with a heart

Noted. See you at 7:50

Seen 09:52

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.

Source
<!-- Icons: Lucide (lucide.dev), ISC licence -->
<style>
.odl-chat-thread { box-sizing: border-box; inline-size: 100%; max-inline-size: 32rem; margin-inline: auto; container-type: inline-size; color: var(--ds-color-text-primary); font: var(--ds-text-body-md); text-wrap: pretty; }
.odl-chat-thread *, .odl-chat-thread *::before, .odl-chat-thread *::after { box-sizing: border-box; }
.odl-chat-thread .odl-card { display: grid; grid-template-rows: auto minmax(0, 1fr) auto; grid-template-columns: minmax(0, 1fr); max-block-size: 46rem; 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-chat-thread button { display: inline-grid; place-items: center; flex: none; min-inline-size: var(--ds-size-target-touch); min-block-size: var(--ds-size-target-touch); padding: 0; border: 0; border-radius: var(--ds-radius-full);
  background: transparent; color: var(--ds-color-text-primary); cursor: pointer; transition: background-color var(--ds-motion-duration-short) var(--ds-motion-easing-standard); }
.odl-chat-thread button:hover:not(:disabled) { background: var(--ds-color-bg-neutral-subtle); }
.odl-chat-thread button svg { inline-size: var(--ds-size-icon-md); block-size: var(--ds-size-icon-md); stroke-width: var(--ds-icon-stroke-md); }
.odl-chat-thread .odl-head { display: flex; align-items: center; gap: var(--ds-space-inline-xs); padding: var(--ds-space-inset-sm) var(--ds-space-inset-sm); border-block-end: var(--ds-border-width-default) solid var(--ds-color-border-subtle); }
.odl-chat-thread .odl-who { display: flex; align-items: center; gap: var(--ds-space-inline-md); flex: 1; min-inline-size: 0; }
.odl-chat-thread .odl-who > span:last-child { display: grid; gap: var(--ds-space-stack-xs); min-inline-size: 0; }
.odl-chat-thread .odl-who b { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font: var(--ds-text-label-lg); font-weight: var(--ds-font-weight-heading); }
.odl-chat-thread .odl-who small { color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
.odl-chat-thread .odl-av { position: relative; display: grid; flex: none; inline-size: var(--ds-size-control-lg); aspect-ratio: 1; border-radius: var(--ds-radius-person); }
.odl-chat-thread .odl-av img { display: block; inline-size: 100%; block-size: 100%; border-radius: var(--ds-radius-person); object-fit: cover; background: var(--ds-color-bg-neutral-subtle); }
.odl-chat-thread .odl-av.is-sm { inline-size: var(--ds-size-control-sm); }
.odl-chat-thread .odl-on { position: absolute; inset-inline-end: 0; inset-block-end: 0; inline-size: 28%; aspect-ratio: 1; border-radius: var(--ds-radius-full); background: var(--ds-color-bg-success-bold);
  border: var(--ds-border-width-emphasis) solid var(--ds-color-surface-raised); }
.odl-chat-thread .odl-log { display: flex; flex-direction: column; gap: var(--ds-space-4); padding: var(--ds-space-inset-lg) var(--ds-space-inset-xl); overflow-y: auto; overscroll-behavior: contain; background: var(--ds-color-surface-raised); }
.odl-chat-thread .odl-day { align-self: center; margin: 0 0 var(--ds-space-stack-md); color: var(--ds-color-text-tertiary); font: var(--ds-text-label-sm); font-variant-numeric: tabular-nums; }
.odl-chat-thread .odl-row { display: flex; align-items: flex-end; gap: var(--ds-space-inline-sm); }
.odl-chat-thread .odl-row.is-gap { margin-block-start: var(--ds-space-stack-md); }
.odl-chat-thread .odl-row.is-in .odl-slot { flex: none; inline-size: var(--ds-size-control-sm); }
.odl-chat-thread .odl-row.is-out { justify-content: flex-end; }
.odl-chat-thread .odl-stack { position: relative; display: grid; justify-items: start; max-inline-size: min(78%, 22rem); }
.odl-chat-thread .is-out .odl-stack { justify-items: end; }
/* Their bubbles are grey; yours take the accent's soft tint, so the accent stays for Send. */
.odl-chat-thread .odl-msg { position: relative; margin: 0; padding: var(--ds-space-stack-sm) var(--ds-space-inset-md); border-radius: var(--ds-radius-container); overflow-wrap: anywhere; }
.odl-chat-thread .is-in .odl-msg { background: var(--ds-color-bg-neutral-subtle); color: var(--ds-color-text-primary); }
.odl-chat-thread .is-out .odl-msg { background: var(--ds-color-bg-accent-subtle); color: var(--ds-color-text-primary); }
/* Bubbles in a run share a flatter corner on the sender's side. */
.odl-chat-thread .is-in .odl-msg.is-first { border-end-start-radius: var(--ds-radius-detail); }
.odl-chat-thread .is-in .odl-msg.is-mid { border-start-start-radius: var(--ds-radius-detail); border-end-start-radius: var(--ds-radius-detail); }
.odl-chat-thread .is-in .odl-msg.is-last { border-start-start-radius: var(--ds-radius-detail); }
.odl-chat-thread .is-out .odl-msg.is-first { border-end-end-radius: var(--ds-radius-detail); }
.odl-chat-thread .is-out .odl-msg.is-last { border-start-end-radius: var(--ds-radius-detail); }
.odl-chat-thread .odl-msg p { margin: 0; }
.odl-chat-thread .odl-msg .odl-quote { display: grid; gap: var(--ds-space-2); margin: 0 calc(-1 * var(--ds-space-4)) var(--ds-space-stack-sm); padding: var(--ds-space-stack-sm) var(--ds-space-inset-sm);
  border-radius: max(var(--ds-radius-detail), calc(var(--ds-radius-container) - var(--ds-space-8))); background: var(--ds-color-surface-raised); border-inline-start: var(--ds-border-width-emphasis) solid var(--ds-color-border-accent);
  color: var(--ds-color-text-secondary); font: var(--ds-text-body-sm); }
.odl-chat-thread .odl-quote b { color: var(--ds-color-text-primary); font: var(--ds-text-label-md); font-weight: var(--ds-font-weight-heading); }
.odl-chat-thread .odl-msg.has-react { margin-block-end: var(--ds-space-16); }
.odl-chat-thread .odl-react { position: absolute; inset-block-end: calc(-1 * var(--ds-space-4)); inset-inline-end: var(--ds-space-8); display: inline-flex; align-items: center; gap: var(--ds-space-2);
  padding: var(--ds-space-4) var(--ds-space-6); border-radius: var(--ds-radius-full); background: var(--ds-color-surface-raised); border: var(--ds-border-width-default) solid var(--ds-color-border-subtle);
  box-shadow: var(--ds-elevation-raised); color: var(--ds-color-text-primary); font: var(--ds-text-label-sm); }
.odl-chat-thread .odl-react svg { inline-size: var(--ds-size-icon-sm); block-size: var(--ds-size-icon-sm); fill: currentColor; stroke-width: var(--ds-icon-stroke-sm); }
.odl-chat-thread .odl-seen { margin: var(--ds-space-stack-xs) 0 0; color: var(--ds-color-text-tertiary); font: var(--ds-text-label-sm); font-variant-numeric: tabular-nums; }
.odl-chat-thread .odl-typing { display: inline-flex; gap: var(--ds-space-4); padding: var(--ds-space-12) var(--ds-space-16); border-radius: var(--ds-radius-container); border-end-start-radius: var(--ds-radius-detail); background: var(--ds-color-bg-neutral-subtle); }
.odl-chat-thread .odl-typing i { inline-size: var(--ds-space-6); block-size: var(--ds-space-6); border-radius: var(--ds-radius-full); background: var(--ds-color-icon-subtle);
  animation: odl-chat-thread-dot var(--ds-motion-duration-extra) var(--ds-motion-easing-standard) infinite; }
.odl-chat-thread .odl-typing i:nth-child(2) { animation-delay: var(--ds-motion-duration-short); }
.odl-chat-thread .odl-typing i:nth-child(3) { animation-delay: var(--ds-motion-duration-medium); }
@keyframes odl-chat-thread-dot { 0%, 60%, 100% { transform: translateY(0); opacity: 0.5; } 30% { transform: translateY(-40%); opacity: 1; } }
.odl-chat-thread .odl-composer { display: flex; align-items: center; gap: var(--ds-space-inline-sm); margin: 0; padding: var(--ds-space-inset-sm) var(--ds-space-inset-md);
  border-block-start: var(--ds-border-width-default) solid var(--ds-color-border-subtle); background: var(--ds-color-surface-raised); }
.odl-chat-thread .odl-composer input { flex: 1; min-inline-size: 0; min-block-size: var(--ds-size-target-touch); padding: 0 var(--ds-space-inset-md); border: var(--ds-border-width-default) solid var(--ds-color-border-input);
  border-radius: var(--ds-radius-control); background: var(--ds-color-bg-field); color: var(--ds-color-text-primary); font: var(--ds-text-body-md); }
.odl-chat-thread .odl-composer input::placeholder { color: var(--ds-color-text-tertiary); }
.odl-chat-thread .odl-send { background: var(--ds-color-bg-accent-bold); color: var(--ds-color-text-on-accent); }
.odl-chat-thread .odl-send:hover:not(:disabled) { background: var(--ds-color-bg-accent-bold-hover); }
.odl-chat-thread .odl-send:disabled { background: var(--ds-color-bg-disabled); color: var(--ds-color-text-disabled); cursor: default; }
.odl-chat-thread button:focus-visible, .odl-chat-thread input:focus-visible { outline: var(--ds-focus-ring-width) solid var(--ds-color-border-focus); outline-offset: var(--ds-focus-ring-offset); }
.odl-chat-thread .odl-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@container (max-width: 28rem) { .odl-chat-thread .odl-log { padding-inline: var(--ds-space-inset-lg); } .odl-chat-thread .odl-composer { padding-inline: var(--ds-space-inset-sm); } }
@container (max-width: 22rem) { .odl-chat-thread .odl-video { display: none; } }
@media (prefers-reduced-motion: reduce) { .odl-chat-thread, .odl-chat-thread *, .odl-chat-thread *::before { transition: none !important; animation: none !important; } }
</style>
<section class="odl-chat-thread" aria-label="Messages with Ximena Quintana">
  <div class="odl-card">
    <header class="odl-head">
      <button type="button" aria-label="Back to messages"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m15 18-6-6 6-6"/></svg></button>
      <span class="odl-who"><span class="odl-av" aria-hidden="true"><img src="https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=96&h=96&fit=crop&auto=format&q=70" alt="" width="48" height="48" loading="lazy" decoding="async"><span class="odl-on"></span></span><span><b>Ximena Quintana</b><small>Active now</small></span></span>
      <button type="button" aria-label="Voice call"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07 19.5 19.5 0 0 1-6-6 19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 4.11 2h3a2 2 0 0 1 2 1.72 12.84 12.84 0 0 0 .7 2.81 2 2 0 0 1-.45 2.11L8.09 9.91a16 16 0 0 0 6 6l1.27-1.27a2 2 0 0 1 2.11-.45 12.84 12.84 0 0 0 2.81.7A2 2 0 0 1 22 16.92z"/></svg></button>
      <button type="button" class="odl-video" aria-label="Video call"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m16 13 5.223 3.482a.5.5 0 0 0 .777-.416V7.87a.5.5 0 0 0-.752-.432L16 10.5"/><rect x="2" y="6" width="14" height="12" rx="2"/></svg></button>
    </header>
    <div class="odl-log" role="log" aria-label="Conversation" tabindex="0">
      <p class="odl-day">Today 09:41</p>
      <div class="odl-row is-in"><span class="odl-slot"></span><div class="odl-stack"><p class="odl-msg is-first">Are you still up for the flea market on Saturday?</p></div></div>
      <div class="odl-row is-in"><span class="odl-av is-sm" aria-hidden="true"><img src="https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=64&h=64&fit=crop&auto=format&q=70" alt="" width="32" height="32" loading="lazy" decoding="async"></span><div class="odl-stack"><p class="odl-msg is-last">The ceramics stall opens at 8 and the good bowls go fast</p></div></div>
      <div class="odl-row is-out is-gap"><div class="odl-stack"><div class="odl-msg is-first"><p class="odl-quote"><span class="odl-sr">Replying to </span><b>Ximena</b> The ceramics stall opens at 8 and the good bowls go fast</p><p>8 is brutal but fine. I'll bring coffee</p></div></div></div>
      <div class="odl-row is-out"><div class="odl-stack"><p class="odl-msg is-last">Meet by the fountain?</p></div></div>
      <div class="odl-row is-in is-gap"><span class="odl-av is-sm" aria-hidden="true"><img src="https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=64&h=64&fit=crop&auto=format&q=70" alt="" width="32" height="32" loading="lazy" decoding="async"></span><div class="odl-stack"><p class="odl-msg has-react">Perfect. Oat milk for me please</p>
        <span class="odl-react"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z"/></svg><span class="odl-sr">You reacted with a heart</span></span></div></div>
      <div class="odl-row is-out is-gap"><div class="odl-stack"><p class="odl-msg">Noted. See you at 7:50</p><p class="odl-seen">Seen 09:52</p></div></div>
      <div class="odl-row is-in is-gap odl-is-typing"><span class="odl-av is-sm" aria-hidden="true"><img src="https://images.unsplash.com/photo-1531746020798-e6953c6e8e04?w=64&h=64&fit=crop&auto=format&q=70" alt="" width="32" height="32" loading="lazy" decoding="async"></span><span class="odl-typing" role="status" aria-label="Ximena is typing"><i></i><i></i><i></i></span></div>
    </div>
    <form class="odl-composer">
      <button type="button" aria-label="Attach a photo or file"><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></button>
      <label class="odl-sr" for="odl-ct-input">Message</label>
      <input id="odl-ct-input" type="text" placeholder="Message…" autocomplete="off" enterkeyhint="send">
      <button type="submit" class="odl-send" aria-label="Send" disabled><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m5 12 7-7 7 7"/><path d="M12 19V5"/></svg></button>
    </form>
  </div>
</section>
<script>
document.querySelectorAll(".odl-chat-thread").forEach((root) => {
  if (root.dataset.odlReady) return; // wire once, even if the file is pasted twice
  root.dataset.odlReady = "1";
  const form = root.querySelector(".odl-composer"), input = form.querySelector("input"), send = form.querySelector(".odl-send");
  const log = root.querySelector(".odl-log"), typing = root.querySelector(".odl-is-typing");
  input.addEventListener("input", () => { send.disabled = !input.value.trim(); });
  form.addEventListener("submit", (e) => {
    e.preventDefault();
    const text = input.value.trim();
    if (!text) return;
    root.querySelectorAll(".odl-seen").forEach((s) => s.remove()); // the receipt sits under the newest message only
    const row = document.createElement("div"), stack = document.createElement("div"), msg = document.createElement("p"), seen = document.createElement("p");
    row.className = "odl-row is-out is-gap"; stack.className = "odl-stack"; msg.className = "odl-msg"; seen.className = "odl-seen";
    msg.textContent = text; seen.textContent = "Sent";
    stack.append(msg, seen); row.append(stack);
    log.insertBefore(row, typing);
    input.value = ""; send.disabled = true; input.focus();
    log.scrollTop = log.scrollHeight;
  });
  log.scrollTop = log.scrollHeight;
});
</script>

More like this

ConsumerFriendly · Social

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.

ConsumerFriendly · Media

Track list

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.

ConsumerFriendly · Media

Media shelf

Two swipeable rows for a streaming home: Continue watching, with stills, a progress bar and the time left, and Because you watched, with posters and New season labels.

Your kit

Nothing here yet. Add parts with Add to kit, and a look if you want the whole system.

    Parts
    0
    Files to fetch
    0 KB
    Licence
    MIT
    Total
    Free, now and later

    How do you want it?

    Your prompt

    No sign-up. Nothing leaves your browser until you paste it.

    Free