Book a callGitHub10
  • 23day streak
  • 1,240total XP
  • 30/50XP today

Unit 3 · Section 2

Ordering food

  1. Ordering a meal

    Lesson 4 of 6 · +15 XP

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-lesson-streak { box-sizing: border-box; inline-size: 100%; max-inline-size: 28rem; margin-inline: auto; container-type: inline-size; color: var(--ds-color-text-primary); font: var(--ds-text-body-md); text-wrap: pretty; }
.odl-lesson-streak *, .odl-lesson-streak *::before, .odl-lesson-streak *::after { box-sizing: border-box; }
.odl-lesson-streak p, .odl-lesson-streak h3 { margin: 0; }
.odl-lesson-streak .odl-card { --pad: var(--ds-space-inset-xl); display: grid; gap: var(--ds-space-stack-lg); padding: var(--pad); 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); }
/* Stats: streak, XP and the daily goal ring */
.odl-lesson-streak .odl-stats { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--ds-space-inline-sm); margin: 0; padding: 0; list-style: none; }
.odl-lesson-streak .odl-stats li { display: flex; align-items: center; justify-content: center; gap: var(--ds-space-inline-sm); min-block-size: var(--ds-size-control-lg);
  padding: var(--ds-space-inset-xs) var(--ds-space-inset-sm); border-radius: max(var(--ds-radius-4), calc(var(--ds-radius-container) - var(--pad))); background: var(--ds-color-bg-neutral-subtle); font: var(--ds-text-label-lg); font-variant-numeric: tabular-nums; white-space: nowrap; }
.odl-lesson-streak .odl-stats li > span { display: grid; justify-items: start; }
.odl-lesson-streak .odl-stats .odl-cap { color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); white-space: nowrap; }
.odl-lesson-streak .odl-stats .odl-flame { color: var(--ds-color-warning-light-9); }
.odl-lesson-streak .odl-stats svg { flex: none; inline-size: var(--ds-size-icon-md); block-size: var(--ds-size-icon-md); stroke-width: var(--ds-icon-stroke-md); color: var(--ds-color-icon-default); }
.odl-lesson-streak .odl-stats .odl-goal circle { fill: none; stroke-width: 4; }
.odl-lesson-streak .odl-goal .odl-t { stroke: var(--ds-color-border-default); }
.odl-lesson-streak .odl-goal .odl-a { stroke: var(--ds-color-bg-success-bold); stroke-linecap: round; transform: rotate(-90deg); transform-origin: 50% 50%; }
.odl-lesson-streak .odl-stats small { color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
/* Unit banner */
.odl-lesson-streak .odl-unit { display: grid; grid-template-columns: minmax(0, 1fr) auto; align-items: center; column-gap: var(--ds-space-inline-md); padding-block-end: var(--ds-space-inset-md); overflow: hidden;
  border-radius: max(var(--ds-radius-4), calc(var(--ds-radius-container) - var(--pad))); background: var(--ds-color-bg-neutral-subtle); }
.odl-lesson-streak .odl-unit img { grid-column: 1 / -1; display: block; inline-size: 100%; block-size: auto; aspect-ratio: 5 / 2; margin-block-end: var(--ds-space-stack-md); object-fit: cover; background: var(--ds-color-bg-neutral-subtle-hover); }
.odl-lesson-streak .odl-unit > div { padding-inline-start: var(--ds-space-inset-md); }
.odl-lesson-streak .odl-unit p { color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
.odl-lesson-streak .odl-unit h3 { margin-block-start: var(--ds-space-stack-xs); font: var(--ds-text-title-lg); }
.odl-lesson-streak .odl-guide { display: grid; place-items: center; inline-size: var(--ds-size-target-touch); block-size: var(--ds-size-target-touch); margin-inline-end: var(--ds-space-inset-md); padding: 0; border: var(--ds-border-width-default) solid var(--ds-color-border-default);
  border-radius: var(--ds-radius-full); background: var(--ds-color-surface-raised); color: var(--ds-color-icon-default); cursor: pointer; transition: background-color var(--ds-motion-duration-short) var(--ds-motion-easing-standard); }
.odl-lesson-streak .odl-guide:hover { background: var(--ds-color-bg-neutral-subtle-hover); }
.odl-lesson-streak .odl-guide svg { inline-size: var(--ds-size-icon-md); block-size: var(--ds-size-icon-md); stroke-width: var(--ds-icon-stroke-md); }
/* The path: nodes that zig-zag down the middle */
.odl-lesson-streak .odl-path { display: grid; justify-items: center; gap: var(--ds-space-stack-lg); margin: 0; padding: var(--ds-space-stack-md) 0 var(--ds-space-stack-lg); list-style: none; --step: var(--ds-space-48); }
.odl-lesson-streak .odl-path > li { display: grid; justify-items: center; gap: var(--ds-space-stack-md); transform: translateX(calc(var(--x, 0) * var(--step))); }
.odl-lesson-streak .odl-node { --face: var(--ds-color-bg-neutral-subtle); --lip: var(--ds-color-border-strong); --ink: var(--ds-color-text-tertiary);
  display: grid; place-items: center; inline-size: calc(var(--ds-size-control-lg) * 1.2); aspect-ratio: 1; padding: 0; border: 0; border-radius: var(--ds-radius-full);
  background: var(--face); color: var(--ink); cursor: pointer;
  transition: transform var(--ds-motion-duration-short) var(--ds-motion-easing-standard), box-shadow var(--ds-motion-duration-short) var(--ds-motion-easing-standard); }
.odl-lesson-streak .odl-node svg { inline-size: var(--ds-size-icon-lg); block-size: var(--ds-size-icon-lg); stroke-width: var(--ds-icon-stroke-lg); }
.odl-lesson-streak .odl-node:active:not(:disabled) { transform: scale(0.96); }
.odl-lesson-streak .is-done .odl-node { --face: var(--ds-color-bg-success-subtle); --lip: var(--ds-color-border-success); --ink: var(--ds-color-text-success); box-shadow: inset 0 0 0 var(--ds-border-width-emphasis) var(--ds-color-border-success); }
.odl-lesson-streak .is-now .odl-node { --face: var(--ds-color-bg-accent-bold); --lip: var(--ds-color-accent-light-11); --ink: var(--ds-color-text-on-accent); }
.odl-lesson-streak .odl-node:disabled { cursor: not-allowed; }
.odl-lesson-streak .odl-path > li:not(.is-done):not(.is-now) .odl-node { box-shadow: inset 0 0 0 var(--ds-border-width-emphasis) var(--ds-color-border-default); }
/* A faint dashed path from each lesson up to the one before it (--px is the previous lesson's offset). */
.odl-lesson-streak .odl-path > li { position: relative; }
.odl-lesson-streak .odl-path > li + li::before { --dx: calc((var(--px, 0) - var(--x, 0)) * var(--step)); content: ""; position: absolute; inset-block-end: 100%; inset-inline-start: 50%;
  block-size: hypot(var(--dx), var(--ds-space-stack-lg)); border-inline-start: var(--ds-border-width-emphasis) dashed var(--ds-color-border-strong);
  transform-origin: 50% 100%; transform: translateX(-50%) rotate(atan2(var(--dx), var(--ds-space-stack-lg))); pointer-events: none; }
.odl-lesson-streak .odl-path > li:has(.odl-pop) + li::before { --px: 0; }
.odl-lesson-streak .odl-node[aria-pressed="true"] { outline: var(--ds-border-width-selected) solid var(--face); outline-offset: var(--ds-space-6); }
.odl-lesson-streak .is-now .odl-node { position: relative; inline-size: calc(var(--ds-size-control-lg) * 1.4); }
.odl-lesson-streak .is-now .odl-node svg { inline-size: calc(var(--ds-size-icon-lg) * 1.3); block-size: calc(var(--ds-size-icon-lg) * 1.3); fill: currentColor; }
.odl-lesson-streak button:focus-visible { outline: var(--ds-focus-ring-width) solid var(--ds-color-border-focus); outline-offset: var(--ds-focus-ring-offset); }
.odl-lesson-streak .odl-node:focus-visible { outline-offset: var(--ds-space-8); }
/* The pop-over card for the chosen lesson; it moves under whichever node is chosen */
.odl-lesson-streak .odl-pop { position: relative; display: grid; gap: var(--ds-space-stack-sm); inline-size: min(calc(100cqi - var(--ds-space-8)), 19rem); margin-block-start: var(--ds-space-8);
  padding: var(--ds-space-inset-lg); border-radius: var(--ds-radius-container); background: var(--ds-color-surface-raised); border: var(--ds-border-width-emphasis) solid var(--ds-color-border-default);
  box-shadow: var(--ds-elevation-floating); text-align: center; transform: translateX(calc(var(--x, 0) * -1 * var(--step))); }
.odl-lesson-streak .odl-pop::before { content: ""; position: absolute; inset-block-start: 0; inset-inline-start: calc(50% + var(--x, 0) * var(--step)); inline-size: var(--ds-space-16); aspect-ratio: 1;
  background: inherit; border-block-start: inherit; border-inline-start: inherit; transform: translate(-50%, -60%) rotate(45deg); }
.odl-lesson-streak .odl-pop b { font: var(--ds-text-title-md); }
.odl-lesson-streak .odl-pop p { margin-block-end: var(--ds-space-stack-sm); color: var(--ds-color-text-secondary); font: var(--ds-text-label-md); }
.odl-lesson-streak .odl-start { min-block-size: max(var(--ds-size-control-lg), var(--ds-size-target-touch)); border: 0; border-radius: var(--ds-radius-control);
  background: var(--ds-color-bg-action-primary); color: var(--ds-color-text-on-action); font: var(--ds-text-label-lg); cursor: pointer; transition: background-color var(--ds-motion-duration-short) var(--ds-motion-easing-standard); }
.odl-lesson-streak .odl-start:hover { background: var(--ds-color-bg-action-primary-hover); }
.odl-lesson-streak .odl-sr { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
@container (max-width: 26rem) {
  .odl-lesson-streak .odl-card { --pad: var(--ds-space-inset-lg); }
  .odl-lesson-streak .odl-stats li { padding: 0 var(--ds-space-inset-xs); }
  .odl-lesson-streak .odl-path { --step: var(--ds-space-40); }
}
@media (prefers-reduced-motion: reduce) { .odl-lesson-streak, .odl-lesson-streak *, .odl-lesson-streak *::before { transition: none !important; animation: none !important; } }
</style>
<section class="odl-lesson-streak" aria-label="Spanish course: unit 3">
  <div class="odl-card">
    <ul class="odl-stats">
      <li><svg class="odl-flame" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M8.5 14.5A2.5 2.5 0 0 0 11 12c0-1.38-.5-2-1-3-1.072-2.143-.224-4.054 2-6 .5 2.5 2 4.9 4 6.5 2 1.6 3 3.5 3 5.5a7 7 0 1 1-14 0c0-1.153.433-2.294 1-3a2.5 2.5 0 0 0 2.5 2.5z"/></svg><span>23<span class="odl-cap">day streak</span></span></li>
      <li><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 3h12l4 6-10 13L2 9Z"/><path d="M11 3 8 9l4 13 4-13-3-6"/><path d="M2 9h20"/></svg><span>1,240<span class="odl-cap">total XP</span></span></li>
      <li><svg class="odl-goal" viewBox="0 0 24 24" aria-hidden="true"><circle class="odl-t" cx="12" cy="12" r="9"/><circle class="odl-a" cx="12" cy="12" r="9" pathLength="100" stroke-dasharray="60 100"/></svg><span><span><span class="odl-xp">30</span>/50</span><span class="odl-cap">XP today</span></span></li>
    </ul>
    <div class="odl-unit">
      <img src="https://images.unsplash.com/photo-1583354608715-177553a4035e?w=896&h=358&fit=crop&auto=format&q=70" alt="" width="896" height="358" loading="lazy" decoding="async">
      <div><p>Unit 3 · Section 2</p><h3>Ordering food</h3></div>
      <button type="button" class="odl-guide" aria-label="Unit 3 guide: words and phrases"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M12 7v14"/><path d="M3 18a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h5a4 4 0 0 1 4 4 4 4 0 0 1 4-4h5a1 1 0 0 1 1 1v13a1 1 0 0 1-1 1h-6a3 3 0 0 0-3 3 3 3 0 0 0-3-3z"/></svg></button>
    </div>
    <ol class="odl-path">
      <li class="is-done" style="--x: 0"><button type="button" class="odl-node" data-title="Greetings at the table" data-sub="Done · practise again for +5 XP" data-cta="Practise" aria-pressed="false" aria-label="Lesson 1, Greetings at the table, done"><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></button></li>
      <li class="is-done" style="--x: -1; --px: 0"><button type="button" class="odl-node" data-title="Reading a menu" data-sub="Done · practise again for +5 XP" data-cta="Practise" aria-pressed="false" aria-label="Lesson 2, Reading a menu, done"><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></button></li>
      <li class="is-done" style="--x: -1.3; --px: -1"><button type="button" class="odl-node" data-title="Numbers and prices" data-sub="Done · practise again for +5 XP" data-cta="Practise" aria-pressed="false" aria-label="Lesson 3, Numbers and prices, done"><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></button></li>
      <li class="is-now" style="--x: -0.4; --px: -1.3"><button type="button" class="odl-node" data-title="Ordering a meal" data-sub="Lesson 4 of 6 · +15 XP" data-cta="Start lesson" aria-pressed="true" aria-current="step" aria-label="Lesson 4, Ordering a meal, up next"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.123 2.123 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.123 2.123 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.122 2.122 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.122 2.122 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.122 2.122 0 0 0 1.597-1.16z"/></svg></button>
        <div class="odl-pop" aria-live="polite"><b class="odl-pt">Ordering a meal</b><p class="odl-ps">Lesson 4 of 6 · +15 XP</p><button type="button" class="odl-start">Start lesson</button></div></li>
      <li style="--x: 0.7; --px: -0.4"><button type="button" class="odl-node" disabled aria-label="Lesson 5, Asking for the bill, locked"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect width="18" height="11" x="3" y="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg></button></li>
      <li style="--x: 1.3; --px: 0.7"><button type="button" class="odl-node" disabled aria-label="Unit review, locked"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M6 9H4.5a2.5 2.5 0 0 1 0-5H6"/><path d="M18 9h1.5a2.5 2.5 0 0 0 0-5H18"/><path d="M4 22h16"/><path d="M10 14.66V17c0 .55-.47.98-.97 1.21C7.85 18.75 7 20.24 7 22"/><path d="M14 14.66V17c0 .55.47.98.97 1.21C16.15 18.75 17 20.24 17 22"/><path d="M18 2H6v7a6 6 0 0 0 12 0V2Z"/></svg></button></li>
    </ol>
  </div>
</section>
<script>
document.querySelectorAll(".odl-lesson-streak").forEach((root) => {
  if (root.dataset.odlReady) return; // wire once, even if the file is pasted twice
  root.dataset.odlReady = "1";
  const pop = root.querySelector(".odl-pop"), title = pop.querySelector(".odl-pt"), sub = pop.querySelector(".odl-ps"), start = pop.querySelector(".odl-start");
  root.querySelectorAll(".odl-node:not(:disabled)").forEach((node) => node.addEventListener("click", () => {
    root.querySelectorAll(".odl-node").forEach((n) => n.setAttribute("aria-pressed", String(n === node)));
    node.parentElement.appendChild(pop); // the card follows the chosen lesson
    title.textContent = node.dataset.title; sub.textContent = node.dataset.sub; start.textContent = node.dataset.cta;
  }));
});
</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 · Social

Chat thread

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.

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