first-commit
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
ci / Validate workspace (push) Has been cancelled
landing-page-ci / Validate landing page (push) Has been cancelled
landing-page-deploy / Deploy landing page (push) Has been cancelled
github-metrics / Generate repository metrics SVG (push) Has been cancelled
refresh-contributors-wall / Refresh contributors wall cache bust (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
---
|
||||
name: mobile-app
|
||||
description: |
|
||||
A mobile-app screen rendered inside a pixel-accurate iPhone 15 Pro frame
|
||||
on the page. Built by copying the seed `assets/template.html` and pasting
|
||||
one screen archetype from `references/layouts.md`. Use when the brief asks
|
||||
for "mobile app", "iOS app", "Android app", "phone screen", or "app UI".
|
||||
triggers:
|
||||
- "mobile app"
|
||||
- "ios app"
|
||||
- "android app"
|
||||
- "phone screen"
|
||||
- "app ui"
|
||||
- "app mockup"
|
||||
- "移动端"
|
||||
- "手机 app"
|
||||
od:
|
||||
mode: prototype
|
||||
platform: mobile
|
||||
scenario: design
|
||||
preview:
|
||||
type: html
|
||||
entry: index.html
|
||||
design_system:
|
||||
requires: true
|
||||
sections: [color, typography, layout, components]
|
||||
---
|
||||
|
||||
# Mobile App Skill
|
||||
|
||||
Produce a single mobile-app screen mockup, framed inside a real-feeling iPhone 15 Pro device.
|
||||
|
||||
## Resource map
|
||||
|
||||
```
|
||||
mobile-app/
|
||||
├── SKILL.md ← you're reading this
|
||||
├── assets/
|
||||
│ └── template.html ← seed: device frame + screen primitives (READ FIRST)
|
||||
└── references/
|
||||
├── layouts.md ← 6 screen archetypes (Feed / Detail / Onboarding / Profile / Checkout / Focus)
|
||||
└── checklist.md ← P0/P1/P2 self-review (anti-fake-device)
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
### Step 0 — Pre-flight
|
||||
|
||||
1. **Read `assets/template.html`** end-to-end through the `<style>` block. The Dynamic Island, status bar SVG icons, home indicator, side rails, and tab bar are all already drawn in HTML/SVG — do not re-implement them inline on each screen.
|
||||
2. **Read `references/layouts.md`** so you know which 6 archetypes exist.
|
||||
3. **Read the active DESIGN.md** — map its tokens to the six `:root` variables in the seed.
|
||||
|
||||
### Step 1 — Copy the seed
|
||||
|
||||
Copy `assets/template.html` to the project root as `index.html`. Replace the six `:root` variables with the active design system's tokens. Replace the page `<title>` and the caption above the device.
|
||||
|
||||
### Step 2 — Pick exactly one archetype
|
||||
|
||||
| Brief language | Use |
|
||||
|---|---|
|
||||
| feed, inbox, timeline, list, messages, notifications | A — Feed |
|
||||
| article, post, item, recipe, song, product, song detail | B — Detail |
|
||||
| sign-up, welcome, intro, walkthrough, tour | C — Onboarding |
|
||||
| profile, account, user page, someone's bio | D — Profile |
|
||||
| checkout, payment, order, form, settings step | E — Checkout |
|
||||
| timer, map, dashboard widget, single big number | F — Focus / hero card |
|
||||
|
||||
A mobile screen does **one job**. If the brief seems to combine two, ship one screen and offer the other as a follow-up.
|
||||
|
||||
### Step 3 — Paste and fill
|
||||
|
||||
Copy the archetype block from `layouts.md` into `<main class="content">`, replacing the placeholder card. Fill bracketed text with real, specific copy from the brief. **Drop the `<nav class="tabbar">` block entirely** for archetypes that don't show one (B, C, E).
|
||||
|
||||
### Step 4 — Self-check
|
||||
|
||||
Run through `references/checklist.md`. Pay extra attention to:
|
||||
- Frame still has the Dynamic Island, status bar SVGs, and home indicator
|
||||
- Tap targets ≥ 44px
|
||||
- One accent, used ≤ 2× on the screen
|
||||
- Display headings still use `var(--font-display)` (serif)
|
||||
|
||||
### Step 5 — Emit the artifact
|
||||
|
||||
```
|
||||
<artifact identifier="mobile-slug" type="text/html" title="Mobile — Screen Name">
|
||||
<!doctype html>
|
||||
<html>...</html>
|
||||
</artifact>
|
||||
```
|
||||
|
||||
One sentence before describing what's there. Stop after `</artifact>`.
|
||||
|
||||
## Hard rules
|
||||
|
||||
- **The phone is real.** Dynamic Island gap, SVG status icons, home indicator. The seed protects all three — don't rewrite the frame.
|
||||
- **Single screen, single job.** No multi-tab tours, no spliced flows.
|
||||
- **Accent budget = 2.** One active tab + one primary action is the default.
|
||||
- **Numerics in mono** via `.num` class.
|
||||
- **Display in serif** via `var(--font-display)`.
|
||||
- **No external images** — use `.ph-img` placeholders.
|
||||
@@ -0,0 +1,442 @@
|
||||
<!doctype html>
|
||||
<!--
|
||||
OD mobile-app seed.
|
||||
|
||||
A pixel-accurate iPhone 15 Pro frame (390 × 844) with Dynamic Island,
|
||||
status-bar SVG icons, and home indicator — drawn entirely in HTML/SVG, no
|
||||
external image. The screen content lives inside `<main class="screen">`;
|
||||
paste in one of the layouts from `references/layouts.md`.
|
||||
|
||||
Tokens at the top of `<style>` mirror the web-prototype seed so a single
|
||||
DESIGN.md flows into both. Mobile spacing is tighter (~25%) and type sizes
|
||||
drop one step from desktop — all pre-applied here.
|
||||
-->
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>[REPLACE] Screen name · brand</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafaf7;
|
||||
--surface: #ffffff;
|
||||
--fg: #1a1916;
|
||||
--muted: #6b6964;
|
||||
--border: #e8e5df;
|
||||
--accent: #c96442;
|
||||
|
||||
--accent-soft: color-mix(in oklch, var(--accent) 14%, transparent);
|
||||
--fg-soft: color-mix(in oklch, var(--fg) 6%, transparent);
|
||||
|
||||
--font-display: 'Iowan Old Style', 'Charter', Georgia, serif;
|
||||
--font-body: -apple-system, BlinkMacSystemFont, 'SF Pro Text', system-ui, sans-serif;
|
||||
--font-mono: ui-monospace, 'SF Mono', Menlo, monospace;
|
||||
|
||||
/* mobile type — one step down from web-prototype defaults */
|
||||
--fs-h1: 26px;
|
||||
--fs-h2: 20px;
|
||||
--fs-h3: 16px;
|
||||
--fs-body: 15px;
|
||||
--fs-meta: 12px;
|
||||
|
||||
--radius-card: 18px;
|
||||
--radius-pill: 999px;
|
||||
}
|
||||
|
||||
*, *::before, *::after { box-sizing: border-box; }
|
||||
html, body { margin: 0; padding: 0; height: 100%; }
|
||||
body {
|
||||
background:
|
||||
radial-gradient(60% 80% at 50% 0%, color-mix(in oklch, var(--accent) 6%, var(--bg)) 0%, var(--bg) 60%);
|
||||
color: var(--fg);
|
||||
font-family: var(--font-body);
|
||||
font-size: var(--fs-body);
|
||||
line-height: 1.4;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
/* ─── caption above the device ──────────────────────────────────── */
|
||||
.stage {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 24px;
|
||||
}
|
||||
.caption {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
}
|
||||
.caption strong { color: var(--fg); font-weight: 500; }
|
||||
|
||||
/* ─── device frame ──────────────────────────────────────────────── */
|
||||
.device {
|
||||
position: relative;
|
||||
width: 390px;
|
||||
height: 844px;
|
||||
border-radius: 56px;
|
||||
padding: 12px;
|
||||
background:
|
||||
linear-gradient(160deg, #2a2a2c 0%, #1a1a1c 50%, #0e0e10 100%);
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255,255,255,0.04) inset,
|
||||
0 0 0 2px #000 inset,
|
||||
0 28px 60px -12px rgba(0,0,0,0.45),
|
||||
0 8px 20px -8px rgba(0,0,0,0.35);
|
||||
isolation: isolate;
|
||||
}
|
||||
/* metallic side rails */
|
||||
.device::before, .device::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
width: 3px;
|
||||
background: linear-gradient(to bottom, transparent 0%, rgba(255,255,255,0.06) 8%, transparent 16%, transparent 84%, rgba(255,255,255,0.04) 92%, transparent 100%);
|
||||
top: 100px;
|
||||
bottom: 100px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.device::before { left: -1px; }
|
||||
.device::after { right: -1px; }
|
||||
|
||||
/* Dynamic Island */
|
||||
.island {
|
||||
position: absolute;
|
||||
top: 22px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
width: 124px;
|
||||
height: 36px;
|
||||
background: #000;
|
||||
border-radius: 999px;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
/* hardware buttons (subtle) */
|
||||
.btn-rail {
|
||||
position: absolute;
|
||||
width: 4px;
|
||||
background: #0a0a0c;
|
||||
border-radius: 2px;
|
||||
}
|
||||
.btn-rail.left-1 { left: -3px; top: 174px; height: 32px; } /* silent */
|
||||
.btn-rail.left-2 { left: -3px; top: 220px; height: 60px; } /* vol+ */
|
||||
.btn-rail.left-3 { left: -3px; top: 290px; height: 60px; } /* vol- */
|
||||
.btn-rail.right-1 { right: -3px; top: 250px; height: 100px; } /* power */
|
||||
|
||||
/* ─── screen surface ────────────────────────────────────────────── */
|
||||
.screen {
|
||||
position: relative;
|
||||
width: 100%; height: 100%;
|
||||
background: var(--bg);
|
||||
border-radius: 44px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* Status bar — 47px to clear the island. SF-style time, signal/wifi/battery SVG. */
|
||||
.statusbar {
|
||||
flex: 0 0 47px;
|
||||
padding: 18px 26px 0;
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
font-family: var(--font-body);
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: var(--fg);
|
||||
letter-spacing: -0.01em;
|
||||
}
|
||||
.statusbar .right { display: inline-flex; align-items: center; gap: 6px; }
|
||||
.statusbar svg { width: 17px; height: 11px; fill: var(--fg); }
|
||||
.statusbar .battery { width: 25px; }
|
||||
|
||||
/* Content region — owns its scroll, frame stays still */
|
||||
.content {
|
||||
flex: 1 1 auto;
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
padding: 8px 0 28px;
|
||||
}
|
||||
.content::-webkit-scrollbar { display: none; }
|
||||
|
||||
/* Home indicator (must always be the last visible thing) */
|
||||
.home-indicator {
|
||||
flex: 0 0 28px;
|
||||
position: relative;
|
||||
}
|
||||
.home-indicator::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 50%; bottom: 8px;
|
||||
transform: translateX(-50%);
|
||||
width: 134px; height: 5px;
|
||||
background: var(--fg);
|
||||
border-radius: 999px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
/* ─── screen primitives — used by layouts.md ────────────────────── */
|
||||
.pad { padding-inline: 20px; }
|
||||
.stack { display: flex; flex-direction: column; gap: 16px; }
|
||||
.row { display: flex; align-items: center; gap: 12px; }
|
||||
.row-between { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
|
||||
.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; }
|
||||
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
|
||||
|
||||
.header {
|
||||
padding: 8px 20px 12px;
|
||||
display: flex; align-items: center; justify-content: space-between; gap: 12px;
|
||||
}
|
||||
.header h1 {
|
||||
font-family: var(--font-display);
|
||||
font-size: var(--fs-h1);
|
||||
letter-spacing: -0.02em;
|
||||
line-height: 1.1;
|
||||
margin: 0;
|
||||
}
|
||||
.header .icon-btn {
|
||||
width: 36px; height: 36px;
|
||||
border-radius: 999px;
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
display: grid; place-items: center;
|
||||
color: var(--fg);
|
||||
}
|
||||
.header .icon-btn svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.7; }
|
||||
|
||||
.greeting {
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin: 0 0 4px;
|
||||
}
|
||||
|
||||
.h2 { font-family: var(--font-display); font-size: var(--fs-h2); letter-spacing: -0.015em; line-height: 1.2; margin: 0; }
|
||||
.h3 { font-size: var(--fs-h3); font-weight: 600; line-height: 1.3; margin: 0; }
|
||||
.meta { font-family: var(--font-mono); font-size: var(--fs-meta); color: var(--muted); }
|
||||
.num { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
|
||||
|
||||
/* card */
|
||||
.card {
|
||||
background: var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-card);
|
||||
padding: 16px;
|
||||
}
|
||||
.card.accent {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border-color: transparent;
|
||||
}
|
||||
.card.accent .meta { color: rgba(255,255,255,0.72); }
|
||||
.card.flat { background: transparent; border: 0; padding: 12px 0; border-top: 1px solid var(--border); border-radius: 0; }
|
||||
.card.flat:first-child { border-top: 0; padding-top: 0; }
|
||||
|
||||
/* list row */
|
||||
.list-row {
|
||||
display: grid;
|
||||
grid-template-columns: 40px 1fr auto;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 12px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.list-row:first-child { border-top: 0; }
|
||||
.list-row .avatar {
|
||||
width: 40px; height: 40px;
|
||||
border-radius: 50%;
|
||||
background:
|
||||
linear-gradient(135deg, var(--accent-soft), var(--fg-soft)),
|
||||
var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.list-row .body .title { font-size: 15px; font-weight: 500; line-height: 1.25; }
|
||||
.list-row .body .sub { color: var(--muted); font-size: 13px; line-height: 1.3; margin-top: 2px; }
|
||||
|
||||
/* tab bar */
|
||||
.tabbar {
|
||||
flex: 0 0 auto;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(var(--tabs, 4), 1fr);
|
||||
padding: 8px 8px 0;
|
||||
border-top: 1px solid var(--border);
|
||||
background: color-mix(in oklch, var(--surface) 92%, transparent);
|
||||
backdrop-filter: blur(20px);
|
||||
}
|
||||
.tab {
|
||||
display: flex; flex-direction: column; align-items: center; gap: 2px;
|
||||
padding: 8px 0;
|
||||
color: var(--muted);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.02em;
|
||||
}
|
||||
.tab.active { color: var(--accent); }
|
||||
.tab svg { width: 22px; height: 22px; stroke: currentColor; fill: none; stroke-width: 1.7; }
|
||||
.tab.active svg { stroke-width: 2; }
|
||||
|
||||
/* primary button — full-width, 48px tap target */
|
||||
.btn-primary {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 14px 20px;
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: 0;
|
||||
border-radius: 14px;
|
||||
font: inherit;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
letter-spacing: -0.005em;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-secondary {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
width: 100%;
|
||||
min-height: 48px;
|
||||
padding: 14px 20px;
|
||||
background: transparent;
|
||||
color: var(--fg);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
font: inherit;
|
||||
font-size: 15px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* image placeholder */
|
||||
.ph-img {
|
||||
background:
|
||||
linear-gradient(135deg, var(--accent-soft), var(--fg-soft)),
|
||||
var(--surface);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 14px;
|
||||
aspect-ratio: 4 / 3;
|
||||
display: grid; place-items: center;
|
||||
color: var(--muted);
|
||||
font-family: var(--font-mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.ph-img.square { aspect-ratio: 1 / 1; }
|
||||
.ph-img.wide { aspect-ratio: 16 / 9; }
|
||||
|
||||
/* pill / tag */
|
||||
.pill {
|
||||
display: inline-flex; align-items: center; gap: 4px;
|
||||
padding: 4px 10px;
|
||||
background: var(--accent-soft);
|
||||
color: var(--accent);
|
||||
border-radius: 999px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 10px;
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
.tag {
|
||||
display: inline-flex;
|
||||
padding: 3px 9px;
|
||||
background: transparent;
|
||||
color: var(--muted);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* progress */
|
||||
.progress { height: 6px; background: rgba(255,255,255,0.25); border-radius: 999px; overflow: hidden; }
|
||||
.progress > span { display: block; height: 100%; background: #fff; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="stage">
|
||||
<div class="caption"><strong>[REPLACE] App</strong> · [REPLACE] Screen name</div>
|
||||
|
||||
<div class="device" data-od-id="device">
|
||||
<span class="btn-rail left-1" aria-hidden></span>
|
||||
<span class="btn-rail left-2" aria-hidden></span>
|
||||
<span class="btn-rail left-3" aria-hidden></span>
|
||||
<span class="btn-rail right-1" aria-hidden></span>
|
||||
<span class="island" aria-hidden></span>
|
||||
|
||||
<div class="screen">
|
||||
<!-- ─── Status bar ─── -->
|
||||
<div class="statusbar">
|
||||
<span class="num">9:41</span>
|
||||
<span class="right">
|
||||
<!-- signal -->
|
||||
<svg viewBox="0 0 17 11" aria-hidden>
|
||||
<rect x="0" y="7" width="3" height="4" rx="0.6"/>
|
||||
<rect x="4" y="5" width="3" height="6" rx="0.6"/>
|
||||
<rect x="8" y="3" width="3" height="8" rx="0.6"/>
|
||||
<rect x="12" y="0" width="3" height="11" rx="0.6"/>
|
||||
</svg>
|
||||
<!-- wifi -->
|
||||
<svg viewBox="0 0 17 11" aria-hidden>
|
||||
<path d="M8.5 1.5C5.5 1.5 2.7 2.6 0.5 4.6L2 6.1C3.8 4.5 6.1 3.6 8.5 3.6c2.4 0 4.7 0.9 6.5 2.5l1.5-1.5c-2.2-2-5-3.1-8-3.1zM3.5 7.6L5 9.1c1-0.9 2.2-1.4 3.5-1.4 1.3 0 2.5 0.5 3.5 1.4l1.5-1.5c-1.4-1.3-3.1-2-5-2-1.9 0-3.6 0.7-5 2zM6.5 10.6l2 2 2-2c-0.5-0.5-1.2-0.8-2-0.8s-1.5 0.3-2 0.8z"/>
|
||||
</svg>
|
||||
<!-- battery -->
|
||||
<svg class="battery" viewBox="0 0 25 11" aria-hidden>
|
||||
<rect x="0.5" y="0.5" width="21" height="10" rx="2.5" fill="none" stroke="currentColor" stroke-opacity="0.45"/>
|
||||
<rect x="22" y="3.5" width="1.5" height="4" rx="0.4" fill="currentColor" fill-opacity="0.45"/>
|
||||
<rect x="2" y="2" width="18" height="7" rx="1.4"/>
|
||||
</svg>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- ─── Scrollable content (paste a layout from references/layouts.md HERE) ─── -->
|
||||
<main class="content" data-od-id="content">
|
||||
<div class="header" data-od-id="header">
|
||||
<div>
|
||||
<p class="greeting">Tuesday · April 22</p>
|
||||
<h1>[REPLACE] Hi there.</h1>
|
||||
</div>
|
||||
<button class="icon-btn" aria-label="Settings">
|
||||
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.7 1.7 0 0 0 .3 1.8l.1.1a2 2 0 1 1-2.8 2.8l-.1-.1a1.7 1.7 0 0 0-1.8-.3 1.7 1.7 0 0 0-1 1.5V21a2 2 0 1 1-4 0v-.1a1.7 1.7 0 0 0-1.1-1.5 1.7 1.7 0 0 0-1.8.3l-.1.1a2 2 0 1 1-2.8-2.8l.1-.1a1.7 1.7 0 0 0 .3-1.8 1.7 1.7 0 0 0-1.5-1H3a2 2 0 1 1 0-4h.1a1.7 1.7 0 0 0 1.5-1.1 1.7 1.7 0 0 0-.3-1.8l-.1-.1a2 2 0 1 1 2.8-2.8l.1.1a1.7 1.7 0 0 0 1.8.3H9a1.7 1.7 0 0 0 1-1.5V3a2 2 0 1 1 4 0v.1a1.7 1.7 0 0 0 1 1.5 1.7 1.7 0 0 0 1.8-.3l.1-.1a2 2 0 1 1 2.8 2.8l-.1.1a1.7 1.7 0 0 0-.3 1.8V9a1.7 1.7 0 0 0 1.5 1H21a2 2 0 1 1 0 4h-.1a1.7 1.7 0 0 0-1.5 1z"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="pad stack" data-od-id="empty-slot">
|
||||
<div class="card" style="text-align: center; padding: 28px 20px;">
|
||||
<p class="meta" style="margin: 0 0 6px;">PASTE A LAYOUT FROM</p>
|
||||
<p class="h3" style="margin: 0 0 6px;">references/layouts.md</p>
|
||||
<p style="margin: 0; color: var(--muted); font-size: 13px;">into <code style="font-family: var(--font-mono);"><main class="content"></code></p>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- ─── Tab bar (drop if the screen kind doesn't have one) ─── -->
|
||||
<nav class="tabbar" style="--tabs: 4;" data-od-id="tabbar">
|
||||
<a class="tab active">
|
||||
<svg viewBox="0 0 24 24"><path d="M3 12 12 3l9 9"/><path d="M5 10v10h14V10"/></svg>
|
||||
Home
|
||||
</a>
|
||||
<a class="tab">
|
||||
<svg viewBox="0 0 24 24"><circle cx="11" cy="11" r="7"/><path d="m21 21-4.3-4.3"/></svg>
|
||||
Search
|
||||
</a>
|
||||
<a class="tab">
|
||||
<svg viewBox="0 0 24 24"><path d="M22 12c0 5.5-4.5 10-10 10S2 17.5 2 12 6.5 2 12 2s10 4.5 10 10z"/><path d="M12 6v6l4 2"/></svg>
|
||||
Activity
|
||||
</a>
|
||||
<a class="tab">
|
||||
<svg viewBox="0 0 24 24"><circle cx="12" cy="8" r="4"/><path d="M4 21c0-4 4-7 8-7s8 3 8 7"/></svg>
|
||||
Profile
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<div class="home-indicator" aria-hidden></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,92 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<title>Tomato — focus screen</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #fafaf9; --fg: #1c1b1a; --muted: #6b6964; --border: #e6e4e0;
|
||||
--accent: #c96442; --surface: #ffffff;
|
||||
}
|
||||
* { box-sizing: border-box; }
|
||||
body { margin: 0; min-height: 100vh; background: var(--bg); display: flex; align-items: center; justify-content: center; padding: 32px; font: 14px/1.5 -apple-system, system-ui, sans-serif; color: var(--fg); }
|
||||
.frame { width: 390px; height: 844px; background: black; border-radius: 56px; padding: 12px; box-shadow: 0 20px 60px rgba(0,0,0,0.18); position: relative; }
|
||||
.frame::before { content: ''; position: absolute; top: 22px; left: 50%; transform: translateX(-50%); width: 124px; height: 36px; background: black; border-radius: 999px; z-index: 5; }
|
||||
.screen { width: 100%; height: 100%; background: var(--bg); border-radius: 44px; overflow: hidden; display: flex; flex-direction: column; }
|
||||
.status { padding: 14px 24px 6px; display: flex; justify-content: space-between; align-items: center; font-size: 14px; font-weight: 600; }
|
||||
.status .right { display: flex; gap: 6px; align-items: center; }
|
||||
.header { padding: 56px 24px 24px; }
|
||||
.header .greeting { color: var(--muted); font-size: 14px; margin: 0 0 4px; }
|
||||
.header h1 { margin: 0; font-size: 22px; letter-spacing: -0.01em; }
|
||||
.timer-card { margin: 12px 24px; background: var(--accent); color: white; border-radius: 24px; padding: 28px 24px; text-align: center; }
|
||||
.timer-card .label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.08em; opacity: 0.85; margin: 0 0 4px; }
|
||||
.timer-card .countdown { font-size: 64px; line-height: 1; letter-spacing: -0.03em; font-weight: 600; margin: 6px 0 18px; font-variant-numeric: tabular-nums; }
|
||||
.timer-card .progress { height: 6px; background: rgba(255,255,255,0.25); border-radius: 999px; overflow: hidden; margin-bottom: 16px; }
|
||||
.timer-card .progress > span { display: block; width: 38%; height: 100%; background: white; }
|
||||
.timer-card .actions { display: flex; gap: 10px; justify-content: center; }
|
||||
.timer-card button { font: inherit; cursor: pointer; padding: 10px 22px; border-radius: 999px; border: 1px solid rgba(255,255,255,0.4); background: rgba(255,255,255,0.12); color: white; font-weight: 500; }
|
||||
.timer-card button.primary { background: white; color: var(--accent); border-color: white; }
|
||||
.section { padding: 18px 24px 0; }
|
||||
.section .label { font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; color: var(--muted); margin: 0 0 10px; }
|
||||
.stats { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
|
||||
.stat { background: var(--surface); border: 1px solid var(--border); border-radius: 14px; padding: 12px; }
|
||||
.stat .v { font-size: 22px; letter-spacing: -0.01em; line-height: 1; margin-bottom: 4px; }
|
||||
.stat .l { font-size: 11px; color: var(--muted); }
|
||||
.tasks { padding: 18px 24px 8px; }
|
||||
.task { display: flex; align-items: center; gap: 12px; padding: 14px 0; border-top: 1px solid var(--border); }
|
||||
.task:first-child { border-top: none; }
|
||||
.check { width: 20px; height: 20px; border: 1.5px solid var(--border); border-radius: 50%; flex-shrink: 0; }
|
||||
.task.done .check { background: var(--accent); border-color: var(--accent); }
|
||||
.task.done .check::after { content: '✓'; color: white; font-size: 13px; display: block; text-align: center; line-height: 18px; }
|
||||
.task .body { flex: 1; }
|
||||
.task .title { font-size: 14.5px; line-height: 1.3; }
|
||||
.task.done .title { color: var(--muted); text-decoration: line-through; }
|
||||
.task .meta { font-size: 11px; color: var(--muted); margin-top: 2px; }
|
||||
.tabbar { margin-top: auto; display: grid; grid-template-columns: repeat(4, 1fr); padding: 10px 16px 28px; border-top: 1px solid var(--border); background: var(--surface); }
|
||||
.tab { text-align: center; color: var(--muted); font-size: 11px; padding: 6px 0; }
|
||||
.tab.active { color: var(--accent); font-weight: 500; }
|
||||
.tab .icon { font-size: 18px; line-height: 1; margin-bottom: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="frame" data-od-id="frame">
|
||||
<div class="screen">
|
||||
<div class="status"><span>9:41</span><span class="right">·· 5G · 100%</span></div>
|
||||
<div class="header" data-od-id="header">
|
||||
<p class="greeting">Tuesday · April 22</p>
|
||||
<h1>Two pomodoros to lunch.</h1>
|
||||
</div>
|
||||
<div class="timer-card" data-od-id="timer">
|
||||
<p class="label">Focus session</p>
|
||||
<div class="countdown">15:42</div>
|
||||
<div class="progress"><span></span></div>
|
||||
<div class="actions">
|
||||
<button>Skip</button>
|
||||
<button class="primary">Pause</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="section" data-od-id="stats">
|
||||
<p class="label">Today</p>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="v">3</div><div class="l">Sessions</div></div>
|
||||
<div class="stat"><div class="v">75m</div><div class="l">Focused</div></div>
|
||||
<div class="stat"><div class="v">2</div><div class="l">Tasks done</div></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tasks" data-od-id="tasks">
|
||||
<p class="section label" style="padding: 0;">Up next</p>
|
||||
<div class="task done"><div class="check"></div><div class="body"><div class="title">Review Q2 OKRs</div><div class="meta">25m · completed</div></div></div>
|
||||
<div class="task"><div class="check"></div><div class="body"><div class="title">Draft sync-engine post</div><div class="meta">2 sessions estimated</div></div></div>
|
||||
<div class="task"><div class="check"></div><div class="body"><div class="title">1:1 prep with Mira</div><div class="meta">1 session</div></div></div>
|
||||
</div>
|
||||
<nav class="tabbar" data-od-id="tabbar">
|
||||
<div class="tab active"><div class="icon">⏱</div>Focus</div>
|
||||
<div class="tab"><div class="icon">✓</div>Tasks</div>
|
||||
<div class="tab"><div class="icon">📊</div>Stats</div>
|
||||
<div class="tab"><div class="icon">⚙</div>Settings</div>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,46 @@
|
||||
# Mobile app checklist
|
||||
|
||||
Run this before emitting `<artifact>`. P0 must pass.
|
||||
|
||||
## P0 — must pass
|
||||
|
||||
- [ ] **Frame looks like a phone, not a generic card.** Dynamic Island visible, status bar SVG icons present (signal/wifi/battery), home indicator at bottom. The seed already does this — verify you didn't accidentally delete the island/rails/indicator markup.
|
||||
- [ ] **Status bar shows real glyphs**, not text like `· · · 5G · 100%`. Use the SVG icons from the seed.
|
||||
- [ ] **Home indicator is the last visible thing.** Anything below it (e.g. extra padding, accidental `<div>`) breaks the illusion.
|
||||
- [ ] **Content scrolls, frame doesn't.** `<main class="content">` has `overflow-y: auto`; the surrounding `.device` does not. The page background never moves.
|
||||
- [ ] **Tap targets ≥ 44px tall.** The seed's `.btn-primary` (48px), `.tab` (~50px), `.icon-btn` (36px ≥ touch with padding), `.list-row` (≥48px with padding) all pass. Don't ship a button under 44px.
|
||||
- [ ] **Body text ≥ 14px.** `--fs-body: 15px` already enforces this on most copy. List-row sub text uses 13px max — that's the floor.
|
||||
- [ ] **One accent, used at most twice on the screen.** Typically: one active tab + one CTA, OR one accent card + one tab. Never three.
|
||||
- [ ] **No external image URLs.** Use the `.ph-img` placeholder class. External CDN images break the OD preview iframe and look fake when they 404.
|
||||
- [ ] **Tab bar matches the screen kind.** Onboarding / detail / checkout: drop the `<nav class="tabbar">` entirely. Feed / focus / profile: keep it.
|
||||
- [ ] **Display headlines use `var(--font-display)` (serif).** The seed binds this via `.h1`, `.h2`, `.header h1`. Don't override headings to system-sans — it instantly looks like a stock template.
|
||||
- [ ] **No emoji icons in the UI.** SVG monoline only. Emoji in copy is fine ("9:41 ☀️ Tuesday" is not, but "Sunny day in Berlin" is).
|
||||
- [ ] **`data-od-id` on the device, content, header, and any major sections.**
|
||||
|
||||
## P1 — should pass
|
||||
|
||||
- [ ] **One screen, one job.** A profile screen does profile things. Don't graft a checkout form onto a feed.
|
||||
- [ ] **Caption above the device** names the screen (e.g. "FILEBASE · INBOX"). The seed already has the slot — fill it.
|
||||
- [ ] **Status bar time is `9:41`** (Apple convention) unless the brief asks otherwise.
|
||||
- [ ] **Mono font for numerics** — counts, prices, durations, dates. The seed's `.num` class binds this.
|
||||
- [ ] **Real, specific copy.** "Mira Hassan · CTO" beats "User Name". "$1,920" beats "$X,XXX".
|
||||
- [ ] **First-screen content fits inside the 844px frame** without requiring scroll for the primary action. If the CTA is below the fold, it's the wrong layout.
|
||||
|
||||
## P2 — nice to have
|
||||
|
||||
- [ ] **Subtle accent radial gradient on the page background** (already in seed). Removing it makes the device feel pasted onto a flat sheet.
|
||||
- [ ] **Backdrop-blurred tab bar** (already in seed via `backdrop-filter`).
|
||||
- [ ] **At most one image placeholder per screen.** Two placeholders on a small canvas competes for attention.
|
||||
- [ ] **Subtle metallic side rails on the bezel** (already in seed via `::before`/`::after`).
|
||||
|
||||
## Anti-fake-device checklist
|
||||
|
||||
If any of these are true, the screen looks like a *card pretending to be a phone* rather than a phone:
|
||||
|
||||
- The device's outer corners aren't visibly more rounded (~56px) than the inner screen (~44px).
|
||||
- There's no Dynamic Island gap at the top centre.
|
||||
- The status bar text is grey or low-opacity (it should be `var(--fg)` at full strength).
|
||||
- The home indicator is missing.
|
||||
- The bottom tab bar has no top border or no backdrop blur.
|
||||
|
||||
The seed prevents all of these — the most common regression is the agent rewriting the frame with `border-radius: 24px` and losing the depth.
|
||||
@@ -0,0 +1,312 @@
|
||||
# Mobile app layouts
|
||||
|
||||
**6 paste-ready screen archetypes.** Drop into `<main class="content">` of `assets/template.html`. Don't write screens from scratch — pick the closest archetype, paste, swap copy.
|
||||
|
||||
## Pre-flight
|
||||
|
||||
1. **Read `assets/template.html`** at minimum through the `<style>` block — every class below is defined there. The Dynamic Island, status bar, home indicator, and tab bar are already drawn; do not re-implement them inline.
|
||||
2. **Pick exactly one archetype.** A mobile screen does one job. Mixing "feed + checkout + profile" into one mock is the #1 reason mobile prototypes feel fake.
|
||||
3. **If the archetype implies a tab bar, keep it; otherwise delete the entire `<nav class="tabbar">` block.** Onboarding, detail, and checkout screens generally don't show one.
|
||||
|
||||
## Class inventory
|
||||
|
||||
> `pad` `stack` `row` `row-between` `grid-2` `grid-3` `header` `greeting` `h2` `h3` `meta` `num` `card` `card.accent` `card.flat` `list-row` `avatar` `tag` `pill` `tabbar` `tab` `tab.active` `btn-primary` `btn-secondary` `ph-img` `progress`
|
||||
|
||||
If you reach for a class not on this list, define it in the seed's `<style>` first.
|
||||
|
||||
---
|
||||
|
||||
## Archetype A — Feed (home / for-you / inbox)
|
||||
|
||||
Top: greeting + title. Body: 4–6 list rows, hairline-separated. Tab bar: yes.
|
||||
|
||||
```html
|
||||
<div class="header" data-od-id="header">
|
||||
<div>
|
||||
<p class="greeting">Tuesday · April 22</p>
|
||||
<h1>Inbox</h1>
|
||||
</div>
|
||||
<button class="icon-btn" aria-label="Compose">
|
||||
<svg viewBox="0 0 24 24"><path d="M12 5v14M5 12h14"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="pad" data-od-id="filters" style="margin-bottom: 8px;">
|
||||
<div class="row" style="overflow-x: auto; padding-bottom: 4px;">
|
||||
<span class="pill">All · 14</span>
|
||||
<span class="tag">Mentions</span>
|
||||
<span class="tag">Following</span>
|
||||
<span class="tag">Shared</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad" data-od-id="feed">
|
||||
<div class="list-row">
|
||||
<div class="avatar"></div>
|
||||
<div class="body">
|
||||
<div class="title">Mira Hassan · Sync engine v3 review</div>
|
||||
<div class="sub">"Merged the chunker — egress is down 38% on Northwind."</div>
|
||||
</div>
|
||||
<span class="meta">2m</span>
|
||||
</div>
|
||||
<div class="list-row">
|
||||
<div class="avatar"></div>
|
||||
<div class="body">
|
||||
<div class="title">#engineering · 7 new replies</div>
|
||||
<div class="sub">Latency spike between 03:40 and 04:10 — probably the cron.</div>
|
||||
</div>
|
||||
<span class="meta">14m</span>
|
||||
</div>
|
||||
<div class="list-row">
|
||||
<div class="avatar"></div>
|
||||
<div class="body">
|
||||
<div class="title">Northwind Studios · Invoice paid</div>
|
||||
<div class="sub">$2,184 · April · auto-receipt sent to billing@</div>
|
||||
</div>
|
||||
<span class="meta">1h</span>
|
||||
</div>
|
||||
<div class="list-row">
|
||||
<div class="avatar"></div>
|
||||
<div class="body">
|
||||
<div class="title">Daniel Park · Re: Next Tuesday's review</div>
|
||||
<div class="sub">"I'll have the Q2 numbers by Monday EOD."</div>
|
||||
</div>
|
||||
<span class="meta">3h</span>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
## Archetype B — Detail (single item)
|
||||
|
||||
Hero image up top, eyebrow + title + meta, body text, primary action floating at the bottom. Tab bar: no.
|
||||
|
||||
```html
|
||||
<div class="ph-img wide" style="border-radius: 0; aspect-ratio: 4/3;" data-od-id="hero">[ Hero image ]</div>
|
||||
|
||||
<section class="pad" style="padding-top: 18px;" data-od-id="meta">
|
||||
<span class="pill">Studio session</span>
|
||||
<h1 class="h2" style="margin: 10px 0 6px;">Filebase v3 — what we shipped, what we cut.</h1>
|
||||
<p class="meta">Mira Hassan · April 22 · 9 min read</p>
|
||||
</section>
|
||||
|
||||
<section class="pad stack" style="margin-top: 18px; gap: 14px;" data-od-id="body">
|
||||
<p>The biggest unlock in v3 was the new content-defined chunker. On Final Cut projects, post-edit re-uploads dropped 38× — from full multi-GB pushes to the few hundred KB that actually changed.</p>
|
||||
<p>What we cut: per-folder compression. It looked great on benchmarks; on real footage it was slower than no compression at all because the chunker was already doing the dedup work.</p>
|
||||
<p>Next quarter: dual-region replication on R2 + S3, rolling out to Enterprise first.</p>
|
||||
</section>
|
||||
|
||||
<section class="pad" style="padding-top: 24px; padding-bottom: 8px;" data-od-id="cta">
|
||||
<button class="btn-primary">Save to library</button>
|
||||
</section>
|
||||
```
|
||||
|
||||
## Archetype C — Onboarding (1 of N)
|
||||
|
||||
Illustration block + headline + subhead + paginator + primary CTA. Tab bar: no. Status bar still visible.
|
||||
|
||||
```html
|
||||
<section class="pad stack" style="height: 100%; padding-top: 24px; padding-bottom: 24px; gap: 24px;" data-od-id="onboarding">
|
||||
<div class="ph-img square" style="aspect-ratio: 1/1; max-width: 240px; margin: 0 auto;">[ Illustration ]</div>
|
||||
|
||||
<div style="text-align: center;">
|
||||
<p class="meta" style="margin: 0 0 6px;">STEP 2 OF 4</p>
|
||||
<h1 style="font-family: var(--font-display); font-size: 26px; margin: 0 0 10px; letter-spacing: -0.02em; line-height: 1.15;">Sync only what changed.</h1>
|
||||
<p style="margin: 0 auto; max-width: 26ch; color: var(--muted); font-size: 14px; line-height: 1.5;">No more 4 GB re-uploads when you fix one frame. We diff at the byte level so the network stays quiet.</p>
|
||||
</div>
|
||||
|
||||
<!-- pagination dots -->
|
||||
<div class="row" style="justify-content: center; gap: 6px;">
|
||||
<span style="width: 6px; height: 6px; border-radius: 50%; background: var(--border);"></span>
|
||||
<span style="width: 18px; height: 6px; border-radius: 999px; background: var(--accent);"></span>
|
||||
<span style="width: 6px; height: 6px; border-radius: 50%; background: var(--border);"></span>
|
||||
<span style="width: 6px; height: 6px; border-radius: 50%; background: var(--border);"></span>
|
||||
</div>
|
||||
|
||||
<div class="stack" style="gap: 10px; margin-top: auto;">
|
||||
<button class="btn-primary">Continue</button>
|
||||
<button class="btn-secondary" style="border: 0; color: var(--muted);">Skip</button>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
> Drop the `<nav class="tabbar">` block from the seed for this archetype.
|
||||
|
||||
## Archetype D — Profile (someone's page)
|
||||
|
||||
Avatar + name + meta row; stat row; tabbed content underneath. Tab bar: yes (often the surrounding app's tabs).
|
||||
|
||||
```html
|
||||
<section class="pad" style="padding-top: 8px;" data-od-id="head">
|
||||
<div class="row" style="gap: 16px;">
|
||||
<div class="avatar" style="width: 64px; height: 64px;"></div>
|
||||
<div>
|
||||
<h1 class="h2" style="margin: 0;">Mira Hassan</h1>
|
||||
<p class="meta" style="margin: 4px 0 0;">CTO · Northwind Studios · Joined 2024</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row" style="margin-top: 16px; gap: 8px;">
|
||||
<button class="btn-secondary" style="flex: 1; min-height: 38px; font-size: 13px;">Message</button>
|
||||
<button class="btn-secondary" style="flex: 1; min-height: 38px; font-size: 13px;">Follow</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad" data-od-id="stats" style="margin-top: 18px;">
|
||||
<div class="grid-3">
|
||||
<div class="card flat" style="text-align: center;">
|
||||
<div class="num" style="font-size: 22px; letter-spacing: -0.02em;">218</div>
|
||||
<div class="meta">Posts</div>
|
||||
</div>
|
||||
<div class="card flat" style="text-align: center;">
|
||||
<div class="num" style="font-size: 22px; letter-spacing: -0.02em;">3.1k</div>
|
||||
<div class="meta">Followers</div>
|
||||
</div>
|
||||
<div class="card flat" style="text-align: center;">
|
||||
<div class="num" style="font-size: 22px; letter-spacing: -0.02em;">142</div>
|
||||
<div class="meta">Following</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad" data-od-id="tabs" style="margin-top: 12px;">
|
||||
<div class="row" style="border-bottom: 1px solid var(--border); gap: 24px;">
|
||||
<span style="padding: 12px 0; border-bottom: 2px solid var(--accent); color: var(--fg); font-weight: 500; font-size: 14px;">Posts</span>
|
||||
<span style="padding: 12px 0; color: var(--muted); font-size: 14px;">Replies</span>
|
||||
<span style="padding: 12px 0; color: var(--muted); font-size: 14px;">Likes</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad" data-od-id="post-list" style="margin-top: 4px;">
|
||||
<div class="list-row" style="grid-template-columns: 1fr;">
|
||||
<div class="body">
|
||||
<div class="title">"Bandwidth pricing went up 4× — sync engine choice is no longer cosmetic."</div>
|
||||
<div class="sub" style="margin-top: 6px;">2 days ago · 142 likes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-row" style="grid-template-columns: 1fr;">
|
||||
<div class="body">
|
||||
<div class="title">"Shipped v3 today. The team carried this one."</div>
|
||||
<div class="sub" style="margin-top: 6px;">5 days ago · 88 likes</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
## Archetype E — Checkout / form
|
||||
|
||||
Stacked card sections (item summary → details → totals), bottom-fixed CTA. Tab bar: no.
|
||||
|
||||
```html
|
||||
<section class="pad" style="padding-top: 12px;" data-od-id="title">
|
||||
<h1 class="h2">Confirm order</h1>
|
||||
</section>
|
||||
|
||||
<section class="pad" data-od-id="item">
|
||||
<div class="card row" style="gap: 14px; align-items: flex-start;">
|
||||
<div class="ph-img square" style="width: 64px; height: 64px; aspect-ratio: 1; border-radius: 10px;"></div>
|
||||
<div style="flex: 1;">
|
||||
<div class="h3">Filebase Team · annual</div>
|
||||
<p class="meta" style="margin: 4px 0 0;">$4 / seat / month, billed yearly</p>
|
||||
</div>
|
||||
<span class="num">$1,920</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad stack" data-od-id="details" style="margin-top: 14px; gap: 10px;">
|
||||
<div class="card flat row-between">
|
||||
<span>Seats</span>
|
||||
<span class="num">40</span>
|
||||
</div>
|
||||
<div class="card flat row-between">
|
||||
<span>Billing email</span>
|
||||
<span class="meta">billing@northwind.studio</span>
|
||||
</div>
|
||||
<div class="card flat row-between">
|
||||
<span>Payment</span>
|
||||
<span class="meta">Visa · 4242</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad" data-od-id="totals" style="margin-top: 14px;">
|
||||
<div class="card row-between" style="border-top: 1px solid var(--fg); border-radius: 0; padding: 16px 0; background: transparent;">
|
||||
<span style="font-weight: 600;">Total today</span>
|
||||
<span class="num" style="font-size: 22px; letter-spacing: -0.01em;">$1,920</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad" style="padding-top: 16px; padding-bottom: 12px;" data-od-id="cta">
|
||||
<button class="btn-primary">Pay $1,920</button>
|
||||
<p class="meta" style="text-align: center; margin: 12px 0 0;">By tapping Pay you agree to the terms.</p>
|
||||
</section>
|
||||
```
|
||||
|
||||
## Archetype F — Focus / hero card (timer, map, single tool)
|
||||
|
||||
A single accent-coloured hero card dominates; small supporting content underneath. Tab bar: yes.
|
||||
|
||||
```html
|
||||
<div class="header" data-od-id="header">
|
||||
<div>
|
||||
<p class="greeting">Tuesday · April 22</p>
|
||||
<h1>Two pomodoros to lunch.</h1>
|
||||
</div>
|
||||
<button class="icon-btn" aria-label="Settings">
|
||||
<svg viewBox="0 0 24 24"><circle cx="12" cy="12" r="3"/><circle cx="12" cy="3" r="0.5"/><circle cx="12" cy="21" r="0.5"/><circle cx="3" cy="12" r="0.5"/><circle cx="21" cy="12" r="0.5"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<section class="pad" data-od-id="hero-card" style="margin-top: 4px;">
|
||||
<div class="card accent" style="padding: 28px 24px; text-align: center;">
|
||||
<p class="meta" style="margin: 0 0 6px; color: rgba(255,255,255,0.72);">FOCUS SESSION</p>
|
||||
<div class="num" style="font-size: 64px; line-height: 1; letter-spacing: -0.03em; font-weight: 600; margin: 8px 0 18px;">15:42</div>
|
||||
<div class="progress" style="margin-bottom: 18px;"><span style="width: 38%;"></span></div>
|
||||
<div class="row" style="justify-content: center; gap: 8px;">
|
||||
<button style="padding: 10px 22px; border: 1px solid rgba(255,255,255,0.4); background: rgba(255,255,255,0.12); color: #fff; border-radius: 999px; font: inherit; font-weight: 500;">Skip</button>
|
||||
<button style="padding: 10px 22px; border: 0; background: #fff; color: var(--accent); border-radius: 999px; font: inherit; font-weight: 600;">Pause</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad" data-od-id="stats-row" style="margin-top: 18px;">
|
||||
<p class="meta" style="margin: 0 0 8px;">TODAY</p>
|
||||
<div class="grid-3">
|
||||
<div class="card"><div class="num" style="font-size: 22px;">3</div><div class="meta">Sessions</div></div>
|
||||
<div class="card"><div class="num" style="font-size: 22px;">75m</div><div class="meta">Focused</div></div>
|
||||
<div class="card"><div class="num" style="font-size: 22px;">2</div><div class="meta">Done</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="pad" data-od-id="up-next" style="margin-top: 18px;">
|
||||
<p class="meta" style="margin: 0 0 8px;">UP NEXT</p>
|
||||
<div>
|
||||
<div class="list-row" style="grid-template-columns: 22px 1fr auto;">
|
||||
<span style="width: 18px; height: 18px; border-radius: 50%; background: var(--accent);"></span>
|
||||
<div class="body">
|
||||
<div class="title" style="text-decoration: line-through; color: var(--muted);">Review Q2 OKRs</div>
|
||||
<div class="sub">25m · completed</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-row" style="grid-template-columns: 22px 1fr auto;">
|
||||
<span style="width: 18px; height: 18px; border-radius: 50%; border: 1.5px solid var(--border);"></span>
|
||||
<div class="body">
|
||||
<div class="title">Draft sync-engine post</div>
|
||||
<div class="sub">2 sessions estimated</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Choosing an archetype from a brief
|
||||
|
||||
| If the brief mentions… | Use |
|
||||
|---|---|
|
||||
| feed, inbox, timeline, list, messages | A — Feed |
|
||||
| article, post, item, recipe, song, product | B — Detail |
|
||||
| sign-up, welcome, intro, walkthrough | C — Onboarding |
|
||||
| profile, account, user page, bio | D — Profile |
|
||||
| checkout, payment, order, form, settings step | E — Checkout |
|
||||
| timer, map, dashboard widget, single big number | F — Focus |
|
||||
|
||||
If two fit, pick the one that better matches the *primary* action the user takes on this screen.
|
||||
Reference in New Issue
Block a user