Skip to content
Jump to

Design system

Personal brand

Calm paper-and-ink pages with a raspberry accent. Used by rr2.dev, notes.rr2.dev and resume.rr2.dev.

Use with an agent

Paste this into the AGENTS.md (or CLAUDE.md) of any site that should follow this system.

## UI
This site uses the Personal brand design system v1.
- Before any UI work, read https://design.rr2.dev/personal/v1/DESIGN.md and follow it.
- Load https://design.rr2.dev/personal/v1/brand.css and put class="pb" on <html>.
- Copy component markup from DESIGN.md exactly. Use only --pb-* tokens and pb- classes.
- After changing UI, run: curl -sO https://design.rr2.dev/check.py && python3 check.py site . --system personal
  Fix every error before handing over.

Colour

Swatches follow the theme toggle. Values are light · dark. Contrast is measured on paper.

Text on paper

Aa ink--pb-ink#1f2329 · #dadadb14.3:1 · 11.9:1 on paper
Aa text--pb-text#2c3038 · #c4c4c512.0:1 · 9.6:1 on paper
Aa muted--pb-muted#5d636d · #a8a9aa5.5:1 · 7.1:1 on paper

Brand

  • --pb-raspberry#a8325e · #ec8db1Primary brand colour
  • --pb-raspberry-strong#8f2950 · #f4a9c5Primary hover/pressed
  • --pb-raspberry-soft#f8e5ed · #3a1f2bPrimary tint for tags and highlights
  • --pb-slate#3a5a80 · #93b4daSecondary brand colour: links, focus rings, current page
  • --pb-slate-strong#2f6690 · #82b6ddSecondary accent for sections and dates
  • --pb-slate-soft#e2edf6 · #1c2b38Secondary tint

Surfaces and lines

  • --pb-paper#f3f4f6 · #1d1e20Page background
  • --pb-surface#ffffff · #2e2e33Cards, panels, bubbles
  • --pb-surface-hover#f6f7f9 · #34353aRow and card hover
  • --pb-sunken#e9ecf0 · #37383eInline code, icon-button hover
  • --pb-line#e2e5ea · #3a3b3fHairline borders and dividers
  • --pb-line-strong#cdd1d8 · #4a4b4fDashed outlines, slash in the header, inactive dots. Decoration only, never text

Status

  • --pb-amber#995700 · #f2a94aNew, warnings
  • --pb-amber-soft#fff1dc · #3a2c19
  • --pb-green#0e7553 · #7fcca2Success, ok states
  • --pb-green-soft#ddf3ea · #1f3328
  • --pb-red#c2302f · #ef8f8eErrors, bad states
  • --pb-red-soft#fbe6e6 · #3a2020

Type

Use the class, not the numbers.

.pb-displayheadline-display · 52px/600
The quick brown fox
.pb-h1headline-lg · 40px/600
The quick brown fox
.pb-h2headline-md · 28px/600
The quick brown fox
.pb-h3headline-sm · 22px/500
The quick brown fox
.pb-h4title · 18px/600
The quick brown fox
.pb-bodybody-lg · 18px/400
The quick brown fox
.pb-uibody-md · 16px/400
The quick brown fox
.pb-smallbody-sm · 14px/400
The quick brown fox
.pb-labellabel-md · 13px/600
The quick brown fox
.pb-taglabel-sm · 12px/600
label-sm
.pb-codecode · 14px/400
code sample
.pb-handhand · 19px/600
Handwritten aside

Space and shape

4px spacing steps, 8px corners, hairline borders, faint shadows.

14px
28px
312px
416px
520px
624px
832px
936px
1248px
1872px
radius-sm4px
radius-md8px
radius-lg12px
radius-pill999px
shadow-rest
shadow-lift

Components

Each one: a live preview, the exact HTML to copy, and the rules that go with it.

Text

Type classes

Type classes for every text style. Use these instead of setting sizes.

Section label

Display heading

Page title

Section heading

Sub-section heading

Small heading

A lede introduces the page in a sentence or two.

Interface text for lists, menus and forms.

Small text for summaries and captions.

Inline code and an emphasis link.

HTML
<div class="pb-stack is-tight">
  <p class="pb-label">Section label</p>
  <h1 class="pb-display">Display heading</h1>
  <h1 class="pb-h1">Page title</h1>
  <h2 class="pb-h2">Section heading</h2>
  <h3 class="pb-h3">Sub-section heading</h3>
  <h4 class="pb-h4">Small heading</h4>
  <p class="pb-lede">A lede introduces the page in a sentence or two.</p>
  <p class="pb-ui">Interface text for lists, menus and forms.</p>
  <p class="pb-small">Small text for summaries and captions.</p>
  <p class="pb-ui">Inline <code class="pb-code">code</code> and an <a class="pb-link" href="#">emphasis link</a>.</p>
</div>
  • One pb-display or pb-h1 per page. Section headings are pb-h2, sub-sections pb-h3.
  • pb-lede goes right under the page title; pb-label goes above a heading or a group.
  • Inside long-form content, plain <h2>/<h3>/<p> in .pb-content are styled for you.

Layout

Page skeleton

Every page starts from this. Skip link, sticky header, main column, footer.

HTML
<!doctype html>
<html lang="en" class="pb">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Page title — rr2.dev</title>
  <link rel="stylesheet" href="https://design.rr2.dev/personal/v1/brand.css">
  <script>try{var t=localStorage.getItem('pb-theme');if(t)document.documentElement.dataset.theme=t}catch(e){}</script>
</head>
<body>
  <a class="pb-skip" href="#main">Skip to content</a>
  <!-- Header component here -->
  <main class="pb-wrap" id="main">
    <h1 class="pb-h1">Page title</h1>
    <p class="pb-lede">One or two sentences that say what this page is for.</p>
  </main>
  <!-- Footer component here -->
</body>
</html>
  • Put class="pb" on <html> and load brand.css (or inline brand.inline.min.css).
  • Use <main class="pb-wrap"> for reading pages and <main class="pb-wrap is-wide"> for listings.
  • The skip link is the first thing in <body> and points at the main element's id.
  • Keep the inline theme script in <head> so the saved theme applies before paint.

Layout

Header

Sticky frosted bar. Avatar and "rr2.dev / section" on the left, menu and theme toggle on the right.

HTML
<div class="pb-top">
  <header class="pb-nav">
    <div class="pb-logo">
      <a href="https://rr2.dev" aria-label="rr2.dev"><img class="pb-ico" src="https://design.rr2.dev/personal/v1/assets/avatar.webp" width="24" height="24" alt=""><span class="pb-root">rr2.dev</span></a>
      <span class="pb-slash">/</span><a href="/">section</a>
    </div>
    <div class="pb-cluster is-tight">
      <ul class="pb-menu">
        <li><a href="/posts/" aria-current="page"><span>Posts</span></a></li>
        <li><a href="/about/"><span>About</span></a></li>
      </ul>
      <button class="pb-toggle" type="button" aria-label="Switch light or dark theme" onclick="var r=document.documentElement,d=r.dataset.theme?r.dataset.theme==='dark':matchMedia('(prefers-color-scheme: dark)').matches;r.dataset.theme=d?'light':'dark';try{localStorage.setItem('pb-theme',r.dataset.theme)}catch(e){}">
        <svg class="pb-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/></svg>
        <svg class="pb-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true"><circle cx="12" cy="12" r="5"/><path d="M12 1v2M12 21v2M4.22 4.22l1.42 1.42M18.36 18.36l1.42 1.42M1 12h2M21 12h2M4.22 19.78l1.42-1.42M18.36 5.64l1.42-1.42"/></svg>
      </button>
    </div>
  </header>
</div>
  • "rr2.dev" always links to https://rr2.dev; the section name links to the site's home.
  • At most four menu items. Mark the current page with aria-current="page" on the link and wrap its text in <span>.
  • Add is-optional to the menu if it can disappear on phones (in-page jump links). Real site menus should have three short items or fewer so they fit.
  • The toggle needs the small script shown, and its aria-label.

Layout

Stack and cluster

Layout primitives for spacing. A stack spaces children vertically; a cluster lays them out in a wrapping row.

Recent posts

See all
designnotesdraft
HTML
<div class="pb-stack">
  <div class="pb-cluster is-between">
    <h2 class="pb-h3">Recent posts</h2>
    <a class="pb-btn is-quiet" href="#">See all</a>
  </div>
  <div class="pb-cluster is-tight">
    <span class="pb-tag">design</span><span class="pb-tag" data-accent="slate">notes</span><span class="pb-tag" data-accent="neutral">draft</span>
  </div>
</div>
  • Use these instead of margins between siblings.
  • Gaps: is-tight 8px, default 16px, is-loose 24px, is-section 48px (stack only). is-between spreads a cluster.

Actions

Button

Pill buttons. Primary (raspberry), secondary (outlined), quiet (text), and small.

View details
HTML
<div class="pb-cluster">
  <button class="pb-btn" type="button">Save changes</button>
  <a class="pb-btn is-secondary" href="#">View details</a>
  <button class="pb-btn is-secondary is-small" type="button">Small</button>
  <button class="pb-btn is-quiet" type="button">Cancel</button>
  <button class="pb-btn" type="button" disabled>Disabled</button>
</div>
  • One primary button per view, for the single most important action.
  • Secondary for every other action. Quiet for dismiss, cancel and "see all".
  • Use <a> for navigation and <button type="button"> for actions. Never a <div>.
  • Slate is not a button colour.
  • Disabled: add disabled to a <button>, or aria-disabled="true" to an <a>.

Actions

Icon button

Round 32px button holding a single 16px icon.

HTML
<div class="pb-cluster is-tight">
  <button class="pb-icon-btn" type="button" aria-label="Previous"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg></button>
  <button class="pb-icon-btn" type="button" aria-label="Next"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18l6-6-6-6"/></svg></button>
  <button class="pb-icon-btn" type="button" aria-label="Close"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" aria-hidden="true"><path d="M6 6l12 12M18 6L6 18"/></svg></button>
</div>
  • Always give it an aria-label that says what it does.
  • Use for previous/next, close and similar compact controls only.

Actions

Filter chips

Pill links for filtering a list by section. The current one fills with its accent.

HTML
<ul class="pb-chips">
  <li><a href="#" aria-current="page">All <span class="pb-n">24</span></a></li>
  <li><a href="#" data-accent="slate">Notes <span class="pb-n">17</span></a></li>
  <li><a href="#" data-accent="green">Guides <span class="pb-n">5</span></a></li>
  <li><a href="#" data-accent="amber">Reports <span class="pb-n">2</span></a></li>
</ul>
  • Mark the active chip with aria-current="page" (links) or aria-pressed="true" (buttons).
  • Give each chip the accent of the section it filters with data-accent. The "All" chip keeps the default.
  • Counts go in a <span class="pb-n">.

Content

Tags and badges

Tags are mono pills tinted by their accent. Badges are tiny uppercase status pills.

design notes guide draft New Live
HTML
<div class="pb-cluster is-tight">
  <span class="pb-tag">design</span>
  <span class="pb-tag" data-accent="slate">notes</span>
  <span class="pb-tag" data-accent="green">guide</span>
  <span class="pb-tag" data-accent="neutral">draft</span>
  <span class="pb-badge">New</span>
  <span class="pb-badge" data-accent="green">Live</span>
</div>
  • Tags name a category; set data-accent to match the section.
  • Badges show state ("New", "Live", "Draft"). The default badge is amber, for new.
  • Keep both to one or two words, lowercase for tags.

Content

Meta row

Date, reading time and tag under a title, separated by middle dots.

New 3 min read notes
HTML
<div class="pb-meta">
  <span class="pb-badge">New</span>
  <time datetime="2026-11-02">Nov 2, 2026</time>
  <span>3 min read</span>
  <span class="pb-tag" data-accent="slate">notes</span>
</div>
  • Always use <time datetime="YYYY-MM-DD"> for dates.
  • Order: badge (optional), date, reading time, tag. Separators are added for you.

Content

Card

White card with a serif title, a two-line summary and a meta row. Lifts on hover when clickable.

  • A short, specific title

    One or two lines that tell the reader what they will get from this.

    3 min readnotes
  • Another title that runs a little longer than the first

    Summaries are clamped visually by length, so keep them short.

    8 min readdesign
HTML
<ul class="pb-grid">
  <li class="pb-card" data-accent="slate">
    <a class="pb-cover" href="#" aria-label="A short, specific title"></a>
    <h3>A short, specific title</h3>
    <p>One or two lines that tell the reader what they will get from this.</p>
    <div class="pb-meta"><time datetime="2026-11-02">Nov 2</time><span>3 min read</span><span class="pb-tag" data-accent="slate">notes</span></div>
  </li>
  <li class="pb-card">
    <a class="pb-cover" href="#" aria-label="Another title that runs a little longer than the first"></a>
    <h3>Another title that runs a little longer than the first</h3>
    <p>Summaries are clamped visually by length, so keep them short.</p>
    <div class="pb-meta"><time datetime="2026-10-28">Oct 28</time><span>8 min read</span><span class="pb-tag">design</span></div>
  </li>
</ul>
  • Make the whole card clickable with a .pb-cover link as the first child, with an aria-label of the title.
  • Put cards in a .pb-grid. Never nest cards inside cards.
  • data-accent on the card sets the hover title colour and the tag.

Content

Grouped list with date tiles

An archive grouped by month. Each row has a calendar tile, a title and a sub-line.

HTML
<div class="pb-list">
  <h3>November 2026 <span>2 posts</span></h3>
  <ol>
    <li data-accent="slate"><a href="#"><span class="pb-cal"><b>Nov</b><span>2</span></span><span class="pb-body"><span class="pb-t">A short, specific title</span><span class="pb-sub"><span class="pb-tag" data-accent="slate">notes</span> 3 min read</span></span></a></li>
    <li><a href="#"><span class="pb-cal"><b>Nov</b><span>1</span></span><span class="pb-body"><span class="pb-t">Another post title</span><span class="pb-sub"><span class="pb-tag">design</span> 8 min read</span></span></a></li>
  </ol>
</div>
  • One .pb-list per group, with an <h3> holding the group name and a count in a <span>.
  • The tile shows a three-letter month in <b> and the day in <span>. Its colour follows data-accent on the <li>.
  • Wrap the title in .pb-t so it underlines on hover.

Content

Callout

A boxed note with an accent edge. Dashed variant for placeholders.

Heads up. This changes how links open on every page.
Careful. This can't be undone once it is published.
Placeholder for something that hasn't been written yet.
HTML
<div class="pb-stack is-tight">
  <div class="pb-note" data-accent="slate"><strong>Heads up.</strong> This changes how links open on every page.</div>
  <div class="pb-note" data-accent="amber"><strong>Careful.</strong> This can't be undone once it is published.</div>
  <div class="pb-note is-dashed">Placeholder for something that hasn't been written yet.</div>
</div>
  • Start with a bold lead-in ("Heads up.", "Tip.").
  • Slate for information, raspberry for tips, amber for warnings, green for success.
  • One callout per section at most.

Content

Long-form content

Article styles for plain HTML inside .pb-content. Headings, lists, quotes, code, tables and figures.

Opening paragraph in body text, with an inline link and some strong text.

A section heading

  • A first point.
    • A nested detail, a little quieter.
  • A second point with inline code.
A short quote that deserves to stand out.
npm run build
HTML
<article class="pb-content">
  <p>Opening paragraph in body text, with an <a href="#">inline link</a> and some <strong>strong text</strong>.</p>
  <h2>A section heading</h2>
  <ul>
    <li>A first point.
      <ul><li>A nested detail, a little quieter.</li></ul></li>
    <li>A second point with <code>inline code</code>.</li>
  </ul>
  <blockquote>A short quote that deserves to stand out.</blockquote>
  <pre><code>npm run build</code></pre>
</article>
  • Wrap the article body in <article class="pb-content"> inside a reading-width .pb-wrap.
  • Each h2 opens a section with a hairline above it. Don't add your own dividers.
  • Nested bullets change marker per level automatically.

Forms

Text field

Label, input and help text stacked. Also textarea and select. Error state in red.

Add the part after the @, like alex@example.com.

A few lines is plenty.

HTML
<form class="pb-stack" onsubmit="return false">
  <div class="pb-field">
    <label for="f-name">Name</label>
    <input class="pb-input" id="f-name" name="name" autocomplete="name" placeholder="Alex Lee">
  </div>
  <div class="pb-field is-error">
    <label for="f-email">Email</label>
    <input class="pb-input" id="f-email" name="email" type="email" value="alex@" aria-invalid="true" aria-describedby="f-email-help">
    <p class="pb-help" id="f-email-help">Add the part after the @, like alex@example.com.</p>
  </div>
  <div class="pb-field">
    <label for="f-topic">Topic</label>
    <select class="pb-select" id="f-topic" name="topic"><option>General</option><option>Feedback</option></select>
  </div>
  <div class="pb-field">
    <label for="f-msg">Message <span class="pb-optional">(optional)</span></label>
    <textarea class="pb-textarea" id="f-msg" name="message" aria-describedby="f-msg-help"></textarea>
    <p class="pb-help" id="f-msg-help">A few lines is plenty.</p>
  </div>
  <div class="pb-cluster"><button class="pb-btn" type="submit">Send</button><button class="pb-btn is-quiet" type="reset">Clear</button></div>
</form>
  • Every input has a visible <label for>. Placeholders are examples, never labels.
  • Help text goes under the input and is linked with aria-describedby.
  • Error: add is-error to .pb-field, aria-invalid="true" to the input, and say how to fix it in the help text.
  • Mark optional fields, not required ones.

Forms

Checkbox and radio

Native checkboxes and radios with a raspberry accent, wrapped in their label.

Email me about
Theme
HTML
<div class="pb-stack is-loose">
  <fieldset class="pb-fieldset">
    <legend>Email me about</legend>
    <label class="pb-check"><input type="checkbox" checked> New posts</label>
    <label class="pb-check"><input type="checkbox"><span>Project updates <small>About once a month.</small></span></label>
  </fieldset>
  <fieldset class="pb-fieldset">
    <legend>Theme</legend>
    <label class="pb-check"><input type="radio" name="theme-demo" checked> Match my system</label>
    <label class="pb-check"><input type="radio" name="theme-demo"> Light</label>
    <label class="pb-check"><input type="radio" name="theme-demo"> Dark</label>
  </fieldset>
</div>
  • Group related options in a <fieldset class="pb-fieldset"> with a <legend>.
  • Put the input inside its <label class="pb-check"> so the whole line is clickable.
  • Extra description goes in a <small> inside a <span> next to the input.

Navigation

Tabs

A row of links with a raspberry underline on the current one. Works without JavaScript.

HTML
<nav aria-label="Sections">
  <ul class="pb-tabs">
    <li><a href="#" aria-current="page">Overview</a></li>
    <li><a href="#">Notes</a></li>
    <li><a href="#">Guides</a></li>
    <li><a href="#">Archive</a></li>
  </ul>
</nav>
  • Use for switching between pages or sections of the same thing. Each tab is a real link.
  • Mark the current tab with aria-current="page".
  • Two to six tabs. Scrolls sideways on phones.

Navigation

Breadcrumbs

Small path above a page title.

HTML
<nav aria-label="Breadcrumb">
  <ol class="pb-crumbs">
    <li><a href="#">Home</a></li>
    <li><a href="#">Notes</a></li>
    <li><span aria-current="page">This page</span></li>
  </ol>
</nav>
  • Wrap in <nav aria-label="Breadcrumb">. The last item is the current page, with aria-current="page" and no link.

Navigation

Pagination

Previous and next pills with a page count between them.

HTML
<nav class="pb-pager" aria-label="Pagination">
  <a href="#" rel="prev" aria-disabled="true"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg>Newer</a>
  <span>Page 1 of 4</span>
  <a href="#" rel="next">Older<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18l6-6-6-6"/></svg></a>
</nav>
  • Disable the end you are at with aria-disabled="true".
  • Use rel="prev" and rel="next" on the links.

Data

Table

A bordered table on a white surface, with uppercase column labels and tabular figures.

Builds this week
SiteStatusPagesSize
HomeLive1284 KB
NotesLive31212 KB
ProfileNew1146 KB
HTML
<div class="pb-table-wrap">
  <table class="pb-table">
    <caption>Builds this week</caption>
    <thead><tr><th scope="col">Site</th><th scope="col">Status</th><th scope="col" class="is-num">Pages</th><th scope="col" class="is-num">Size</th></tr></thead>
    <tbody>
      <tr><th scope="row">Home</th><td><span class="pb-badge" data-accent="green">Live</span></td><td class="is-num">12</td><td class="is-num">84 KB</td></tr>
      <tr><th scope="row">Notes</th><td><span class="pb-badge" data-accent="green">Live</span></td><td class="is-num">31</td><td class="is-num">212 KB</td></tr>
      <tr><th scope="row">Profile</th><td><span class="pb-badge">New</span></td><td class="is-num">1</td><td class="is-num">146 KB</td></tr>
    </tbody>
  </table>
</div>
  • Always wrap in .pb-table-wrap so wide tables scroll inside themselves on phones.
  • Give it a <caption>. Use <th scope="col"> for headers and <th scope="row"> for the first cell of each row.
  • Right-align number columns with is-num.

Data

Empty state

What shows when a list has nothing in it yet. A short title, one line of why, one action.

Nothing here yet

Posts you save will show up here, newest first.

Browse posts
HTML
<div class="pb-empty">
  <strong>Nothing here yet</strong>
  <p>Posts you save will show up here, newest first.</p>
  <a class="pb-btn is-secondary is-small" href="#">Browse posts</a>
</div>
  • Say what will appear here and how to get it. No apologies, no illustrations.
  • At most one button, secondary unless it's the main action on the page.

Feedback

Dialog

A native <dialog> with a title, a sentence and actions in the footer.

Delete this draft?

The draft and its images will be removed. Published posts aren't affected.

HTML
<dialog class="pb-dialog" id="confirm" aria-labelledby="confirm-title">
  <div class="pb-dialog-body">
    <h2 id="confirm-title">Delete this draft?</h2>
    <p>The draft and its images will be removed. Published posts aren't affected.</p>
  </div>
  <form class="pb-dialog-actions" method="dialog">
    <button class="pb-btn is-quiet" value="cancel">Cancel</button>
    <button class="pb-btn" value="delete">Delete draft</button>
  </form>
</dialog>
<button class="pb-btn is-secondary" type="button" onclick="document.getElementById('confirm').showModal()">Open dialog</button>
  • Open with dialog.showModal(). Close with a <form method="dialog"> button so Escape and focus return work for free.
  • Title in an <h2>, linked with aria-labelledby.
  • Actions right-aligned: quiet cancel first, then the primary action.
  • Only for decisions that block. Everything else is a toast or an inline message.

Feedback

Toast

A short dark pill confirming something happened, with an optional undo.

Link copied to clipboard
HTML
<div class="pb-toast" role="status" data-accent="green"><span class="pb-dot" aria-hidden="true"></span>Link copied to clipboard<button type="button">Undo</button></div>
  • One sentence, past tense ("Link copied"). role="status" so screen readers announce it.
  • Place it in a .pb-toast-region fixed at the bottom centre. Hide after about 4 seconds; keep it while hovered or focused.
  • The dot takes the accent: green for success, red for errors.

Signature

Guide

A stick figure in ink lines with a handwritten speech bubble. The one playful element.

Hi! Want to see the newest post?

Show me
HTML
<div class="pb-guide">
  <svg class="pb-figure" viewBox="0 0 60 88" aria-hidden="true"><path class="ln wave-arm" d="M30 41 L22 52 L20 61"/><path class="ln" d="M30 41 L38 52 L40 61"/><path class="ln" d="M30 36 L30 63 M30 63 L26 75 L24 86 M30 63 L34 75 L36 86"/><circle class="hd" cx="30" cy="23" r="13"/><circle class="eye" cx="25.5" cy="22" r="1.6"/><circle class="eye" cx="34.5" cy="22" r="1.6"/><path class="mouth" d="M25.5 27 Q30 30.5 34.5 27"/><g class="wv"><path class="ln" d="M30 41 L18 38"/><path class="ln fa" d="M18 38 L12 26"/></g></svg>
  <div class="pb-bubble" role="status">
    <p>Hi! Want to see the newest post?</p>
    <div class="pb-acts"><a class="pb-btn is-small is-secondary" href="#">Show me</a><button class="pb-btn is-quiet" type="button">Later</button></div>
  </div>
</div>
  • At most one guide per page. It offers help or points at something; it never blocks content.
  • The speech bubble uses Caveat (pb-bubble p). Keep it under 15 words.
  • It waves once on hover or with is-waving; nothing else moves.