Mind Space: an ambient AI phone OS
A working replica of the whole design language: a sky that takes the hue of whichever context is on top, frosted context cards that carry third-party widgets inside them, and a prompt bar that is the only way to talk to it. Three screens, all tappable.
How it works
- What the language is. Frame-by-frame the source shows one idea repeated at every level: the interface is a sky, not a surface. A single vertical gradient — dark saturated at the top, dissolving to paper by 84% — is the only colour on the screen, and its hue is whatever context currently sits on top (purple for Relaxation, teal for Spring Trip, blue for Build Brain.ai). Cards, chips and the prompt bar are all white at varying alpha; nothing else is tinted.
- The hue is one custom property,
--h, registered with@property … syntax: "<number>"so it can be transitioned (900ms). Every screen state — home, detail, the agents view — writes only that number. An unregistered property would snap; measured on the source, the sky cross-fades over roughly a second. - Cards are contexts, not content. Each one is a 28px-radius frosted panel (
#ffffffa8+backdrop-filter: blur(22px)) with a chip, a title, a one-line subtitle, and a footer that is always a progress fact (Completed 2 of 5, a 45% ring,Tasks to-do 6). The card that lands on the dark part of the sky flips to a dark variant (#ffffff1f, white type) — the same element, one class. - Third parties live in an inset white widget (20px radius,
#ffffffe0) at the right of the card, never as a logo in the corner: the stay's photo collage, the airline's timeline with SFO → Gate B26 → LHR, a row of source apps with an avatar pile and+5 sources. The mark of the service is a 22px circle inside its widget. - Tabs re-sort the stack. Mind Space / Personal / Family / Work each define an order; switching runs a FLIP (measure, reorder the DOM, invert, release) over 620ms, the top card takes the dark variant, and the sky takes its hue. The list in the DOM is the truth; the animation is derived from it.
- Detail is the same sky pulled lower. Tapping a card opens a screen with the identical hue, the dark region now holding the hero (chip, 24px title, lede, a sources list with Yesterday / Today / Recent searches on the right), and the paper region holding Next up and AI Actions as 18px-radius white minis. A bottom bar carries the chat icon and pill tabs.
- The prompt is the only input. A 40px pill floating over the stack with a live caret; tapping it (or the chat icon on detail) opens the agents view: paper background, the context collapsed to a 150×74 thumbnail top-right, tabs, agent cards with a note thumbnail, a Follow up field, three Suggest chips and a keyboard. The context never leaves the screen — it shrinks.
- Type is a single grotesque at five sizes: 24 / 15.5 / 12.5 / 11 / 10, weights 600 and 500 only. Radii: 36 (screen) / 28 (card) / 20 (widget) / 18 (mini) / 99 (pills). The device shell scales to the frame with
transform: scale(), so the layout is authored once at 360×740. - The showcase walks the tabs every 3.6s until the first tap, so the sample demonstrates the sky and the re-sort by itself; any interaction stops it.
Numbers
- Sky
- hsl(--h 55% 16%) → 62% 44% @32% → 50% 80% @62% → #f5f4f9 @84%
- Hues
- Family 275 · Trip 190 · Work 228
- Hue transition
- 900ms cubic-bezier(.22,.61,.36,1) via @property
- Card
- #ffffffa8 · blur 22px · r 28 · dark variant #ffffff1f
- Widget / mini
- #ffffffe0 r 20 · #ffffffd9 r 18
- Re-sort
- FLIP 620ms cubic-bezier(.22,.61,.36,1)
- Type
- 24 / 15.5 / 12.5 / 11 / 10 · 600 & 500
- Device
- 360×740 · bezel 12 · r 46/36 · punch-hole 12
Prompt
Paste this into any coding model to get the effect from scratch. It states every number explicitly — vague words like “smooth” or “modern” produce a different result every time.
Build a self-contained mobile UI replica in vanilla HTML/CSS/JS: a 360×740 phone (12px black bezel, radius 46 outside / 36 screen, 12px punch-hole camera, status bar '19:30') scaled to fit the viewport with transform: scale(min(1, (innerHeight−36)/740, (innerWidth−24)/360)). The screen background is ONE gradient driven by a registered custom property: @property --h { syntax: '<number>'; inherits: true; initial-value: 275 } and background: linear-gradient(180deg, hsl(var(--h) 55% 16%) 0%, hsl(var(--h) 62% 44%) 32%, hsl(var(--h) 50% 80%) 62%, #f5f4f9 84%) with transition: --h 900ms cubic-bezier(.22,.61,.36,1). Home: a row of pill tabs (Mind Space / Personal / Family / Work; the active one has a 1px #ffffff8c border and #ffffff1f fill) and a vertical stack of three context cards, each a 28px-radius frosted panel (#ffffffa8, 1px #ffffffb0 border, backdrop-filter blur(22px)) with a chip (20px pill), a 15.5px/600 title, an 11px subtitle, a progress footer (check + 'Completed 2 of 5', a conic ring + '45% 2 of 5', or 'Tasks to-do 6'), and a right-hand inset white widget (radius 20, #ffffffe0) holding a third-party item: a photo collage with a red circular mark, a flight timeline 'SA2847 to London · 2:00 SFO · Gate B26 · 4:30 LHR' with a yellow mark, or a row of source-app circles plus an avatar pile '+5 sources'. Each context carries a hue (275, 190, 228). Each tab defines an order of the three cards; on tab change reorder the DOM with FLIP (measure tops, appendChild in the new order, measure again, set translateY(delta) with transitions off, flush with getBoundingClientRect, then remove the transform so a 620ms cubic-bezier(.22,.61,.36,1) transition carries it), give the first card a dark variant (#ffffff1f, white text) and write its hue into --h. A 40px floating prompt pill 'I want to become' with a blinking caret sits over the stack. Tapping a card opens a detail screen (translateY 28px → 0, 480ms) using the same hue with the gradient pulled lower (paper from 66%): back button, chip, 24px title, lede, 'Based on 8 sources' rows (Line / Calendar / Chrome with 'Yesterday / Today / Recent searches' right-aligned, '+5 sources' centred), 'Next up' and 'AI Actions' as 170px white minis (radius 18), and a bottom bar with a chat icon and pill tabs 'AI Answer / Notes / Market & Competitors'. Tapping the prompt or the chat icon opens the agents screen on paper: the context shrinks to a 150×74 dark thumbnail top-right, tabs 'AI Agents / Notes / Market & Competitors', 'Specialized agents ready to help with …', agent cards with a 54px note thumbnail, a 'Follow up' field with + and mic, three 'Suggest' chips, and a QWERTY keyboard with an 'English' space bar. Hide the home screen (opacity 0) while another screen is open. Auto-advance the tabs every 3.6s until the first interaction. Under prefers-reduced-motion remove all transitions and the auto-advance.Source
The whole file. It is what the sample above is running — copy it into an .html file and it works with nothing else.
<!doctype html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Mind Space — an ambient AI phone OS</title>
<style>
/* Animate the ambient hue as a NUMBER. Without @property a custom property
flips instantly and the whole sky changes in one frame. */
@property --h { syntax: "<number>"; inherits: true; initial-value: 275; }
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { height: 100%; }
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
background: #07081a; color: #17151f; overflow: hidden;
-webkit-font-smoothing: antialiased; -webkit-user-select: none; user-select: none;
}
/* ---------- the stage: dark room, blue ribbons, as in the source render ---------- */
.stage { position: absolute; inset: 0; display: grid; place-items: center; overflow: hidden; }
.stage::before {
content: ""; position: absolute; inset: -20%;
background:
repeating-linear-gradient(112deg, #0b1a6e00 0 90px, #1330b8 110px 150px, #0b1a6e00 170px 260px),
radial-gradient(70% 60% at 70% 30%, #1b2fa8 0%, #07081a 70%);
filter: blur(28px); opacity: .55;
}
/* ---------- the device ---------- */
.phone {
position: relative; width: 360px; height: 740px;
transform: scale(var(--s, 1)); transform-origin: 50% 50%;
border-radius: 46px; background: #0c0c12; padding: 12px;
box-shadow: 0 40px 80px -30px #000c, 0 0 0 1px #2a2a36, inset 0 0 0 2px #1a1a24;
}
.screen {
position: relative; width: 100%; height: 100%; border-radius: 36px; overflow: hidden;
--h: 275;
transition: --h 900ms cubic-bezier(.22,.61,.36,1);
/* Ambient: one hue, four stops. Dark saturated at the top, dissolving to
paper by 78%. Every screen state only ever changes --h and --dark. */
background: linear-gradient(180deg,
hsl(var(--h) 55% 16%) 0%,
hsl(var(--h) 62% 44%) 32%,
hsl(var(--h) 50% 80%) 62%,
#f5f4f9 84%);
font-size: 12px; line-height: 1.35;
}
.screen::after { /* punch-hole camera */
content: ""; position: absolute; left: 50%; top: 10px; width: 12px; height: 12px;
margin-left: -6px; border-radius: 50%; background: #000; box-shadow: inset 0 0 0 2px #14141c; z-index: 30;
}
.status {
position: absolute; left: 0; right: 0; top: 0; height: 34px; padding: 10px 20px 0;
display: flex; justify-content: space-between; align-items: center;
font-size: 11.5px; font-weight: 600; color: var(--ink, #fff); z-index: 20;
transition: color 500ms;
}
.status i { display: inline-block; width: 12px; height: 8px; margin-left: 5px; background: currentColor; border-radius: 2px; opacity: .9; }
.status i.sig { width: 10px; mask: linear-gradient(90deg, #000 0 20%, #0000 20% 30%, #000 30% 50%, #0000 50% 60%, #000 60% 80%, #0000 80% 90%, #000 90%); -webkit-mask: linear-gradient(90deg, #000 0 20%, #0000 20% 30%, #000 30% 50%, #0000 50% 60%, #000 60% 80%, #0000 80% 90%, #000 90%); }
.status i.wifi { border-radius: 50% 50% 2px 2px; height: 9px; }
.status i.bat { width: 18px; border-radius: 3px; box-shadow: inset 0 0 0 1.5px currentColor, inset 0 0 0 3px var(--ink-bg, #0000); }
.home-ind { position: absolute; left: 50%; bottom: 8px; width: 110px; height: 4px; margin-left: -55px; border-radius: 2px; background: #17151f66; z-index: 20; }
/* ---------- shared atoms ---------- */
.chip {
display: inline-flex; align-items: center; height: 20px; padding: 0 9px; border-radius: 99px;
font-size: 10px; font-weight: 500; letter-spacing: .01em;
border: 1px solid #17151f2a; background: #ffffff8c; color: #17151f;
}
.dark .chip { border-color: #ffffff59; background: #ffffff24; color: #fff; }
.glass {
background: #ffffffa8; border: 1px solid #ffffffb0; border-radius: 28px;
backdrop-filter: blur(22px) saturate(1.2); -webkit-backdrop-filter: blur(22px) saturate(1.2);
box-shadow: 0 12px 30px -18px #1a123a55;
}
.glass.dark { background: #ffffff1f; border-color: #ffffff38; color: #fff; box-shadow: none; }
section { position: absolute; inset: 0; }
.home { padding: 44px 14px 0; }
/* tabs: one bordered pill for the current context, the rest are just words */
.tabs { display: flex; gap: 6px; align-items: center; overflow: hidden; white-space: nowrap; padding: 4px 2px; }
.tab {
height: 30px; padding: 0 13px; border-radius: 99px; border: 1px solid transparent;
font-size: 12px; font-weight: 500; color: #ffffffb8; background: none; cursor: pointer;
transition: border-color 400ms, background-color 400ms, color 400ms;
}
.tab.on { border-color: #ffffff8c; background: #ffffff1f; color: #fff; }
/* ---------- the card stack ---------- */
.stack { position: relative; margin-top: 10px; display: flex; flex-direction: column; gap: 10px; }
.card {
position: relative; padding: 14px 14px 14px 16px; min-height: 172px;
display: grid; grid-template-columns: 1fr 138px; gap: 10px; cursor: pointer;
transition: transform 620ms cubic-bezier(.22,.61,.36,1), background-color 600ms, border-color 600ms, color 600ms;
will-change: transform;
}
.card.moving { transition: none; }
.card h3 { margin-top: 10px; font-size: 15.5px; font-weight: 600; letter-spacing: -.01em; }
.card p { margin-top: 3px; font-size: 11px; color: #17151fa6; max-width: 150px; }
.dark.card p { color: #ffffffb3; }
.card .foot { position: absolute; left: 16px; bottom: 14px; display: flex; align-items: center; gap: 6px; font-size: 10.5px; font-weight: 600; }
.card .foot small { font-weight: 400; opacity: .6; }
.ring { width: 18px; height: 18px; border-radius: 50%; background: conic-gradient(currentColor calc(var(--v) * 1%), #17151f22 0); mask: radial-gradient(circle, #0000 55%, #000 56%); -webkit-mask: radial-gradient(circle, #0000 55%, #000 56%); }
.tick { width: 14px; height: 14px; border-radius: 50%; background: currentColor; position: relative; }
.tick::after { content: ""; position: absolute; left: 4px; top: 3px; width: 4px; height: 7px; border: solid #fff; border-width: 0 1.8px 1.8px 0; transform: rotate(45deg); }
.dark .tick::after { border-color: #17151f; }
/* nested widget: a white inset card; the third-party thing lives here */
.widget {
border-radius: 20px; background: #ffffffe0; color: #17151f; padding: 11px 12px; position: relative;
box-shadow: 0 8px 20px -12px #1a123a4d; overflow: hidden;
}
.dark .widget { background: #ffffffe6; }
.widget b { display: block; font-size: 11px; line-height: 1.3; }
.widget .mark { position: absolute; right: 10px; top: 10px; width: 22px; height: 22px; border-radius: 50%; display: grid; place-items: center; font-size: 10px; font-weight: 800; color: #fff; }
.collage { position: relative; height: 62px; margin-top: 8px; }
.collage i {
position: absolute; width: 60px; height: 52px; border-radius: 14px; border: 3px solid #fff; box-shadow: 0 6px 14px -6px #0006;
}
.collage i:first-child { left: 8px; top: 6px; transform: rotate(-8deg); background: linear-gradient(160deg, #cfe9e7, #5fa9a3 55%, #2f6f6b); }
.collage i:last-child { left: 52px; top: 2px; transform: rotate(6deg); background: radial-gradient(circle at 45% 40%, #fff2c2 0 14%, #f0b25a 15% 30%, #a2561e 31% 60%, #5a2e12 61%); }
.timeline { margin-top: 8px; position: relative; padding-left: 14px; font-size: 10px; }
.timeline::before { content: ""; position: absolute; left: 4px; top: 6px; bottom: 6px; border-left: 1px solid #17151f33; }
.timeline div { position: relative; margin-bottom: 8px; }
.timeline div::before { content: ""; position: absolute; left: -13px; top: 4px; width: 6px; height: 6px; border-radius: 50%; border: 1.5px solid #17151f; background: #fff; }
.timeline b { font-size: 15px; font-weight: 600; display: block; line-height: 1.1; }
.timeline .gate { font-size: 10px; }
.timeline .gate span { display: inline-block; margin-left: 4px; padding: 1px 6px; border-radius: 6px; background: #17151f; color: #fff; font-weight: 600; }
.sources { grid-column: 1 / -1; display: flex; align-items: center; gap: 8px; margin-top: -2px; }
.src { width: 26px; height: 26px; border-radius: 50%; display: grid; place-items: center; color: #fff; font-size: 10px; font-weight: 800; box-shadow: 0 4px 10px -4px #0006; }
.avs { display: flex; align-items: center; gap: 6px; padding: 4px 10px 4px 4px; border-radius: 99px; background: #ffffffd9; color: #17151f; font-size: 10px; font-weight: 600; }
.avs i { width: 18px; height: 18px; border-radius: 50%; margin-right: -8px; border: 2px solid #fff; }
.count { position: absolute; right: 14px; top: 16px; font-size: 10px; color: inherit; opacity: .75; display: flex; align-items: center; gap: 6px; }
.count b { display: grid; place-items: center; width: 20px; height: 20px; border-radius: 50%; background: #17151f14; font-size: 10px; opacity: 1; }
.dark .count b { background: #ffffff2e; }
/* the floating prompt: sits over the stack, never inside it */
.prompt {
position: absolute; left: 22px; right: 22px; bottom: 24px; height: 40px; border-radius: 99px;
display: flex; align-items: center; padding: 0 16px; font-size: 12.5px; color: #17151f80;
background: #ffffffe6; border: 1px solid #ffffff; box-shadow: 0 10px 30px -10px #1a123a55; cursor: text;
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
}
.prompt::after { content: ""; width: 1px; height: 14px; background: #17151f; margin-left: 2px; animation: blink 1s steps(2) infinite; }
@keyframes blink { to { opacity: 0; } }
/* ---------- detail ---------- */
.detail, .agents { opacity: 0; pointer-events: none; transform: translateY(28px); transition: opacity 380ms, transform 480ms cubic-bezier(.22,.61,.36,1); }
.detail.open, .agents.open { opacity: 1; pointer-events: auto; transform: none; }
.detail {
padding: 46px 18px 0; color: #fff; overflow: hidden;
/* same hue, the sky pulled lower: the hero is dark, the lists are paper */
background: linear-gradient(180deg, hsl(var(--h) 55% 16%) 0%, hsl(var(--h) 60% 40%) 30%, hsl(var(--h) 45% 82%) 52%, #f5f4f9 66%);
}
.home { transition: opacity 300ms; }
.screen.away .home { opacity: 0; pointer-events: none; }
.detail h1 { margin-top: 10px; font-size: 24px; font-weight: 600; letter-spacing: -.02em; }
.detail .lede { margin-top: 6px; font-size: 11.5px; color: #ffffffcc; max-width: 250px; }
.detail h4 { margin-top: 18px; font-size: 12px; font-weight: 600; }
.srcs { margin-top: 8px; position: relative; }
.srcs .row { display: flex; align-items: center; gap: 8px; height: 30px; font-size: 11px; font-weight: 500; }
.srcs .row .src { width: 20px; height: 20px; font-size: 8px; }
.srcs .row em { font-style: normal; opacity: .5; font-size: 10px; margin-left: 2px; }
.srcs .when { position: absolute; right: 0; top: 0; text-align: right; font-size: 10px; color: #ffffff8c; }
.srcs .when div { height: 30px; line-height: 30px; }
.srcs .more { text-align: center; font-size: 10px; color: #ffffffb0; margin-top: 2px; }
.nextup { display: flex; gap: 8px; margin-top: 8px; overflow: hidden; }
.mini { flex: 0 0 170px; padding: 12px; border-radius: 18px; background: #ffffffd9; color: #17151f; }
.mini b { display: block; font-size: 12px; }
.mini span { display: block; margin-top: 3px; font-size: 10px; color: #17151f99; }
.actions { margin-top: 8px; }
.actions .mini { flex: none; background: #ffffffe0; }
.actions .mini b { background: linear-gradient(90deg, #e2427a, #b23dd9); -webkit-background-clip: text; background-clip: text; color: transparent; }
.detail h4.paper, .detail .paper { color: #17151f; }
.bar {
position: absolute; left: 14px; right: 14px; bottom: 22px; height: 40px; display: flex; align-items: center; gap: 6px;
padding: 0 6px; border-radius: 99px; background: #ffffffe6; border: 1px solid #fff; color: #17151f; overflow: hidden; white-space: nowrap;
box-shadow: 0 10px 30px -10px #1a123a55;
}
.bar .ic { width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; background: #17151f0f; flex: none; cursor: pointer; }
.bar .t { padding: 0 12px; height: 28px; line-height: 28px; border-radius: 99px; font-size: 11px; font-weight: 500; color: #17151f99; }
.bar .t.on { background: #17151f; color: #fff; }
.back { position: absolute; left: 16px; top: 44px; width: 28px; height: 28px; border-radius: 50%; display: grid; place-items: center; cursor: pointer; z-index: 25; background: #ffffff2e; color: #fff; font-size: 14px; }
/* ---------- agents / follow-up ---------- */
.agents { background: #f5f4f9; color: #17151f; padding: 46px 16px 0; }
.agents .back { background: #17151f10; color: #17151f; }
.thumb {
position: absolute; right: 16px; top: 40px; width: 150px; height: 74px; border-radius: 14px; padding: 10px 12px;
color: #fff; overflow: hidden; box-shadow: 0 10px 24px -12px #000a;
background: linear-gradient(160deg, hsl(var(--h) 55% 18%), hsl(var(--h) 62% 40%));
}
.thumb .chip { transform: scale(.85); transform-origin: 0 0; }
.thumb b { display: block; font-size: 11px; margin-top: 4px; }
.thumb span { font-size: 8px; opacity: .7; }
.agents .tabs { margin-top: 84px; padding: 0; }
.agents .tab { color: #17151f99; height: 28px; font-size: 11.5px; }
.agents .tab.on { border-color: #17151f26; background: #fff; color: #17151f; box-shadow: 0 4px 12px -8px #0006; }
.agents .lede { margin-top: 12px; font-size: 11px; color: #17151f99; }
.agent { display: grid; grid-template-columns: 54px 1fr; gap: 10px; align-items: center; margin-top: 8px; padding: 10px; border-radius: 18px; background: #fff; box-shadow: 0 8px 24px -16px #1a123a55; }
.agent .note { height: 60px; border-radius: 8px; background: #f3f2f6; padding: 6px; font-size: 5px; line-height: 1.3; color: #17151f99; overflow: hidden; }
.agent .note b { display: block; font-size: 6px; color: #17151f; }
.agent .note i { display: block; width: 90%; height: 3px; margin-top: 3px; border-radius: 2px; background: #17151f1a; }
.agent h5 { font-size: 12px; font-weight: 600; }
.agent span { display: block; font-size: 10px; color: #17151f99; margin-top: 2px; }
.follow { margin-top: 12px; display: flex; align-items: center; gap: 8px; height: 40px; padding: 0 12px; border-radius: 14px; background: #fff; box-shadow: 0 8px 24px -16px #1a123a55; font-size: 12.5px; color: #17151f80; }
.follow .plus { font-size: 18px; color: #17151f; }
.follow .mic { margin-left: auto; width: 14px; height: 18px; border-radius: 7px; border: 2px solid #17151f; }
.suggest { display: flex; gap: 6px; margin-top: 8px; }
.suggest span { padding: 5px 10px; border-radius: 99px; background: #fff; font-size: 10.5px; color: #17151f; }
.kbd { position: absolute; left: 0; right: 0; bottom: 0; padding: 8px 4px 22px; background: #d9d9e2; }
.kbd .r { display: flex; justify-content: center; gap: 4px; margin-bottom: 6px; }
.kbd .k { width: 30px; height: 36px; border-radius: 6px; background: #fff; box-shadow: 0 1px 0 #8a8a99; display: grid; place-items: center; font-size: 15px; font-weight: 500; }
.kbd .k.w { width: 42px; background: #b8b8c6; font-size: 10px; }
.kbd .k.sp { width: 150px; font-size: 11px; }
.hint { position: absolute; left: 0; right: 0; top: 7px; text-align: center; font-size: 10px; letter-spacing: .18em; text-transform: uppercase; color: #ffffff55; }
@media (prefers-reduced-motion: reduce) {
.screen, .card, .detail, .agents, .tab { transition: none; }
.prompt::after { animation: none; }
.hint { display: none; }
}
</style>
<div class="stage">
<div class="phone" id="phone">
<div class="screen" id="screen">
<div class="status"><span>19:30</span><span><i class="sig"></i><i class="wifi"></i><i class="bat"></i></span></div>
<section class="home">
<nav class="tabs" id="tabs"></nav>
<div class="stack" id="stack"></div>
<div class="prompt" id="prompt">I want to become </div>
</section>
<section class="detail" id="detail">
<div class="back" data-go="home">‹</div>
<div class="count" style="right:18px;top:50px;color:#fff"><span id="d-countlabel">Tasks to-do</span><b id="d-count">6</b></div>
<div style="margin-top:6px;padding-left:40px"><span class="chip" id="d-chip">Work</span></div>
<h1 id="d-title">Build Brain.ai</h1>
<p class="lede" id="d-lede"></p>
<h4 id="d-srclabel">Based on 8 sources</h4>
<div class="srcs" id="d-srcs"></div>
<h4 class="paper">Next up</h4>
<div class="nextup" id="d-next"></div>
<h4 class="paper">AI Actions</h4>
<div class="nextup actions" id="d-actions"></div>
<div class="bar">
<span class="ic" data-go="agents">💬</span><span class="t on">AI Answer</span><span class="t">Notes</span><span class="t">Market & Competitors</span>
</div>
</section>
<section class="agents" id="agents">
<div class="back" data-go="home">‹</div>
<div class="thumb"><span class="chip" id="a-chip">Work</span><b id="a-title">Build Brain.ai</b><span>Based on 8 sources</span></div>
<nav class="tabs"><span class="tab on">AI Agents</span><span class="tab">Notes</span><span class="tab">Market & Competitors</span></nav>
<p class="lede">Specialized agents ready to help with “<span id="a-title2">Build Brain.ai</span>”</p>
<div id="a-list"></div>
<div class="follow"><span class="plus">+</span>Follow up<span class="mic"></span></div>
<div class="suggest"><span>Suggest</span><span>Suggest</span><span>Suggest</span></div>
<div class="kbd" id="kbd"></div>
</section>
<div class="home-ind"></div>
</div>
</div>
<div class="hint">tap a tab · a card · the prompt</div>
</div>
<script>
/* ---------- data ----------
Three "contexts". Each carries its own hue: the ambient sky is not a
theme, it is whichever context sits on top right now. */
const CARDS = {
relax: {
hue: 275, chip: "Family", title: "Relaxation Ideas", sub: "Activities for Couples",
foot: `<span class="tick"></span>Completed <small>2 of 5</small>`,
widget: `<b>Spa, Herbal<br>Tea Ritual<br>and more</b><span class="mark" style="background:#ff385c">⌂</span><div class="collage"><i></i><i></i></div>`,
lede: "A weekend of small rituals for two — a spa afternoon, an evening tea ceremony, and one surprise.",
count: ["Ideas saved", 5], srcLabel: "Based on 3 sources",
sources: [["Stays", "#ff385c", "Yesterday"], ["Calendar", "#2f6fff", "Today"], ["Maps", "#22a06b", "Recent searches"]],
next: [["Book the spa slot", "Saturday 14:00 still open"], ["Tea ritual kit", "Arrives Thursday"]],
actions: [["Find a quiet café nearby", "Walking distance from the spa"]],
agents: [["Weekend Planner", "Builds the itinerary around both calendars"], ["Gift Scout", "Finds the one surprise that fits"]],
},
trip: {
hue: 190, chip: "Family", title: "Spring Trip", sub: "Complete vacation planning",
foot: `<span class="ring" style="--v:45"></span>45% <small>2 of 5</small>`,
widget: `<b>SA2847<br><span style="font-weight:400">to London</span></b><span class="mark" style="background:#ffe600;color:#000;font-size:7px">spirit</span>
<div class="timeline"><div><b>2:00</b>SFO</div><div class="gate">Gate<span>B26</span></div><div><b>4:30</b>LHR</div></div>`,
lede: "Six days in London for four. Flights are booked; the hotel, the museum day and the trains are not.",
count: ["Tasks to-do", 3], srcLabel: "Based on 5 sources",
sources: [["Mail", "#2f6fff", "Yesterday"], ["Wallet", "#111", "Today"], ["Maps", "#22a06b", "Recent searches"]],
next: [["Confirm the hotel", "Two options under budget"], ["Museum day", "Tickets release Monday"]],
actions: [["Compare train vs. car to Bath", "Time, cost, kids"]],
agents: [["Itinerary Agent", "Keeps the days balanced for the kids"], ["Budget Watcher", "Flags anything over the trip cap"]],
},
brain: {
hue: 228, chip: "Work", title: "Build Brain.ai", sub: "Lead the mission to reinvent personal computing by creating the next generation of AI-native phones.",
foot: ``,
widget: null,
lede: "Lead the mission to reinvent personal computing by creating the next generation of AI-native phones.",
count: ["Tasks to-do", 6], srcLabel: "Based on 8 sources",
sources: [["Line", "#06c755", "Yesterday"], ["Calendar", "#2f6fff", "Today"], ["Chrome", "#e64a3b", "Recent searches"]],
next: [["Define Product Vision", "Clearly articulate the core features and unique selling pro…"], ["Outline Tech Stack", "Develop a high-level architecture"]],
actions: [["Research Competitor Offerings", "Clearly articulate the core features"]],
agents: [["Define Product Vision", "Clearly articulate the core features"], ["Keynote for Investors", "Narrative, numbers and the demo"]],
},
};
// which context each tab puts on top — the sky follows that card
const TABS = [["Mind Space", ["relax", "trip", "brain"]], ["Personal", ["trip", "relax", "brain"]], ["Family", ["relax", "brain", "trip"]], ["Work", ["brain", "relax", "trip"]]];
const $ = (id) => document.getElementById(id);
const screen = $("screen"), stack = $("stack"), tabs = $("tabs");
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
// ---------- home: cards
const cardEls = {};
for (const [id, c] of Object.entries(CARDS)) {
const el = document.createElement("article");
el.className = "card glass"; el.dataset.id = id;
const brain = id === "brain";
el.innerHTML = `
<div><span class="chip">${c.chip}</span><h3>${c.title}</h3><p${brain ? ' style="max-width:100%;grid-column:1/-1"' : ""}>${c.sub}</p></div>
${brain ? `<div class="count"><span>Tasks to-do</span><b>6</b></div>
<div class="sources"><span class="src" style="background:#06c755">L</span><span class="src" style="background:#2f6fff">▦</span><span class="src" style="background:conic-gradient(#e64a3b 0 33%,#ffcd2b 0 66%,#22a06b 0)"><i style="width:10px;height:10px;border-radius:50%;background:#fff;display:block"></i></span>
<span class="avs"><i style="background:#f0b25a"></i><i style="background:#5fa9a3"></i><i style="background:#c9789a"></i><span style="margin-left:12px">+5 sources</span></span></div>`
: `<div class="widget">${c.widget}</div>`}
<div class="foot">${c.foot}</div>`;
if (brain) el.style.gridTemplateColumns = "1fr";
el.addEventListener("click", () => openDetail(id));
cardEls[id] = el;
}
// ---------- home: tabs
let tabIndex = 0, auto = 0;
TABS.forEach(([name], i) => {
const b = document.createElement("button");
b.className = "tab"; b.textContent = name;
b.addEventListener("click", () => { clearInterval(auto); setTab(i); });
tabs.appendChild(b);
});
/* Reorder with FLIP: measure, move the DOM, measure again, then let each
card travel from its old spot to its new one. The list is the truth; the
animation is derived from it, so nothing can end up out of sync. */
function setTab(i) {
tabIndex = i;
[...tabs.children].forEach((b, k) => b.classList.toggle("on", k === i));
const order = TABS[i][1];
const before = {};
for (const id of order) before[id] = cardEls[id].getBoundingClientRect().top;
for (const id of order) stack.appendChild(cardEls[id]);
order.forEach((id, k) => {
const el = cardEls[id];
el.classList.toggle("dark", k === 0); // top card sits on the dark sky
if (reduce || before[id] === undefined) return;
const dy = before[id] - el.getBoundingClientRect().top;
el.classList.add("moving");
el.style.transform = `translateY(${dy / scale}px)`;
el.getBoundingClientRect(); // flush, then release
el.classList.remove("moving");
el.style.transform = "";
});
screen.style.setProperty("--h", CARDS[order[0]].hue);
}
// ---------- detail
let current = "brain";
function openDetail(id) {
clearInterval(auto); current = id;
const c = CARDS[id];
screen.style.setProperty("--h", c.hue);
$("d-chip").textContent = c.chip; $("d-title").textContent = c.title; $("d-lede").textContent = c.lede;
$("d-countlabel").textContent = c.count[0]; $("d-count").textContent = c.count[1]; $("d-srclabel").textContent = c.srcLabel;
$("d-srcs").innerHTML = c.sources.map(([n, col]) => `<div class="row"><span class="src" style="background:${col}">${n[0]}</span>${n}<em>·</em></div>`).join("")
+ `<div class="when">${c.sources.map(([, , w]) => `<div>${w}</div>`).join("")}</div><div class="more">+5 sources</div>`;
$("d-next").innerHTML = c.next.map(([b, s]) => `<div class="mini"><b>${b}</b><span>${s}</span></div>`).join("");
$("d-actions").innerHTML = c.actions.map(([b, s]) => `<div class="mini"><b>${b}</b><span>${s}</span></div>`).join("");
go("detail");
}
function openAgents() {
const c = CARDS[current];
$("a-chip").textContent = c.chip; $("a-title").textContent = c.title; $("a-title2").textContent = c.title;
$("a-list").innerHTML = c.agents.map(([b, s]) => `<div class="agent"><div class="note"><b>Michael Chen</b>12:20 PM<i></i><i></i><i></i></div><div><h5>${b}</h5><span>${s}</span></div></div>`).join("");
go("agents");
}
function go(where) {
$("detail").classList.toggle("open", where === "detail");
screen.classList.toggle("away", where !== "home");
$("agents").classList.toggle("open", where === "agents");
document.querySelector(".status").style.setProperty("--ink", where === "agents" ? "#17151f" : "#fff");
if (where === "home") screen.style.setProperty("--h", CARDS[TABS[tabIndex][1][0]].hue);
}
document.querySelectorAll("[data-go]").forEach((el) => el.addEventListener("click", (e) => { e.stopPropagation(); el.dataset.go === "agents" ? openAgents() : go(el.dataset.go); }));
$("prompt").addEventListener("click", () => { clearInterval(auto); openAgents(); });
// keyboard
const rows = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"];
$("kbd").innerHTML = rows.map((r, i) => `<div class="r">${i === 2 ? '<span class="k w">⇧</span>' : ""}${[...r].map((k) => `<span class="k">${k}</span>`).join("")}${i === 2 ? '<span class="k w">⌫</span>' : ""}</div>`).join("")
+ `<div class="r"><span class="k w">?123</span><span class="k">,</span><span class="k sp">English</span><span class="k">.</span><span class="k w">⏎</span></div>`;
// ---------- fit the device to the frame
let scale = 1;
function fit() {
scale = Math.min(1, (innerHeight - 36) / 740, (innerWidth - 24) / 360);
$("phone").style.setProperty("--s", scale.toFixed(4));
}
addEventListener("resize", fit); fit();
// ---------- start: the showcase walks the tabs until someone touches it
setTab(0);
if (!reduce) auto = setInterval(() => setTab((tabIndex + 1) % TABS.length), 3600);
</script>Where it breaks
@propertyis what makes the sky cross-fade. Firefox shipped it in 128 (2024); anything older snaps the hue instantly — the layout is unaffected, only the transition is lost.backdrop-filteron three stacked cards over a gradient is fine on a phone-sized canvas; the same language at desktop width with a dozen cards will show on integrated GPUs. Cap the blur radius or drop it on the cards that sit on paper, where it does nothing visible anyway.- The third-party marks are placeholders (a red house circle, a yellow 'spirit' disc, coloured initials) — they stand in for real brand assets, which are not free to redistribute. The placement is the point: inside the widget, never on the card.
- The re-sort is measured against the phone's own coordinates, so the FLIP delta is divided by the shell's scale; forget that and the cards fly twice as far as they should on a shrunken device.
- This is a replica of a concept render, not of a shipped product. Text and data are ours; the layout, colour behaviour and interaction model are the source's.
Seen in
@海森堡 (Douyin) · “自然人工智能设计探索的思维空间” · 0:00–0:19