One graphic per feature section of the launch blog post. Each is a 1440×760 canvas exported as a PNG and dropped into the post's figure slot. The browser frame around each one is drawn by the blog template, not baked into the export.
handoff/in-context/my-emoji.html — rendered in an iframe at 1440×760, not re-mocked. This is the source the slot record already names..bsg-frame · viewer chrome only — the blog template draws the frame, dots and URL pill. The PNG export is the bare stage..bsg-fig column · figure over caption, one axis (R10)<div class="bsg-sizer">
<div class="bsg-stage">
<iframe src="in-context/my-emoji.html" title="My Emoji" scrolling="no"></iframe>
</div>
</div>
{
id: "inline-eclaunch-01",
label: "My Emoji",
description: "My Emoji. Recents, lists, and collections, all on one page.
Screenshot of emojicopy.com. Source mockup in the handoff
workspace: handoff/in-context/my-emoji.html",
}
/* fixed 1440×760 export canvas: raw px on purpose; colors from tokens.css.
The post renders the figure 880px wide, so the canvas scales 880/1440. */
.bsg-fig { width: 880px; margin: 0 auto; }
.bsg-frame { border-radius: var(--r-xl); overflow: hidden;
border: 1px solid var(--c-border-primary);
background: var(--c-surface-secondary);
box-shadow: 0 22px 60px rgba(15,16,18,.12); }
.bsg-bar { display: flex; align-items: center; gap: 7px;
padding: 0 14px; height: 34px;
background: var(--c-surface-cta);
border-bottom: 1px solid var(--c-border-primary); }
.bsg-dot { width: 9px; height: 9px; border-radius: 50%;
background: var(--c-border-secondary); }
.bsg-url { margin: 0 auto; padding: 3px 14px; border-radius: 999px;
background: var(--c-surface-secondary);
border: 1px solid var(--c-border-primary);
font-family: var(--ff-inter); font-size: 1.1rem;
color: var(--c-text-tertiary); }
.bsg-sizer { width: 880px; height: 464.4px; overflow: hidden; }
.bsg-stage { width: 1440px; height: 760px;
transform: scale(.61111); transform-origin: top left;
position: relative; overflow: hidden;
font-family: var(--ff-inter);
background: var(--c-surface-primary); }
.bsg-cap { margin-top: 10px; font-size: 1.3rem;
color: var(--c-text-tertiary); text-align: center; }
.bsg-cap b { color: var(--c-text-secondary); }
.bsg-fig iframe { position: absolute; top: 0; left: 0;
width: 1440px; height: 760px; border: 0; }
QuickSearch · ui/components/QuickSearch.vue — the 304px dropdown, section headers "My Emoji (N)" / "All Emoji (N)", closing on a black "View N results" button. Strings are the shipping ones.ComboField · ui/components/QuickSearch.vue — 48px, 12px radius, --c-input-primary fill, 2px border.bsg-stage--dark · new — sets color-scheme: dark so every light-dark() token in tokens.css flips inside the canvas. No duplicate dark palette..bsg-app__body row · fixed 250px rail beside a fluid column, one axis (R10)<div class="bsg-side">
<div class="bsg-field bsg-field--focus">
<span class="bsg-field__ico">⌕</span>smiling face
<span class="bsg-field__x">✕</span>
</div>
<div class="bsg-menu" style="width:304px">
<div class="bsg-menu__label"><b>My Emoji (4)</b></div>
<div class="bsg-grid"><!-- 4 imgs --></div>
<div class="bsg-menu__label"><b>All Emoji (28)</b></div>
<div class="bsg-grid"><!-- first 10 imgs --></div>
<div class="bsg-btn-black">View 32 results</div>
</div>
</div>
<!-- emoji base: https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/<codepoint>.png -->
<div class="combo-field fluid quick-search focused filled">
<input placeholder="Search" value="smiling face">
<div class="append"><button class="icon-button size:sm">
<i class="icon icon-x"></i></button></div>
</div>
<div class="menu-surface variant:search-menu search-menu-surface">
<div class="menu-input-row variant:quick-search"><div class="row">
<span class="text f:body/s-heavy">My Emoji (4)</span></div></div>
<div class="emoji-grid">…</div>
<div class="menu-input-row variant:quick-search"><div class="row">
<span class="text f:body/s-heavy">All Emoji (28)</span></div></div>
<div class="emoji-grid">…</div>
<button class="primary-button variant:black">
<label>View 32 results</label></button>
</div>
.bsg-stage--dark { color-scheme: dark; }
.bsg-field { height: 48px; border-radius: 12px;
border: 2px solid transparent;
background: var(--c-input-primary);
display: flex; align-items: center; gap: 10px;
padding: 0 14px; font-size: 16px;
color: var(--c-text-primary); }
.bsg-field--focus { border-color: var(--c-border-tertiary); }
.bsg-field__ico { color: var(--c-icon-gray); font-size: 16px; }
.bsg-field__x { margin-left: auto; width: 24px; height: 24px;
border-radius: 50%;
background: var(--c-button-gray-bg-2);
color: var(--c-icon-gray);
display: flex; align-items: center;
justify-content: center; font-size: 12px; }
.bsg-menu { border-radius: 16px; padding: 4px;
background: var(--c-surface-dropdown);
border: 1px solid var(--c-border-secondary);
box-shadow: 0 16px 72px 0 #171A571F; }
.bsg-menu__label { display: flex; align-items: center;
justify-content: space-between; gap: 8px;
padding: 4px 16px; font-size: 12px; font-weight: 500;
color: var(--c-text-tertiary); }
.bsg-menu__label b { font-weight: 700; }
.bsg-btn-black { display: flex; align-items: center;
justify-content: center; height: 44px;
border-radius: 12px;
background: var(--c-button-black-bg);
color: var(--c-text-white);
font-size: 14px; font-weight: 700; margin: 4px; }
ActionBar · ui/components/ActionBar.vue — 56px pill at bottom 32 / right 32, --c-surface-action-bar, 32px emoji at 8px gaps, 2px×40px separator, three 40px white circle IconButtonsEmojiGrid · ui/components/EmojiGrid.vue — 48px cells, 32px glyphs, 16px gap, 16px hover radius.bsg-actionbar row · run, rule, actions on one axis (R10)<div class="bsg-actionbar">
<span class="bsg-actionbar__list">
<!-- six imgs: 1f525 1f4af 1f64c 1f389 2728 1f44f -->
</span>
<span class="bsg-actionbar__sep"></span>
<span class="bsg-actionbar__acts">
<span class="bsg-iconbtn">✕</span> <!-- Clear All -->
<span class="bsg-iconbtn">⧉</span> <!-- Copy All -->
<span class="bsg-iconbtn">⋯</span> <!-- Action bar features -->
</span>
</div>
<!-- emoji base: https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/<codepoint>.png -->
<div class="action-bar">
<span class="__item-grids-wrapper">
<div class="action-bar__emoji-list">
<i class="action-bar-emoji jp-font">🔥</i> …six total
</div>
</span>
<span class="action-bar__sep"></span>
<div class="__actions __active">
<IconButton size="md!" shape="circle" variant="white" icon="x" />
<IconButton size="md!" shape="circle" variant="white" icon="copy" />
<IconButton size="md!" shape="circle" variant="white" icon="dots-three" />
</div>
</div>
.bsg-actionbar { position: absolute; bottom: 32px; right: 32px;
height: 56px; border-radius: 999px;
background: var(--c-surface-action-bar);
display: flex; align-items: center; padding: 0 8px 0 0; }
.bsg-actionbar__list { display: inline-flex; align-items: center;
gap: 8px; padding: 0 8px; }
.bsg-actionbar__list img { width: 32px; height: 32px; }
.bsg-actionbar__sep { width: 2px; height: 40px;
background: var(--c-border-action-bar); }
.bsg-actionbar__acts { display: flex; align-items: center;
padding-left: 8px; gap: 4px; }
.bsg-iconbtn { width: 40px; height: 40px; border-radius: 50%;
background: var(--white); color: var(--metal-950);
display: flex; align-items: center; justify-content: center;
font-size: 16px; font-weight: 700; }
.bsg-grid { display: grid;
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
gap: 16px; margin: 12px 0; }
.bsg-grid img { width: 32px; height: 32px; justify-self: center; }
.bsg-cell { aspect-ratio: 1; display: flex; align-items: center;
justify-content: center; border-radius: 16px; }
.bsg-cell--hover { background: var(--c-button-gray-bg-2); }
handoff/in-context/explore.html — rendered in an iframe at 1440×760, not re-mocked. This is the source the slot record already names..bsg-frame · viewer chrome only — the blog template draws the frame, dots and URL pill. The PNG export is the bare stage..bsg-fig column · figure over caption, one axis (R10)<div class="bsg-sizer">
<div class="bsg-stage">
<iframe src="in-context/explore.html" title="Explore" scrolling="no"></iframe>
</div>
</div>
{
id: "inline-eclaunch-04",
label: "Explore",
description: "Explore. Browse, preview, follow. Screenshot of
emojicopy.com/explore. Source mockup in the handoff
workspace: handoff/in-context/explore.html",
}
.bsg-fig { width: 880px; margin: 0 auto; }
.bsg-frame { border-radius: var(--r-xl); overflow: hidden;
border: 1px solid var(--c-border-primary);
background: var(--c-surface-secondary);
box-shadow: 0 22px 60px rgba(15,16,18,.12); }
.bsg-sizer { width: 880px; height: 464.4px; overflow: hidden; }
.bsg-stage { width: 1440px; height: 760px;
transform: scale(.61111); transform-origin: top left;
position: relative; overflow: hidden;
font-family: var(--ff-inter);
background: var(--c-surface-primary); }
.bsg-fig iframe { position: absolute; top: 0; left: 0;
width: 1440px; height: 760px; border: 0; }
ui/layouts/Main.vue — PopMenu bottom-end, 272px, opened by IconButton icon="sliders-horizontal". Labels "Skin Tone" · "Theme" · "Emoji Size" and the "View all settings" button are the shipping strings.SkinToneSwitch · ui/input/SkinToneSwitch.vue — six swatches, exact shipping hexes, active swatch takes --c-border-tertiary.bsg-badge · new — the shortcut chip beside each label. Text only, no leading dot..bsg-tones row · six swatches space-between on one axis (R10)<div class="bsg-menu" style="width:272px">
<div class="bsg-menu__label"><b>Skin Tone</b><span class="bsg-badge">[ ]</span></div>
<div class="bsg-tones">
<i class="on" style="--tone:#FFCD00"></i><i style="--tone:#FFE2C1"></i>
<i style="--tone:#E6CEA0"></i><i style="--tone:#E4A170"></i>
<i style="--tone:#AE7F51"></i><i style="--tone:#614C41"></i>
</div>
<hr class="bsg-menudiv">
<div class="bsg-menu__label"><b>Theme</b><span class="bsg-badge">;</span></div>
<div class="bsg-select"><span>Light</span><span>▾</span></div>
<hr class="bsg-menudiv">
<div class="bsg-menu__label"><b>Emoji Size</b><span class="bsg-badge">− +</span></div>
<div class="bsg-select"><span>Medium</span><span>▾</span></div>
<div class="bsg-btn-green">⚙ View all settings</div>
</div>
<div class="menu-surface settings-menu-surface">
<div class="menu-input-row">
<div class="row"><Text type="body/s-heavy">Skin Tone</Text>
<span class="shortcut-badge">[ ]</span></div>
<SkinToneSwitch />
</div>
<hr class="menu-divider">
<div class="menu-input-row">
<div class="row"><Text type="body/s-heavy">Theme</Text>
<span class="shortcut-badge">;</span></div>
<DropdownField /> <!-- Auto · Light · Dark -->
</div>
<hr class="menu-divider">
<div class="menu-input-row">
<div class="row"><Text type="body/s-heavy">Emoji Size</Text>
<span class="shortcut-badge">− +</span></div>
<DropdownField /> <!-- S 24 · M 32 · L 40 · XL 48 · H 56 -->
</div>
<PrimaryButton variant="green" size="md" icon="gear">
View all settings</PrimaryButton>
</div>
.bsg-tones { display: flex; justify-content: space-between;
padding: 0 16px 4px; }
.bsg-tones i { width: 32px; height: 32px; border-radius: 100%;
border: 2px solid var(--c-border-secondary);
padding: 2px; display: flex; }
.bsg-tones i::before { content: ''; flex: 1; border-radius: 100%;
background: var(--tone); }
.bsg-tones i.on { border-color: var(--c-border-tertiary); }
.bsg-select { display: flex; align-items: center;
justify-content: space-between; height: 44px;
margin: 0 16px 4px; padding: 0 14px;
border-radius: 12px; background: var(--c-input-primary);
font-size: 14px; font-weight: 500;
color: var(--c-text-primary); }
.bsg-badge { display: inline-flex; align-items: center; gap: 4px;
padding: 2px 8px; border-radius: 6px;
background: var(--c-button-gray-bg);
font-size: 11px; font-weight: 700;
color: var(--c-text-tertiary);
font-family: var(--ff-inter); }
.bsg-btn-green { display: flex; align-items: center;
justify-content: center; gap: 8px; height: 44px;
margin: 4px 16px 12px; border-radius: 12px;
background: var(--c-button-green-bg);
color: var(--green-900);
font-size: 14px; font-weight: 700; }
.bsg-menudiv { height: 1px; background: var(--c-border-primary);
border: 0; margin: 0; }
Combo · ui/components/Combo.vue — 48px pill, 99px radius, --c-surface-combo fill, 20px emoji at 8px gaps; selected takes --c-surface-highlightActionMenuRow · ui/menus/ActionMenuRow.vue — the label + sub + trailing-icon row used by the action bar's ⋯ menu.bsg-comborow wrap · pills flow and wrap, one axis with wrapping (R10)<div class="bsg-comborow">
<span class="bsg-combo bsg-combo--on">
<!-- three imgs: 1f525 1f4af 1f64c -->
</span>
<span class="bsg-combo"><!-- 1f389 1f973 2728 --></span>
…
</div>
<div class="bsg-menu" style="width:288px">
<div class="bsg-menurow">
<span class="bsg-menurow__l">
<label>Save as combo</label>
<sub>Keep this string as one pill</sub>
</span><i>+</i>
</div>
</div>
<!-- emoji base: https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/<codepoint>.png -->
<span class="combo size__20">
<span class="combo-emoji jp-font">🔥</span>
<span class="combo-emoji jp-font">💯</span>
<span class="combo-emoji jp-font">🙌</span>
</span>
<!-- the cap, verbatim from ActionBar.vue -->
const canSaveAsCombo = computed(() => {
return emoji.actionBar.emoji.length >= 2
&& emoji.actionBar.emoji.length <= 8
&& lists.my.length > 0
})
.bsg-combo { display: inline-flex; height: 48px;
padding: 8px 12px; gap: 8px; align-items: center;
border-radius: 99px;
background: var(--c-surface-combo); }
.bsg-combo img { width: 20px; height: 20px; }
.bsg-combo--on { background: var(--c-surface-highlight); }
.bsg-comborow { display: flex; flex-wrap: wrap; gap: 16px;
padding: 12px 0; }
.bsg-menurow { display: flex; align-items: center; gap: 16px;
min-height: 52px; padding: 4px 16px;
border-radius: 12px; }
.bsg-menurow__l{ flex: 1; display: flex; flex-direction: column; }
.bsg-menurow__l label { font-size: 14px; font-weight: 500;
color: var(--c-text-primary); }
.bsg-menurow__l sub { font-size: 13px; font-weight: 500;
color: var(--c-text-tertiary); }
.bsg-menurow i { font-size: 20px; color: var(--c-icon-dark-gray);
font-style: normal; }
SelectAllMenuRow / CopyAllMenuRow · ui/components/Lists/ — strings are verbatim: "Select all items" / "Add to your action bar" and "Copy all items" / "Copy to clipboard"GroupHeading · ui/components/GroupHeading.vue — cover emoji, title, caret, hairline divider running to the ⋯ menuGroupSidebarButton · ui/components/GroupSidebarButton.vue — 48px row, 10px radius, active takes --c-surface-highlight.bsg-grid · two-axis emoji wall, auto-fill 48px tracks (R10)<div class="bsg-group">
<img src=".../64/2764.png" alt="">
<span class="bsg-group__t">Favorites</span>
<span class="bsg-caret">▼</span>
<span class="bsg-group__rule"></span>
<span class="bsg-group__dots">⋯</span>
</div>
<div class="bsg-grid"><!-- fifteen cells --></div>
<div class="bsg-menu" style="width:300px">
<div class="bsg-menurow"><span class="bsg-menurow__l">
<label>Select all items</label>
<sub>Add to your action bar</sub></span><i>🗂</i></div>
<div class="bsg-menurow"><span class="bsg-menurow__l">
<label>Copy all items</label>
<sub>Copy to clipboard</sub></span><i>⧉</i></div>
</div>
<!-- emoji base: https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/<codepoint>.png -->
<ActionMenuRow label="Select all items"
sub="Add to your action bar" size="sm">
<template #end><Icon name="folder" weight="bold" /></template>
</ActionMenuRow>
<ActionMenuRow label="Copy all items"
sub="Copy to clipboard" size="sm">
<template #end><Icon name="copy" weight="bold" /></template>
</ActionMenuRow>
<!-- both morph the menu to a success panel: "Selected!" / "Copied!" -->
.bsg-group { display: flex; align-items: center; gap: 16px;
min-height: 28px; padding: 4px 0; }
.bsg-group img { width: 20px; height: 20px; }
.bsg-group__t { font-size: 14px; font-weight: 700;
color: var(--c-text-tertiary); }
.bsg-group__rule { flex: 1 1 auto; height: 1px;
background: var(--c-border-primary); }
.bsg-group__dots { color: var(--c-icon-gray); font-size: 18px;
letter-spacing: 1px; }
.bsg-sidelabel { display: flex; align-items: center; gap: 16px;
min-height: 28px; margin-top: 12px;
font-size: 12px; font-weight: 500;
color: var(--c-text-tertiary); }
.bsg-sidelabel i { flex: 1 1 auto; height: 1px;
background: var(--c-border-primary); }
.bsg-siderow { display: flex; height: 48px; align-items: center;
gap: 12px; padding-left: 12px; border-radius: 10px;
font-size: 14px; font-weight: 500;
color: var(--c-text-primary); }
.bsg-siderow--on { background: var(--c-surface-highlight); }
.bsg-siderow img { width: 20px; height: 20px; }
GroupHeading · ui/components/GroupHeading.vue — a followed collection renders as its own group on My Emoji, identical chrome to a list you madeGroupSidebarButton · ui/components/GroupSidebarButton.vue — followed collections get their own "Collections" section in the jump nav.bsg-grid · two-axis emoji wall, auto-fill 48px tracks (R10)<div class="bsg-coll">
<div class="bsg-coll__head">
<span class="bsg-coll__lead"><img src=".../64/26a1.png" alt=""></span>
<span>
<span class="bsg-coll__name">Quick Reactions</span><br>
<span class="bsg-coll__meta">37 emojis</span>
</span>
</div>
<div class="bsg-coll__status">
<span class="bsg-btn-sm bsg-btn-sm--on">✓ Following</span>
</div>
</div>
<!-- emoji base: https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/<codepoint>.png -->
<div class="collection-main from__grid">
<div class="__header-wrapper">
<div class="leading"><div class="emoji jp-font">⚡</div></div>
<div class="about">
<Text type="body/l-heavy">Quick Reactions</Text>
<Text type="body/s-med">37 emojis</Text>
</div>
</div>
<div class="status">
<SmallButton variant="gray" icon="plus" label="Follow" />
<!-- followed: variant="green" icon="check" label="Following" -->
</div>
</div>
.bsg-coll { display: flex; flex-direction: column; padding: 16px;
border-radius: 8px;
border: 1px solid var(--c-border-primary);
background: var(--c-surface-primary); }
.bsg-coll__head { display: grid; grid-template-columns: 64px 1fr;
gap: 16px; }
.bsg-coll__lead { width: 64px; height: 64px; padding: 8px;
display: flex; align-items: center;
justify-content: center; border-radius: 8px;
background: var(--c-surface-tertiary); }
.bsg-coll__lead img { width: 40px; height: 40px; }
.bsg-coll__name { font-size: 17px; font-weight: 700;
color: var(--c-text-primary); }
.bsg-coll__meta { font-size: 12px; font-weight: 500;
color: var(--c-text-tertiary); }
.bsg-coll__status { display: flex; align-self: flex-end;
position: relative; top: -60px;
margin-bottom: -30px; }
.bsg-btn-sm { display: inline-flex; align-items: center; gap: 6px;
height: 33px; padding: 0 14px; border-radius: 10rem;
font-size: 13px; font-weight: 700;
background: var(--c-button-gray-bg-2);
color: var(--c-text-primary); }
.bsg-btn-sm--on { background: var(--c-button-green-bg);
color: var(--green-900); }
Same lists, both deviceshandoff/seo-website-pages.html, so the two sets read as one system<div class="bsg-stage" style="background: /* bloom, see CSS */">
<div class="bsg-sync__pane" style="left:96px;top:150px;
width:620px;
transform:rotate(-3deg)">
<div class="bsg-sync__head">
<img src=".../64/2764.png" alt=""><span>Favorites</span>
</div>
<div class="bsg-sync__grid"><!-- ten imgs --></div>
</div>
<div class="bsg-sync__pane" style="right:110px;top:250px;
width:400px;
transform:rotate(4deg)">
<!-- the same list, same order -->
</div>
<div class="bsg-sync__tag">
<img src=".../64/1f504.png" alt="">Same lists, both devices
</div>
</div>
<!-- emoji base: https://cdn.jsdelivr.net/joypixels/assets/11.0/png/unicode/64/<codepoint>.png -->
<PopTip :offset="[0, 0]">
<IconButton v-if="surface.need_synced && !syncing"
size="md!" shape="rounded" icon="arrows-clockwise"
@click="beginSync(); emit('sync')" />
<template #pop>
<TipMessage><span>Fetch updated recents</span></TipMessage>
</template>
</PopTip>
<IconButton v-if="syncing" size="md!" icon="loader-circle" :disabled="true" />
<!-- that is all of it: one conditional header button and a
1-second spinner. There is nothing else to photograph. -->
/* the shared house backdrop, identical to the SEO share images */
.bsg-stage {
background:
radial-gradient(46% 62% at 77% 44%, var(--green-300) 0%, transparent 72%),
radial-gradient(42% 54% at 4% 96%, var(--metal-50) 0%, transparent 70%),
var(--white);
}
.bsg-sync__pane { position: absolute; background: var(--white);
border: 1px solid var(--metal-100); border-radius: 16px;
box-shadow: 0 24px 50px -16px rgba(15,16,18,.25),
0 3px 10px rgba(15,16,18,.07);
overflow: hidden; }
.bsg-sync__head { display: flex; align-items: center; gap: 10px;
padding: 14px 18px;
border-bottom: 1px solid var(--metal-100); }
.bsg-sync__head img { width: 22px; height: 22px; }
.bsg-sync__head span { font-size: 15px; font-weight: 700;
color: var(--metal-950); }
.bsg-sync__grid { display: grid; grid-template-columns: repeat(5, 1fr);
gap: 12px; padding: 16px 18px 20px; }
.bsg-sync__grid img { width: 34px; height: 34px; justify-self: center; }
.bsg-sync__tag { position: absolute; display: inline-flex;
align-items: center; gap: 8px; height: 40px;
padding: 0 18px; border-radius: 999px;
background: var(--green-300); color: var(--green-900);
font-size: 15px; font-weight: 700;
box-shadow: 0 24px 50px -16px rgba(15,16,18,.25),
0 3px 10px rgba(15,16,18,.07); }
.bsg-sync__tag img { width: 20px; height: 20px; }