④段整套同步(六节 + P/S/F/J + 模板)+ open-design 移到技能根

一、④段整套同步(用户选候选 A)
- 新增 `references/stage-proto-doc/assets/产品文档模板.html`(16 KB/247 行,从备份拷、转 LF)。
- ④段 SKILL.md 换成备份 15:28 那版,并按模板口径改齐:
  · 「产品文档框架」从「四块+一附录」改成**六节** —— 页面结构/功能清单/使用场景/操作流程/
    跨页与界面状态/待确认与偏离;与模板同构,⛔ 不从空白页重写结构。
  · 编号从 R/S/F 改成 **P 页面/S 场景/F 功能/J 二级功能**,各自唯一、按展示顺序通排。
  · 保留旧版有价值的要求:每项功能补齐 做什么/入口在哪/输入边界/异常与恢复;重点功能三处标红;
    横向流程图每步写清页面/功能/操作/完成判据。
  · 完成标准「七节」→「六节」,自查清单同步换掉「需求」那批条目,补模板与配图两条。
  · 路径 `designs/<项目>/` → `docs/pm/<项目>/ui/`(2 处)。
  · 补回本机那条「每份产出文档开头都要有一张图」的引用(指向主 skill,段内不复述)。
- 主 skill 同步:四段结构表④行、④段沿革、画图小节(④段画页面结构图 + 用户路径)。

二、open-design 移到技能根(用户选候选 A)
- `git mv product-planning/references/stage-delivery/assets/open-design → open-design/`(4178 文件)。
- `.gitignore` 白名单加 `!/open-design/` —— 否则移出去就不再纳入版本控制。
- 引用改 11 处:路径形态 `assets/open-design/...` → `<技能根>/open-design/...`;
  「随段携带的自带件」改称「同根技能」;「路径怎么找」那条改成自带件(段内相对路径)与
  同根技能(`<技能根>/<技能名>/`)两类分开写。
- open-design 自己的 ATTRIBUTION.md 两处同步(它现在与 oil-ui-pro 同层)。
- 副作用(有益):命名闸门读数由 2063 降到 225 —— 此前它把 open-design 里 919 个第三方 md
  当成规则一起扫了;现在只扫本包 23 个 md。已确认④段的 H1 与 4a/4b/4c 子步仍在检查范围内。

三、验收
- check_naming.py --ws E:/ProgramData/AIProject/contentm_agent = 通过 225 / 失败 0 / 提示 27。
- 活文件里 `assets/open-design` 残留 0;工作区 CRLF 文件 0。
This commit is contained in:
admin committed 2026-10-15 21:17:28 +08:00
1 parent fa0a9026c8
commit 5afeb6b78a
4184 files changed
+326 -70

No files matched your search

@@ -0,0 +1,761 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Mira's First Month · A quiet panel</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600&family=Caveat:wght@500;600&display=swap" rel="stylesheet">
<style>
:root {
/* Soft, warm, parental */
--bg: #fdf9f5;
--paper: #ffffff;
--paper-tint: #faf5ef;
--paper-deep: #f4ece2;
--line: #ece2d5;
--line-strong: #d8c8b3;
--hairline: rgba(60,40,20,0.06);
--ink: #2a2520;
--ink-soft: #4d433b;
--muted: #8a7e72;
--muted-2: #b3a89a;
/* Soft pastel accents */
--peach: #f5b885;
--peach-soft: #fce4cf;
--sage: #9ec79e;
--sage-soft: #e0eddc;
--lavender: #c5b8e0;
--lavender-soft: #e8e1f5;
--sky: #b3d4e8;
--sky-soft: #def0fa;
--rose: #e8b3c2;
--rose-soft: #fadfe6;
--gold: #e8d094;
--gold-soft: #faf1d6;
/* Type */
--serif: 'Instrument Serif', Iowan, Charter, Georgia, serif;
--display: 'Instrument Serif', Iowan, Charter, Georgia, serif;
--body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--hand: 'Caveat', 'Instrument Serif', cursive;
--r: 16px; /* softer corners */
--r-pill: 999px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background:
radial-gradient(800px 400px at 100% -10%, rgba(245,184,133,0.15), transparent 60%),
radial-gradient(700px 400px at 0% 60%, rgba(197,184,224,0.12), transparent 60%),
radial-gradient(900px 500px at 50% 110%, rgba(158,199,158,0.10), transparent 60%),
var(--bg);
color: var(--ink);
font-family: var(--body);
font-size: 14px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
min-height: 100vh;
}
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
/* ─────── Layout ─────── */
main {
max-width: 880px; margin: 0 auto;
padding: 60px 32px 80px;
}
header.app {
display: flex; align-items: center; justify-content: space-between;
padding: 22px 32px;
border-bottom: 1px solid var(--hairline);
}
.word-mark {
font-family: var(--serif); font-style: italic;
font-size: 19px; letter-spacing: -0.005em;
color: var(--ink);
}
.word-mark .small {
font-family: var(--body); font-style: normal;
font-size: 11px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.16em;
margin-right: 10px;
}
.icon-btn {
padding: 7px 14px; font-size: 12.5px;
border-radius: var(--r-pill);
background: var(--paper); border: 1px solid var(--line);
color: var(--ink-soft); transition: all 0.15s;
display: inline-flex; gap: 8px; align-items: center;
}
.icon-btn:hover { border-color: var(--line-strong); color: var(--ink); }
.icon-btn.warm {
background: linear-gradient(180deg, #fef4e6, var(--paper));
border-color: rgba(245,184,133,0.45);
color: #8a5a2a;
}
.icon-btn .ic { width: 13px; height: 13px; }
.icon-btn.spin .ic { animation: spin 0.8s linear; }
@keyframes spin { to { transform: rotate(360deg); } }
/* ─────── Hero ─────── */
.hero {
text-align: center;
margin-bottom: 56px;
}
.hero .pre {
font-size: 11px; color: var(--muted); text-transform: uppercase;
letter-spacing: 0.2em; margin-bottom: 18px;
}
.hero .name {
font-family: var(--serif); font-weight: 400;
font-size: 80px; line-height: 1.05;
margin: 0; letter-spacing: -0.02em;
color: var(--ink);
}
.hero .name em {
font-style: italic;
background: linear-gradient(180deg, #f5b885 0%, #c5b8e0 100%);
-webkit-background-clip: text; background-clip: text; color: transparent;
}
.hero .age {
font-family: var(--hand);
font-size: 32px; line-height: 1;
color: var(--peach);
margin-top: 14px;
transform: rotate(-2deg);
display: inline-block;
}
.hero .meta {
margin-top: 22px;
display: inline-flex; gap: 18px;
font-size: 13px; color: var(--muted);
}
.hero .meta .v { color: var(--ink); }
.hero .meta .sep { color: var(--muted-2); }
/* ─────── Section ─────── */
section.block {
margin-bottom: 44px;
}
.block-head {
display: flex; align-items: baseline; justify-content: space-between;
margin-bottom: 18px;
}
.block-head h2 {
font-family: var(--serif); font-weight: 400; font-size: 30px;
margin: 0; line-height: 1; letter-spacing: -0.005em;
color: var(--ink);
}
.block-head h2 em {
font-style: italic; color: var(--peach);
margin-right: 8px; font-size: 21px; vertical-align: 4px;
}
.block-head .meta {
font-size: 11.5px; color: var(--muted);
text-transform: uppercase; letter-spacing: 0.14em;
}
.block-head .meta .src { color: var(--ink-soft); margin-right: 4px; }
.card {
background: var(--paper);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 28px;
box-shadow: 0 2px 0 rgba(60,40,20,0.02);
}
/* ─────── Sleep circle ─────── */
.sleep-card {
display: grid; grid-template-columns: 280px 1fr; gap: 28px; align-items: center;
}
.sleep-circle {
width: 280px; height: 280px;
margin: 0 auto;
}
.sleep-circle svg { width: 100%; height: 100%; display: block; }
.sleep-stat .lbl {
font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.14em;
}
.sleep-stat .big {
font-family: var(--serif); font-weight: 400; font-size: 56px;
line-height: 1; margin-top: 8px; letter-spacing: -0.02em;
}
.sleep-stat .delta {
margin-top: 10px; font-size: 13px; color: var(--sage);
}
.sleep-stat .delta strong { color: var(--ink); font-weight: 600; }
.sleep-stat .quote {
font-family: var(--serif); font-style: italic;
font-size: 17px; color: var(--ink-soft);
margin-top: 22px; padding-left: 14px;
border-left: 2px solid var(--lavender);
line-height: 1.5;
max-width: 36ch;
}
/* ─────── Feeding timeline ─────── */
.feed-timeline {
position: relative; padding: 28px 0 32px;
}
.feed-line {
height: 2px;
background: linear-gradient(90deg, var(--lavender), var(--peach), var(--sage));
border-radius: 2px;
position: relative;
margin: 0 14px;
}
.feed-marker {
position: absolute; top: 50%; transform: translate(-50%, -50%);
width: 14px; height: 14px; border-radius: 50%;
cursor: pointer;
transition: transform 0.15s;
}
.feed-marker:hover { transform: translate(-50%, -50%) scale(1.4); }
.feed-marker.bottle { background: var(--peach); box-shadow: 0 0 0 4px var(--peach-soft); }
.feed-marker.breast { background: var(--rose); box-shadow: 0 0 0 4px var(--rose-soft); }
.feed-marker.now {
background: var(--ink); box-shadow: 0 0 0 4px var(--paper), 0 0 0 6px var(--ink);
animation: nowBreath 2.4s ease-in-out infinite;
}
@keyframes nowBreath {
0%, 100% { transform: translate(-50%, -50%) scale(1); }
50% { transform: translate(-50%, -50%) scale(1.15); }
}
.feed-tip {
position: absolute; top: -36px; transform: translate(-50%, 0);
background: var(--ink); color: var(--paper);
font-size: 11px; padding: 4px 8px; border-radius: 5px;
white-space: nowrap;
opacity: 0; pointer-events: none; transition: opacity 0.15s;
font-family: var(--body);
}
.feed-tip::after {
content: ""; position: absolute; left: 50%; top: 100%; transform: translateX(-50%);
border: 4px solid transparent; border-top-color: var(--ink);
}
.feed-marker:hover .feed-tip { opacity: 1; }
.feed-axis {
margin-top: 36px; padding: 0 14px;
display: grid; grid-template-columns: repeat(7, 1fr);
font-family: var(--body); font-size: 11px; color: var(--muted);
text-align: center;
}
.feed-legend {
margin-top: 24px;
display: flex; gap: 22px; flex-wrap: wrap;
font-size: 12px; color: var(--ink-soft);
}
.feed-legend .swatch { display: inline-flex; gap: 8px; align-items: center; }
.feed-legend .swatch::before { content: ""; width: 10px; height: 10px; border-radius: 50%; }
.feed-legend .swatch.bottle::before { background: var(--peach); box-shadow: 0 0 0 3px var(--peach-soft); }
.feed-legend .swatch.breast::before { background: var(--rose); box-shadow: 0 0 0 3px var(--rose-soft); }
.feed-legend .swatch.now::before { background: var(--ink); }
.feed-legend .v { font-family: var(--serif); font-style: italic; color: var(--ink); }
/* ─────── Weight curve ─────── */
.growth-card { padding: 28px 32px; }
.growth-card svg { width: 100%; height: 220px; display: block; margin-top: 8px; }
.growth-stats {
display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;
margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--hairline);
}
.growth-stats .stat .lbl { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.14em; }
.growth-stats .stat .v { font-family: var(--serif); font-size: 24px; line-height: 1; margin-top: 6px; letter-spacing: -0.01em; }
.growth-stats .stat .pctile { color: var(--sage); font-size: 11.5px; margin-top: 4px; }
/* ─────── Today reminders ─────── */
.reminders {
display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
}
.rem-card {
padding: 18px 20px; border-radius: 12px;
border: 1px solid var(--line);
background: var(--paper);
display: flex; flex-direction: column; gap: 8px;
cursor: pointer;
transition: all 0.15s;
}
.rem-card:hover { transform: translateY(-2px); box-shadow: 0 6px 18px rgba(60,40,20,0.06); }
.rem-card .l-row { display: flex; gap: 10px; align-items: center; color: var(--muted); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.14em; }
.rem-card .glyph {
width: 28px; height: 28px; border-radius: 50%;
display: inline-flex; align-items: center; justify-content: center;
font-size: 14px;
}
.rem-card.feed .glyph { background: var(--peach-soft); color: #8a5a2a; }
.rem-card.nap .glyph { background: var(--lavender-soft); color: #5e4d8e; }
.rem-card.appt .glyph { background: var(--sky-soft); color: #2a5e7a; }
.rem-card.bath .glyph { background: var(--sage-soft); color: #4a6e4a; }
.rem-card .when {
font-family: var(--serif); font-size: 28px; line-height: 1; letter-spacing: -0.01em;
}
.rem-card .desc { font-size: 12.5px; color: var(--ink-soft); line-height: 1.45; }
/* ─────── AI insights ─────── */
.insight-card {
background: linear-gradient(180deg, var(--lavender-soft) 0%, var(--paper) 100%);
border-color: rgba(197,184,224,0.4);
padding: 28px 32px;
}
.insight-card .badge {
display: inline-flex; gap: 8px; align-items: center;
font-family: var(--body); font-size: 10.5px;
text-transform: uppercase; letter-spacing: 0.16em;
color: #6e5e9e;
}
.insight-card .badge .glyph {
width: 22px; height: 22px; border-radius: 50%;
background: linear-gradient(135deg, var(--lavender), var(--peach));
display: inline-flex; align-items: center; justify-content: center;
color: #fff; font-size: 12px; font-weight: 600;
box-shadow: 0 0 12px rgba(197,184,224,0.4);
}
.insight-card h3 {
font-family: var(--serif); font-weight: 400; font-size: 28px;
line-height: 1.2; margin: 12px 0 6px; letter-spacing: -0.01em;
}
.insight-card h3 em { font-style: italic; color: #6e5e9e; }
.insight-card p {
color: var(--ink-soft); font-size: 14px; margin: 0;
line-height: 1.55; max-width: 56ch;
}
.insight-list {
margin-top: 16px; display: flex; flex-direction: column; gap: 10px;
}
.insight-list .row {
display: grid; grid-template-columns: 22px 1fr; gap: 10px;
align-items: flex-start;
}
.insight-list .check {
color: var(--sage); font-family: var(--serif); font-size: 18px; line-height: 1;
}
.insight-list .text { font-size: 13px; color: var(--ink-soft); line-height: 1.5; }
.insight-list .text strong { color: var(--ink); }
.insight-list .text .hand {
font-family: var(--hand); font-size: 16px; color: var(--peach);
transform: rotate(-1deg); display: inline-block; padding: 0 4px;
}
/* ─────── Photo strip (placeholder) ─────── */
.photo-strip {
display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px;
}
.photo {
aspect-ratio: 4 / 5; border-radius: 10px;
background: linear-gradient(135deg, var(--peach-soft), var(--lavender-soft));
border: 1px solid var(--line);
position: relative;
overflow: hidden;
cursor: pointer;
transition: transform 0.15s;
}
.photo:hover { transform: translateY(-2px) rotate(-1deg); }
.photo:nth-child(2) { background: linear-gradient(135deg, var(--rose-soft), var(--sky-soft)); transform: rotate(0.5deg); }
.photo:nth-child(3) { background: linear-gradient(135deg, var(--sage-soft), var(--gold-soft)); transform: rotate(-0.5deg); }
.photo:nth-child(4) { background: linear-gradient(135deg, var(--gold-soft), var(--peach-soft)); transform: rotate(0.8deg); }
.photo .cap {
position: absolute; bottom: 8px; left: 10px; right: 10px;
font-family: var(--hand); font-size: 16px;
color: var(--ink-soft); transform: rotate(-1deg);
}
.photo .day {
position: absolute; top: 8px; right: 10px;
font-family: var(--body); font-size: 10.5px; color: var(--muted);
background: rgba(255,255,255,0.7); padding: 2px 6px; border-radius: 3px;
backdrop-filter: blur(2px);
}
/* Closing line */
.closing {
text-align: center; margin-top: 40px;
font-family: var(--serif); font-style: italic;
font-size: 18px; color: var(--muted);
line-height: 1.5;
}
.closing .author {
font-family: var(--body); font-style: normal;
font-size: 11px; color: var(--muted-2);
text-transform: uppercase; letter-spacing: 0.16em;
margin-top: 8px;
}
/* Toast */
.toast-box { position: fixed; bottom: 24px; right: 24px; z-index: 200; display: flex; flex-direction: column; gap: 10px; pointer-events: none; }
.toast {
pointer-events: auto;
min-width: 280px; max-width: 380px;
padding: 14px 16px;
background: var(--paper);
border: 1px solid var(--line);
border-left: 3px solid var(--peach);
border-radius: 12px;
box-shadow: 0 16px 40px rgba(60,40,20,0.10);
font-size: 13px;
color: var(--ink);
animation: toastIn 0.32s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.toast.lavender { border-left-color: var(--lavender); }
.toast.sage { border-left-color: var(--sage); }
.toast .t-title { font-family: var(--serif); font-size: 16px; line-height: 1.2; margin-bottom: 4px; }
.toast .t-body { color: var(--ink-soft); line-height: 1.45; }
.toast .t-body strong { color: var(--ink); }
.toast.fade { opacity: 0; transform: translateX(20px); transition: all 0.25s; }
@keyframes toastIn { from { opacity: 0; transform: translateX(20px); } to { opacity: 1; transform: translateX(0); } }
@media (max-width: 720px) {
main { padding: 40px 18px 60px; }
.hero .name { font-size: 56px; }
.sleep-card { grid-template-columns: 1fr; }
.sleep-circle { width: 220px; height: 220px; }
.reminders { grid-template-columns: 1fr; }
.photo-strip { grid-template-columns: repeat(2, 1fr); }
}
</style>
</head>
<body>
<header class="app">
<div class="word-mark">
<span class="small">a quiet panel from</span>
<em>quiver</em>
</div>
<button class="icon-btn warm" id="logBtn">
<svg class="ic" viewBox="0 0 16 16" fill="none"><path d="M8 3v10M3 8h10" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
Log a feed
</button>
</header>
<main>
<!-- ─────── Hero ─────── -->
<div class="hero">
<div class="pre">Mira's first month · 2026</div>
<h1 class="name">Mira <em>Avery</em></h1>
<div class="age">28 days &amp; counting</div>
<div class="meta">
<span><span class="v">Born</span> April 9 · 2026</span>
<span class="sep">·</span>
<span><span class="v">3.42 kg</span> · 50.8 cm</span>
<span class="sep">·</span>
<span><span class="v">Mom &amp; dad</span> have her back</span>
</div>
</div>
<!-- ─────── 1. Sleep ─────── -->
<section class="block">
<div class="block-head">
<h2><em>i.</em>How she slept last night</h2>
<span class="meta"><span class="src">Huckleberry</span>· synced 6:42 AM</span>
</div>
<div class="card sleep-card">
<div class="sleep-circle">
<svg viewBox="0 0 200 200">
<defs>
<radialGradient id="bgG" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fff" stop-opacity="0.6"/>
<stop offset="100%" stop-color="#c5b8e0" stop-opacity="0.06"/>
</radialGradient>
</defs>
<circle cx="100" cy="100" r="92" fill="url(#bgG)" stroke="#ece2d5" stroke-width="0.8"/>
<!-- 24h tick marks -->
<g stroke="#d8c8b3" stroke-width="0.6">
<line x1="100" y1="14" x2="100" y2="20"/>
<line x1="186" y1="100" x2="180" y2="100"/>
<line x1="100" y1="186" x2="100" y2="180"/>
<line x1="14" y1="100" x2="20" y2="100"/>
</g>
<!-- Hour labels -->
<g font-family="Inter,sans-serif" font-size="9" fill="#8a7e72">
<text x="100" y="11" text-anchor="middle">12 am</text>
<text x="194" y="103" text-anchor="end">6</text>
<text x="100" y="197" text-anchor="middle">12 pm</text>
<text x="6" y="103">6</text>
</g>
<!-- Sleep arcs (purple = night, peach = naps) -->
<!-- Night sleep 7:30 PM → 6:30 AM (with 4 brief wakes) -->
<!-- Stroke arcs at radius 76 -->
<path d="M 154.5 145.5 A 76 76 0 0 1 100 24" fill="none" stroke="#c5b8e0" stroke-width="14" stroke-linecap="round" opacity="0.85"/>
<path d="M 100 24 A 76 76 0 0 1 134.7 32.5" fill="none" stroke="#c5b8e0" stroke-width="14" stroke-linecap="round" opacity="0.5"/>
<!-- 1st morning nap 9:00–10:30 -->
<path d="M 162.4 76.6 A 76 76 0 0 1 173.1 105.4" fill="none" stroke="#f5b885" stroke-width="14" stroke-linecap="round" opacity="0.85"/>
<!-- afternoon nap 1:30–3:00 -->
<path d="M 142.1 168.6 A 76 76 0 0 1 100 176" fill="none" stroke="#f5b885" stroke-width="14" stroke-linecap="round" opacity="0.85"/>
<!-- evening nap 6:00–6:45 -->
<path d="M 24 100 A 76 76 0 0 1 32.5 65.3" fill="none" stroke="#f5b885" stroke-width="14" stroke-linecap="round" opacity="0.65"/>
<!-- Center text -->
<text x="100" y="92" text-anchor="middle" font-family="Instrument Serif,serif" font-size="34" fill="#2a2520" letter-spacing="-0.5">14h 22m</text>
<text x="100" y="115" text-anchor="middle" font-family="Inter,sans-serif" font-size="10" fill="#8a7e72" letter-spacing="0.6">total · 24h window</text>
<!-- now hand -->
<line x1="100" y1="100" x2="100" y2="62" stroke="#2a2520" stroke-width="1.5" stroke-linecap="round" transform="rotate(105 100 100)"/>
<circle cx="100" cy="100" r="3" fill="#2a2520"/>
</svg>
</div>
<div class="sleep-stat">
<div class="lbl">Last night · 7:30 PM → 6:30 AM</div>
<div class="big">9h 14m</div>
<div class="delta">3 wakes (down from 4) · longest stretch <strong>3h 12m</strong></div>
<div class="quote">"She slept 22 minutes longer per night this week. Not a streak — a rhythm."</div>
</div>
</div>
</section>
<!-- ─────── 2. Feeding ─────── -->
<section class="block">
<div class="block-head">
<h2><em>ii.</em>How she ate today</h2>
<span class="meta"><span class="src">Baby Tracker + scale</span>· last 24h</span>
</div>
<div class="card">
<div class="feed-timeline">
<div class="feed-line">
<!-- timeline runs midnight → midnight → markers as % -->
<div class="feed-marker bottle" style="left:8.3%"><span class="feed-tip">2:00 AM · 90ml bottle</span></div>
<div class="feed-marker breast" style="left:21%"><span class="feed-tip">5:00 AM · breast 18 min</span></div>
<div class="feed-marker bottle" style="left:33%"><span class="feed-tip">8:00 AM · 110ml bottle</span></div>
<div class="feed-marker breast" style="left:46%"><span class="feed-tip">11:00 AM · breast 22 min</span></div>
<div class="feed-marker bottle" style="left:58%"><span class="feed-tip">2:00 PM · 100ml bottle</span></div>
<div class="feed-marker now" style="left:60.5%"><span class="feed-tip">Now · 14:32</span></div>
<div class="feed-marker breast" style="left:71%"><span class="feed-tip">5:00 PM · breast 16 min (est.)</span></div>
<div class="feed-marker bottle" style="left:83%"><span class="feed-tip">8:00 PM · 100ml bottle (est.)</span></div>
<div class="feed-marker breast" style="left:96%"><span class="feed-tip">11:30 PM · breast (est.)</span></div>
</div>
<div class="feed-axis">
<span>12am</span><span>4am</span><span>8am</span><span>noon</span><span>4pm</span><span>8pm</span><span>12am</span>
</div>
<div class="feed-legend">
<span class="swatch bottle">Bottle <span class="v" style="margin-left:4px;">4 today · 400 ml</span></span>
<span class="swatch breast">Breast <span class="v" style="margin-left:4px;">5 today · ~110 min</span></span>
<span class="swatch now">Now</span>
</div>
</div>
</div>
</section>
<!-- ─────── 3. Weight curve ─────── -->
<section class="block">
<div class="block-head">
<h2><em>iii.</em>How she's growing</h2>
<span class="meta"><span class="src">Withings smart scale</span>· weekly</span>
</div>
<div class="card growth-card">
<svg viewBox="0 0 600 220" preserveAspectRatio="none">
<defs>
<linearGradient id="growthArea" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#9ec79e" stop-opacity="0.28"/>
<stop offset="100%" stop-color="#9ec79e" stop-opacity="0"/>
</linearGradient>
<linearGradient id="bandG" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#c5b8e0" stop-opacity="0.18"/>
<stop offset="100%" stop-color="#c5b8e0" stop-opacity="0.04"/>
</linearGradient>
</defs>
<!-- WHO percentile band (10–90) -->
<polygon fill="url(#bandG)" points="40,170 120,154 200,138 280,122 360,108 440,96 520,84 580,76 580,140 520,150 440,160 360,168 280,176 200,182 120,188 40,194"/>
<!-- 50th percentile dashed line -->
<polyline fill="none" stroke="#c5b8e0" stroke-width="1" stroke-dasharray="3 4"
points="40,182 120,170 200,158 280,148 360,138 440,128 520,118 580,108"/>
<!-- Mira's actual weight -->
<polygon fill="url(#growthArea)" points="40,200 40,178 130,170 220,158 310,142 400,124 490,108 580,90 580,200"/>
<polyline fill="none" stroke="#9ec79e" stroke-width="2.4" stroke-linejoin="round" stroke-linecap="round"
points="40,178 130,170 220,158 310,142 400,124 490,108 580,90"/>
<!-- Markers -->
<g>
<circle cx="40" cy="178" r="3.5" fill="#9ec79e"/>
<circle cx="130" cy="170" r="3.5" fill="#9ec79e"/>
<circle cx="220" cy="158" r="3.5" fill="#9ec79e"/>
<circle cx="310" cy="142" r="3.5" fill="#9ec79e"/>
<circle cx="400" cy="124" r="3.5" fill="#9ec79e"/>
<circle cx="490" cy="108" r="3.5" fill="#9ec79e"/>
<circle cx="580" cy="90" r="5" fill="#9ec79e" stroke="#fff" stroke-width="2"/>
</g>
<!-- X labels -->
<g font-family="Inter,sans-serif" font-size="10" fill="#8a7e72" text-anchor="middle">
<text x="40" y="215">birth</text>
<text x="130" y="215">wk 1</text>
<text x="220" y="215">wk 2</text>
<text x="310" y="215">wk 3</text>
<text x="400" y="215">wk 4</text>
<text x="490" y="215">wk 5</text>
<text x="580" y="215" fill="#2a2520" font-weight="600">today</text>
</g>
<!-- "now" callout -->
<text x="572" y="80" text-anchor="end" font-family="Caveat,cursive" font-size="20" fill="#9ec79e" transform="rotate(-3 572 80)">on track ✓</text>
<!-- Y axis labels -->
<g font-family="Geist Mono,monospace" font-size="9.5" fill="#8a7e72">
<text x="6" y="80">5.0</text>
<text x="6" y="120">4.5</text>
<text x="6" y="160">4.0</text>
<text x="6" y="200">3.5</text>
</g>
</svg>
<div class="growth-stats">
<div class="stat">
<div class="lbl">Weight today</div>
<div class="v">4.84 kg</div>
<div class="pctile">62nd percentile · WHO</div>
</div>
<div class="stat">
<div class="lbl">Length</div>
<div class="v">53.4 cm</div>
<div class="pctile">58th percentile · WHO</div>
</div>
<div class="stat">
<div class="lbl">Gain · 7 days</div>
<div class="v">+218 g</div>
<div class="pctile">healthy range (150–280g)</div>
</div>
</div>
</div>
</section>
<!-- ─────── 4. Today's reminders ─────── -->
<section class="block">
<div class="block-head">
<h2><em>iv.</em>Today's gentle reminders</h2>
<span class="meta">4 things, no alarms</span>
</div>
<div class="reminders">
<div class="rem-card feed" data-rem="feed">
<div class="l-row"><span class="glyph">◍</span> Next feed</div>
<div class="when">in ~38 min</div>
<div class="desc">Last feed at 2:00 PM (100 ml bottle). Suggested 100–110 ml.</div>
</div>
<div class="rem-card nap" data-rem="nap">
<div class="l-row"><span class="glyph">☾</span> Next likely nap</div>
<div class="when">3:30 — 5:00 PM</div>
<div class="desc">She tends to fade ~90 min after morning rouse. Recent pattern.</div>
</div>
<div class="rem-card appt" data-rem="appt">
<div class="l-row"><span class="glyph">✓</span> Doctor visit</div>
<div class="when">Friday 10:30 AM</div>
<div class="desc">1-month checkup with Dr. Wei. We'll bring the weight log.</div>
</div>
<div class="rem-card bath" data-rem="bath">
<div class="l-row"><span class="glyph">~</span> Bath night</div>
<div class="when">tonight, 6:30 PM</div>
<div class="desc">Every other day · grandma's lullaby works.</div>
</div>
</div>
</section>
<!-- ─────── 5. AI Insight ─────── -->
<section class="block">
<div class="block-head">
<h2><em>v.</em>What we noticed this week</h2>
<span class="meta"><span class="src">Quiver</span>· soft patterns</span>
</div>
<div class="card insight-card">
<div class="badge">
<span class="glyph">✦</span>
Quiver insights · weekly
</div>
<h3>She's settling into a <em>longer-stretch</em> sleep at night.</h3>
<p>Five small patterns matched up this week. None demand action — they just paint a picture.</p>
<div class="insight-list">
<div class="row"><span class="check">✓</span><span class="text"><strong>Longer first stretch:</strong> first sleep block grew from 2h 14m → 3h 12m on average. <span class="hand">good for you both</span></span></div>
<div class="row"><span class="check">✓</span><span class="text"><strong>Feed-then-sleep latency dropped to 14 min</strong> (was 28 min in week 2). She's calmer post-feed.</span></div>
<div class="row"><span class="check">✓</span><span class="text"><strong>Bottle preference stabilized</strong> on the slow-flow Dr. Brown's after the size-2 trial. No spit-up since Monday.</span></div>
<div class="row"><span class="check">✓</span><span class="text"><strong>62nd percentile</strong> on weight, up from 58th — within healthy range. Length is steady at 58th.</span></div>
<div class="row"><span class="check">✓</span><span class="text"><strong>Diaper count averaged 8 per day</strong> (target ≥6). Hydration is comfortable.</span></div>
</div>
</div>
</section>
<!-- ─────── 6. Photo memory ─────── -->
<section class="block">
<div class="block-head">
<h2><em>vi.</em>This week, in pictures</h2>
<span class="meta"><span class="src">iCloud Photos</span>· auto-curated</span>
</div>
<div class="photo-strip">
<div class="photo">
<div class="day">Mon</div>
<div class="cap">first finger-grip</div>
</div>
<div class="photo">
<div class="day">Wed</div>
<div class="cap">sun nap on the porch</div>
</div>
<div class="photo">
<div class="day">Thu</div>
<div class="cap">grandma met her</div>
</div>
<div class="photo">
<div class="day">Sat</div>
<div class="cap">first real smile (we think)</div>
</div>
</div>
</section>
<div class="closing">
"What you measure, you remember."<br/>
<span class="author">— quiver, a quiet panel · auto-refreshes daily at 6:30 AM</span>
</div>
</main>
<div class="toast-box" id="toastBox"></div>
<script>
(() => {
// Toast
const toastBox = document.getElementById('toastBox');
function toast(opts) {
const { kind = '', title = '', body = '' } = (typeof opts === 'string') ? { body: opts } : opts;
const node = document.createElement('div');
node.className = `toast ${kind}`;
node.innerHTML = `<div class="t-title">${title}</div><div class="t-body">${body}</div>`;
toastBox.appendChild(node);
setTimeout(() => {
node.classList.add('fade');
setTimeout(() => node.remove(), 280);
}, 4200);
}
// Reminders
const remCopy = {
feed: { kind: '', title: 'Logged a feed', body: 'Recorded · <strong>110 ml at 3:10 PM</strong> bottle. Updated next-feed estimate to 6:15 PM.' },
nap: { kind: 'lavender', title: 'Nap window noted', body: 'We\'ll send a quiet ping at <strong>3:25 PM</strong> if she\'s still awake.' },
appt: { kind: 'sage', title: 'Friday checkup', body: 'Calendar event opened · 1-month checkup with Dr. Wei · 10:30 AM.<br/>We\'ll auto-prepare the weight log.' },
bath: { kind: '', title: 'Bath time saved', body: 'Tonight at 6:30 PM. We\'ll dim the smart lights at 6:25.' }
};
document.querySelectorAll('[data-rem]').forEach(r => {
r.addEventListener('click', () => {
const k = r.dataset.rem;
if (remCopy[k]) toast(remCopy[k]);
});
});
// Log a feed (header button)
document.getElementById('logBtn').addEventListener('click', () => {
toast({ kind: 'sage', title: 'New feed logged', body: '<strong>110 ml</strong> bottle · 3:10 PM<br/>Avg gain trend: +218 g this week, on track.' });
});
// Photos
document.querySelectorAll('.photo').forEach(p => {
p.addEventListener('click', () => {
const cap = p.querySelector('.cap').textContent;
toast({ kind: 'lavender', title: 'Photo memory', body: `"${cap}" · saved to your Mira album.` });
});
});
// Feed marker click (open detail)
document.querySelectorAll('.feed-marker').forEach(m => {
m.addEventListener('click', e => {
const tip = m.querySelector('.feed-tip');
if (tip) toast({ kind: '', title: 'Feed entry', body: tip.textContent });
});
});
})();
</script>
</body>
</html>
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,919 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Monday morning briefing · Quiver</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Instrument+Serif:ital@0;1&family=Inter:wght@400;500;600;700&family=Geist+Mono:wght@400;500;600&display=swap" rel="stylesheet">
<style>
:root {
/* Editorial · calm Monday morning */
--paper: #f5f1ea; /* warm cream background */
--paper-elev: #fdfcf9; /* card surface */
--paper-deep: #efe9dd; /* elevation contrast */
--ink: #1c1c1f; /* deep near-black */
--ink-soft: #3b3b3f;
--muted: #6b6b66;
--muted-2: #8e8e88;
--line: #e3ddd0;
--line-strong: #cdc4b1;
--hairline: rgba(28,28,31,0.06);
/* Editorial accent palette — earthy, calm */
--espresso: #6f4e37; /* coffee */
--sage: #6e8865;
--sage-soft: #e7eee2;
--slate-blue: #4a6e8a;
--slate-soft: #e3ebf2;
--terracotta: #c8775c;
--tc-soft: #f6e4dd;
--gold: #c9a45a;
--gold-soft: #f1e7ce;
--plum: #6e4e6e;
--plum-soft: #ece2ec;
/* Type */
--serif: 'Instrument Serif', Iowan, Charter, Georgia, serif;
--display: 'Instrument Serif', Iowan, Charter, Georgia, serif;
--body: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--mono: 'Geist Mono', ui-monospace, SFMono-Regular, Menlo, monospace;
--r: 12px;
--r-pill: 999px;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
background:
/* paper grain — barely-there noise via SVG */
url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0.07 0 0 0 0 0.06 0 0 0 0 0.04 0 0 0 0.06 0'/></filter><rect width='160' height='160' filter='url(%23n)'/></svg>"),
var(--paper);
color: var(--ink);
font-family: var(--body);
font-size: 14px;
line-height: 1.6;
-webkit-font-smoothing: antialiased;
text-rendering: optimizeLegibility;
min-height: 100vh;
}
a { color: inherit; text-decoration: none; }
button { font-family: inherit; cursor: pointer; }
/* ─────── Header ─────── */
header.app {
border-bottom: 1px solid var(--line);
padding: 22px 32px 18px;
display: flex; align-items: center; justify-content: space-between;
background: var(--paper);
}
.masthead {
font-family: var(--serif);
font-size: 22px;
letter-spacing: -0.01em;
line-height: 1;
}
.masthead .small {
font-family: var(--mono); font-size: 10.5px;
color: var(--muted); text-transform: uppercase; letter-spacing: 0.16em;
margin-bottom: 6px;
}
.masthead .word {
display: inline-flex; gap: 8px; align-items: baseline;
}
.masthead .quiver {
font-family: var(--serif); font-style: italic;
color: var(--espresso);
font-size: 22px;
}
.head-right { display: flex; align-items: center; gap: 14px; }
.pill-soft {
padding: 6px 12px; font-size: 12px;
border: 1px solid var(--line); border-radius: var(--r-pill);
background: var(--paper-elev); color: var(--ink-soft);
font-family: var(--mono); letter-spacing: 0.04em;
transition: all 0.15s;
}
.pill-soft:hover { border-color: var(--line-strong); color: var(--ink); }
.pill-soft .dot {
display: inline-block; width: 6px; height: 6px; border-radius: 50%;
background: var(--sage); margin-right: 8px; vertical-align: 1px;
box-shadow: 0 0 6px var(--sage);
}
.icon-btn {
padding: 7px 14px; border-radius: var(--r-pill);
background: var(--ink); color: var(--paper);
font-size: 12px; font-weight: 500; letter-spacing: 0.01em;
border: 1px solid var(--ink);
display: inline-flex; gap: 8px; align-items: center;
transition: all 0.15s;
}
.icon-btn:hover { transform: translateY(-1px); }
.icon-btn.ghost { background: transparent; color: var(--ink); }
.icon-btn .ic { width: 13px; height: 13px; }
.icon-btn.spin .ic { animation: spin 0.8s linear; }
@keyframes spin { to { transform: rotate(360deg); } }
/* ─────── Layout ─────── */
main {
max-width: 1220px; margin: 0 auto;
padding: 40px 32px 80px;
display: grid;
grid-template-columns: 1fr 280px;
gap: 48px;
}
/* ─────── Hero greeting ─────── */
.greeting {
margin-bottom: 36px;
border-bottom: 1px solid var(--hairline);
padding-bottom: 28px;
}
.greeting .pretitle {
font-family: var(--mono);
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.18em;
color: var(--muted);
margin-bottom: 10px;
}
.greeting h1 {
font-family: var(--serif);
font-weight: 400;
font-size: 56px;
line-height: 1.05;
letter-spacing: -0.015em;
margin: 0;
color: var(--ink);
}
.greeting h1 em {
font-style: italic; color: var(--espresso);
}
.greeting .lede {
font-family: var(--serif);
font-style: italic;
font-size: 19px;
color: var(--ink-soft);
margin-top: 10px;
max-width: 64ch;
line-height: 1.5;
}
.greeting .meta-row {
margin-top: 18px;
display: flex; gap: 24px; flex-wrap: wrap;
font-family: var(--mono); font-size: 12px;
color: var(--muted);
}
.greeting .meta-row .v { color: var(--ink); margin-right: 4px; }
.greeting .meta-row .sep { color: var(--line-strong); }
/* ─────── Section style ─────── */
section.block {
margin-bottom: 40px;
}
.block > .head {
display: flex; align-items: baseline; justify-content: space-between;
margin-bottom: 14px;
padding-bottom: 6px;
border-bottom: 1px solid var(--hairline);
}
.block > .head h2 {
font-family: var(--serif);
font-weight: 400;
font-size: 26px;
letter-spacing: -0.005em;
line-height: 1;
margin: 0;
}
.block > .head h2 .ord {
font-family: var(--serif); font-style: italic; color: var(--espresso);
margin-right: 12px; font-size: 18px; vertical-align: 4px;
}
.block > .head .meta {
font-family: var(--mono); font-size: 11px;
text-transform: uppercase; letter-spacing: 0.12em;
color: var(--muted);
}
.block > .head .pill-soft { padding: 4px 10px; font-size: 11px; }
.card {
background: var(--paper-elev);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 20px 22px;
box-shadow: 0 1px 0 rgba(28,28,31,0.02);
}
/* ─────── Revenue card ─────── */
.rev-card {
display: grid;
grid-template-columns: 220px 1fr;
gap: 28px;
align-items: center;
padding: 24px 28px;
}
.rev-stat .label {
font-family: var(--mono); font-size: 10.5px;
color: var(--muted); text-transform: uppercase; letter-spacing: 0.14em;
}
.rev-stat .big {
font-family: var(--serif);
font-weight: 400;
font-size: 60px;
line-height: 1;
letter-spacing: -0.02em;
margin-top: 8px;
color: var(--ink);
}
.rev-stat .delta {
margin-top: 10px; font-size: 13px;
color: var(--sage); font-weight: 500;
}
.rev-stat .delta .arr { font-family: var(--mono); }
.rev-stat .sub { color: var(--muted); font-size: 12px; margin-top: 12px; max-width: 30ch; }
.rev-chart svg { width: 100%; height: 140px; display: block; }
/* ─────── Email digest ─────── */
.digest-list { display: flex; flex-direction: column; gap: 12px; }
.digest-item {
background: var(--paper-elev);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 16px 20px;
display: grid;
grid-template-columns: 38px 1fr auto;
gap: 14px;
cursor: pointer;
transition: all 0.15s;
}
.digest-item:hover {
border-color: var(--line-strong);
transform: translateY(-1px);
box-shadow: 0 4px 14px rgba(28,28,31,0.05);
}
.digest-av {
width: 38px; height: 38px; border-radius: 50%;
color: var(--paper); font-family: var(--serif); font-size: 16px;
display: inline-flex; align-items: center; justify-content: center;
flex-shrink: 0;
}
.digest-meta { font-family: var(--mono); font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.08em; }
.digest-from { color: var(--ink); font-weight: 500; }
.digest-subject {
margin-top: 4px; font-family: var(--serif); font-size: 17px; line-height: 1.35;
color: var(--ink);
}
.digest-snippet {
margin-top: 4px; color: var(--muted); font-size: 12.5px;
overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
max-width: 64ch;
}
.digest-tag {
align-self: center;
font-family: var(--mono); font-size: 10px;
padding: 3px 9px; border-radius: 4px;
text-transform: uppercase; letter-spacing: 0.1em;
}
.digest-tag.urgent { background: var(--tc-soft); color: var(--terracotta); }
.digest-tag.warm { background: var(--gold-soft); color: #8e6f1f; }
.digest-tag.calm { background: var(--sage-soft); color: #496938; }
.digest-tag.fyi { background: var(--slate-soft); color: var(--slate-blue); }
/* ─────── Stuck issues ─────── */
.stuck-list { display: flex; flex-direction: column; gap: 10px; }
.stuck-row {
background: var(--paper-elev);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 14px 18px;
display: grid; grid-template-columns: 1fr auto auto auto; gap: 18px;
align-items: center;
cursor: pointer;
transition: all 0.15s;
}
.stuck-row:hover { border-color: var(--line-strong); }
.stuck-row .id {
font-family: var(--mono); font-size: 11px;
color: var(--muted); margin-bottom: 4px;
letter-spacing: 0.04em;
}
.stuck-row .title { font-size: 14.5px; color: var(--ink); font-weight: 500; }
.stuck-row .who {
display: inline-flex; gap: 6px; align-items: center;
font-size: 12px; color: var(--muted);
}
.stuck-row .who .av {
width: 22px; height: 22px; border-radius: 50%;
color: #fff; font-size: 10px; font-weight: 600;
display: inline-flex; align-items: center; justify-content: center;
}
.stuck-row .age {
font-family: var(--mono); font-size: 11.5px;
padding: 3px 9px; border-radius: 5px;
background: var(--tc-soft); color: var(--terracotta);
font-weight: 600;
}
.stuck-row .age.warm { background: var(--gold-soft); color: #8e6f1f; }
.stuck-row .nudge {
padding: 5px 11px; font-family: var(--mono); font-size: 11px;
border-radius: 6px; border: 1px solid var(--line); background: var(--paper);
color: var(--ink-soft); font-weight: 500;
transition: all 0.15s;
}
.stuck-row .nudge:hover { background: var(--ink); color: var(--paper); border-color: var(--ink); }
/* ─────── Schedule timeline ─────── */
.schedule {
display: flex; flex-direction: column; gap: 0;
padding: 0;
background: var(--paper-elev);
border: 1px solid var(--line);
border-radius: var(--r);
overflow: hidden;
}
.sched-row {
display: grid; grid-template-columns: 80px 12px 1fr auto; gap: 16px;
align-items: center;
padding: 14px 22px;
border-bottom: 1px solid var(--hairline);
transition: background 0.12s;
position: relative;
cursor: pointer;
}
.sched-row:last-child { border-bottom: 0; }
.sched-row:hover { background: var(--paper-deep); }
.sched-row .time {
font-family: var(--mono); font-size: 12px;
color: var(--ink); font-weight: 500;
}
.sched-row .time .dur { color: var(--muted); font-size: 10.5px; display: block; margin-top: 2px; }
.sched-row .dot {
width: 10px; height: 10px; border-radius: 50%;
background: var(--sage); justify-self: center;
}
.sched-row.now .dot {
background: var(--terracotta);
box-shadow: 0 0 0 4px var(--tc-soft);
animation: nowPulse 2.4s ease-out infinite;
}
@keyframes nowPulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.18); }
}
.sched-row.past .dot { background: var(--muted-2); opacity: 0.55; }
.sched-row.past .title, .sched-row.past .desc { opacity: 0.55; }
.sched-row.past .title { text-decoration: line-through; text-decoration-color: var(--muted-2); }
.sched-row .title { font-size: 14.5px; color: var(--ink); font-weight: 500; }
.sched-row .desc { font-size: 12px; color: var(--muted); margin-top: 2px; }
.sched-row .source {
font-family: var(--mono); font-size: 10.5px;
padding: 2px 8px; border-radius: 4px;
color: var(--muted); border: 1px solid var(--line);
text-transform: uppercase; letter-spacing: 0.08em;
}
/* ─────── PR list ─────── */
.pr-list { display: flex; flex-direction: column; gap: 0;
background: var(--paper-elev);
border: 1px solid var(--line);
border-radius: var(--r);
overflow: hidden;
}
.pr-row {
display: grid; grid-template-columns: 1fr auto auto auto; gap: 14px;
padding: 14px 22px;
border-bottom: 1px solid var(--hairline);
align-items: center;
cursor: pointer;
transition: background 0.12s;
}
.pr-row:last-child { border-bottom: 0; }
.pr-row:hover { background: var(--paper-deep); }
.pr-row .num { font-family: var(--mono); font-size: 11px; color: var(--muted); }
.pr-row .title { font-size: 14px; color: var(--ink); font-weight: 500; margin-top: 2px; }
.pr-row .files {
font-family: var(--mono); font-size: 11px; color: var(--muted);
background: var(--paper-deep);
padding: 3px 8px; border-radius: 4px;
}
.pr-row .age {
font-family: var(--mono); font-size: 11.5px;
padding: 3px 8px; border-radius: 5px;
color: var(--ink-soft);
}
.pr-row .age.urgent { background: var(--tc-soft); color: var(--terracotta); }
.pr-row .age.warm { background: var(--gold-soft); color: #8e6f1f; }
.pr-row .age.calm { background: var(--sage-soft); color: #496938; }
/* ─────── Wins (subtle bottom) ─────── */
.wins {
margin-top: 36px;
padding: 32px 36px;
background: linear-gradient(180deg, transparent, var(--paper-deep));
border-radius: var(--r);
border: 1px dashed var(--line-strong);
}
.wins-title {
font-family: var(--serif); font-style: italic; font-size: 28px;
color: var(--espresso); margin: 0 0 14px;
line-height: 1; letter-spacing: -0.01em;
}
.wins-list { columns: 2; column-gap: 32px; }
.wins-list .win {
break-inside: avoid;
margin-bottom: 14px;
display: grid; grid-template-columns: 18px 1fr; gap: 8px; align-items: flex-start;
}
.wins-list .checkmark {
color: var(--sage); font-family: var(--serif); font-size: 18px; line-height: 1;
}
.wins-list .text { font-size: 13.5px; color: var(--ink-soft); line-height: 1.5; }
/* ─────── Sidebar ─────── */
aside.briefing-side {
align-self: start;
position: sticky; top: 32px;
}
.first-thing {
background: var(--ink);
color: var(--paper);
border-radius: var(--r);
padding: 20px 22px;
margin-bottom: 14px;
position: relative;
overflow: hidden;
}
.first-thing::before {
content: ""; position: absolute; inset: 0;
background: radial-gradient(220px 120px at 110% -20%, rgba(255,180,120,0.12), transparent 70%);
pointer-events: none;
}
.first-thing .label {
font-family: var(--mono); font-size: 10.5px;
text-transform: uppercase; letter-spacing: 0.14em;
color: rgba(255,255,255,0.55);
}
.first-thing h3 {
font-family: var(--serif); font-size: 22px; font-weight: 400;
margin: 8px 0 8px; line-height: 1.15; color: var(--paper);
letter-spacing: -0.005em;
}
.first-thing p {
color: rgba(255,255,255,0.78); font-size: 13px; margin: 0;
line-height: 1.55;
}
.first-thing .actions {
margin-top: 16px; display: flex; gap: 8px;
}
.first-thing .a-btn {
padding: 6px 12px; font-size: 12px; border-radius: 6px;
border: 1px solid rgba(255,255,255,0.2);
background: rgba(255,255,255,0.06); color: var(--paper);
font-family: var(--mono); letter-spacing: 0.04em;
transition: all 0.15s;
}
.first-thing .a-btn:hover { background: rgba(255,255,255,0.14); }
.first-thing .a-btn.primary {
background: var(--gold); color: var(--ink); border-color: var(--gold);
font-weight: 600;
}
.side-card {
background: var(--paper-elev);
border: 1px solid var(--line);
border-radius: var(--r);
padding: 16px 18px;
margin-bottom: 14px;
}
.side-card h4 {
font-family: var(--mono); font-size: 10.5px;
text-transform: uppercase; letter-spacing: 0.14em;
color: var(--muted); margin: 0 0 10px; font-weight: 500;
}
.quick-action {
display: flex; align-items: center; justify-content: space-between;
padding: 8px 0; font-size: 13px; color: var(--ink-soft);
border-bottom: 1px dashed var(--hairline);
cursor: pointer;
transition: color 0.12s;
}
.quick-action:last-child { border-bottom: 0; }
.quick-action:hover { color: var(--ink); }
.quick-action .arr { font-family: var(--mono); color: var(--muted); }
.weather-card {
display: flex; align-items: center; gap: 14px;
padding: 14px 16px;
}
.weather-glyph {
width: 44px; height: 44px;
background: linear-gradient(180deg, #ffce72, #f0a23a);
border-radius: 50%;
box-shadow: 0 0 22px rgba(255,180,80,0.45);
flex-shrink: 0;
}
.weather-card .temp {
font-family: var(--serif); font-size: 28px; line-height: 1; letter-spacing: -0.01em;
}
.weather-card .desc { color: var(--muted); font-size: 12px; margin-top: 4px; }
/* Toast */
.toast-box {
position: fixed; bottom: 24px; right: 24px; z-index: 200;
display: flex; flex-direction: column; gap: 10px;
pointer-events: none;
}
.toast {
pointer-events: auto;
min-width: 280px; max-width: 380px;
padding: 14px 16px;
background: var(--paper-elev);
border: 1px solid var(--line);
border-left: 3px solid var(--espresso);
border-radius: 8px;
box-shadow: 0 16px 48px rgba(28,28,31,0.10);
font-size: 13px;
color: var(--ink);
animation: toastIn 0.32s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.toast.success { border-left-color: var(--sage); }
.toast.warn { border-left-color: var(--gold); }
.toast.urgent { border-left-color: var(--terracotta); }
.toast .t-title { font-family: var(--serif); font-size: 16px; line-height: 1.2; margin-bottom: 4px; }
.toast .t-body { color: var(--ink-soft); line-height: 1.45; }
.toast .t-body strong { color: var(--ink); }
.toast.fade { opacity: 0; transform: translateX(20px); transition: all 0.25s; }
@keyframes toastIn {
from { opacity: 0; transform: translateX(20px); }
to { opacity: 1; transform: translateX(0); }
}
@media (max-width: 1080px) {
main { grid-template-columns: 1fr; }
aside.briefing-side { position: static; }
.greeting h1 { font-size: 40px; }
.rev-card { grid-template-columns: 1fr; }
.wins-list { columns: 1; }
}
</style>
</head>
<body>
<header class="app">
<div class="masthead">
<div class="small">Daily Briefing · Volume IV · Issue 18</div>
<div class="word">
<span>The Monday Memo</span>
<span class="quiver">— from quiver</span>
</div>
</div>
<div class="head-right">
<span class="pill-soft"><span class="dot"></span>5 sources connected · synced 6:42 AM</span>
<button class="icon-btn ghost" id="snoozeBtn">Snooze 1h</button>
<button class="icon-btn" id="doneBtn">
<svg class="ic" viewBox="0 0 16 16" fill="none"><path d="M3 8.5l3 3 7-7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
Mark briefing done
</button>
</div>
</header>
<main>
<div>
<!-- ─────── Greeting ─────── -->
<div class="greeting">
<div class="pretitle">Monday · May 6, 2026 · 8:42 AM</div>
<h1>Good morning, <em>PT.</em></h1>
<div class="lede">
Six things deserve your attention this morning. The rest can wait until coffee #2.
</div>
<div class="meta-row">
<span><span class="v">San Francisco</span></span><span class="sep">·</span>
<span><span class="v">62°</span> light fog, clearing 11 AM</span><span class="sep">·</span>
<span><span class="v">Sunset</span> 8:09 PM</span><span class="sep">·</span>
<span><span class="v" style="color:var(--sage)">7h 24m</span> sleep</span>
</div>
</div>
<!-- ─────── 1. Revenue ─────── -->
<section class="block">
<div class="head">
<h2><span class="ord">i.</span>This week's revenue</h2>
<span class="meta">Stripe · 7-day rolling</span>
</div>
<div class="rev-card card">
<div class="rev-stat">
<div class="label">Past 7 days</div>
<div class="big">$84,210</div>
<div class="delta"><span class="arr">▲</span> +12.4% vs prior week · +$9,316</div>
<div class="sub">Driven by 3 enterprise expansions on Wed–Thu. Two SMB churns absorbed in net new.</div>
</div>
<div class="rev-chart">
<svg viewBox="0 0 600 140" preserveAspectRatio="none">
<defs>
<linearGradient id="revG" x1="0" x2="0" y1="0" y2="1">
<stop offset="0%" stop-color="#6e8865" stop-opacity="0.32"/>
<stop offset="100%" stop-color="#6e8865" stop-opacity="0"/>
</linearGradient>
</defs>
<!-- horizontal guides -->
<line x1="0" y1="40" x2="600" y2="40" stroke="rgba(28,28,31,0.06)" stroke-dasharray="2 4"/>
<line x1="0" y1="80" x2="600" y2="80" stroke="rgba(28,28,31,0.06)" stroke-dasharray="2 4"/>
<line x1="0" y1="120" x2="600" y2="120" stroke="rgba(28,28,31,0.06)" stroke-dasharray="2 4"/>
<!-- prior week ghost line -->
<polyline fill="none" stroke="rgba(28,28,31,0.32)" stroke-width="1.2" stroke-dasharray="3 4"
points="20,90 100,86 180,82 260,86 340,80 420,78 500,72 580,70"/>
<!-- this week area + line -->
<polygon fill="url(#revG)" points="20,140 20,82 100,76 180,68 260,70 340,58 420,52 500,40 580,30 580,140"/>
<polyline fill="none" stroke="#6e8865" stroke-width="2.2" stroke-linejoin="round" stroke-linecap="round"
points="20,82 100,76 180,68 260,70 340,58 420,52 500,40 580,30"/>
<!-- weekday labels -->
<g font-family="Geist Mono,monospace" font-size="9.5" fill="#6b6b66" text-anchor="middle">
<text x="20" y="138">Mon</text>
<text x="100" y="138">Tue</text>
<text x="180" y="138">Wed</text>
<text x="260" y="138">Thu</text>
<text x="340" y="138">Fri</text>
<text x="420" y="138">Sat</text>
<text x="500" y="138">Sun</text>
<text x="580" y="138" fill="#1c1c1f" font-weight="600">Mon</text>
</g>
<!-- "now" marker -->
<circle cx="580" cy="30" r="3.5" fill="#1c1c1f"/>
<text x="586" y="22" font-family="Instrument Serif,serif" font-style="italic" font-size="13" fill="#1c1c1f">today</text>
</svg>
</div>
</div>
</section>
<!-- ─────── 2. Email digest ─────── -->
<section class="block">
<div class="head">
<h2><span class="ord">ii.</span>Customers who deserve a reply</h2>
<span class="meta">Gmail · curated by relevance</span>
</div>
<div class="digest-list" id="digest"></div>
</section>
<!-- ─────── 3. Stuck Linear issues ─────── -->
<section class="block">
<div class="head">
<h2><span class="ord">iii.</span>What's stuck on the team</h2>
<span class="meta">Linear · idle &gt; 48h</span>
</div>
<div class="stuck-list" id="stuck"></div>
</section>
<!-- ─────── 4. Today's schedule ─────── -->
<section class="block">
<div class="head">
<h2><span class="ord">iv.</span>Today, hour by hour</h2>
<span class="meta">Google Calendar · 6 events</span>
</div>
<div class="schedule" id="schedule"></div>
</section>
<!-- ─────── 5. PRs ─────── -->
<section class="block">
<div class="head">
<h2><span class="ord">v.</span>Pull requests waiting on you</h2>
<span class="meta">GitHub · 4 PRs</span>
</div>
<div class="pr-list" id="prList"></div>
</section>
<!-- ─────── 6. Wins ─────── -->
<div class="wins">
<div class="wins-title">Yesterday, quietly:</div>
<div class="wins-list">
<div class="win"><span class="checkmark">✓</span><span class="text"><strong>Voltage Co.</strong> went live in production at 6:14 PM. Zero rollback. Onboarding kickoff tomorrow.</span></div>
<div class="win"><span class="checkmark">✓</span><span class="text">Mira closed <strong>Atlas Cooperative</strong> ($240k) on a Sunday call. The handshake email landed at 11:47 PM.</span></div>
<div class="win"><span class="checkmark">✓</span><span class="text">QA cleared the Q2 release — <strong>0 P0s, 2 P3s</strong>. Cleanest cut in 7 quarters.</span></div>
<div class="win"><span class="checkmark">✓</span><span class="text"><strong>Nora's onboarding</strong> wrapped a week ahead. Already shipping in <code>src/agent/router</code>.</span></div>
<div class="win"><span class="checkmark">✓</span><span class="text">Stripe weekly closed at <strong>$84.2k</strong> — fourth straight week above $80k. Comfortable Q2 trajectory.</span></div>
<div class="win"><span class="checkmark">✓</span><span class="text">Sam shipped the auth migration with a <strong>54-line PR</strong>. The rollback plan was three commits.</span></div>
</div>
</div>
</div>
<!-- ─────── Sidebar ─────── -->
<aside class="briefing-side">
<div class="first-thing">
<div class="label">Your first thing today</div>
<h3>Reply to Lattice Health by 10 AM.</h3>
<p>Their legal review wraps tomorrow. A short "we're ready when you are" keeps the proposal on Friday's close list.</p>
<div class="actions">
<button class="a-btn primary" data-action="draft">Draft reply</button>
<button class="a-btn" data-action="snooze1">Snooze 1h</button>
</div>
</div>
<div class="weather-card side-card">
<div class="weather-glyph"></div>
<div>
<div class="temp">62°</div>
<div class="desc">Light fog · clearing by 11</div>
</div>
</div>
<div class="side-card">
<h4>Quick actions</h4>
<div class="quick-action" data-action="generatePlan"><span>✦ Generate next week's plan</span><span class="arr">→</span></div>
<div class="quick-action" data-action="standup"><span>Post standup digest</span><span class="arr">→</span></div>
<div class="quick-action" data-action="snooze"><span>Snooze whole briefing 1h</span><span class="arr">→</span></div>
<div class="quick-action" data-action="email"><span>Email me a copy at 7 PM</span><span class="arr">→</span></div>
</div>
<div class="side-card">
<h4>Connected sources</h4>
<div style="display:flex;flex-direction:column;gap:8px;font-size:13px;">
<div style="display:flex;justify-content:space-between;color:var(--ink-soft);">
<span>Stripe</span><span style="font-family:var(--mono);font-size:11px;color:var(--sage);">live</span>
</div>
<div style="display:flex;justify-content:space-between;color:var(--ink-soft);">
<span>Gmail</span><span style="font-family:var(--mono);font-size:11px;color:var(--sage);">live</span>
</div>
<div style="display:flex;justify-content:space-between;color:var(--ink-soft);">
<span>Linear</span><span style="font-family:var(--mono);font-size:11px;color:var(--sage);">live</span>
</div>
<div style="display:flex;justify-content:space-between;color:var(--ink-soft);">
<span>GitHub</span><span style="font-family:var(--mono);font-size:11px;color:var(--sage);">live</span>
</div>
<div style="display:flex;justify-content:space-between;color:var(--ink-soft);">
<span>Calendar</span><span style="font-family:var(--mono);font-size:11px;color:var(--sage);">live</span>
</div>
</div>
</div>
<div style="text-align:center;font-family:var(--serif);font-style:italic;color:var(--muted);font-size:14px;line-height:1.4;padding:14px 8px;">
"What gets your attention<br/>shapes your week."
<div style="font-family:var(--mono);font-style:normal;font-size:10.5px;color:var(--muted-2);margin-top:6px;letter-spacing:0.06em;">— quiver, vol. IV</div>
</div>
</aside>
</main>
<div class="toast-box" id="toastBox"></div>
<script>
(() => {
// ─────── Email digest ───────
const EMAILS = [
{ from: 'Pioneer Robotics — Sarah Chen', subject: 'Re: Pricing 2-pager and onboarding timeline', snippet: 'Quick one — the team is ready to move forward, just need the term sheet by Wednesday so legal can…', tag: 'urgent', ago: '2h', av: 'S', avBg: '#1f2937' },
{ from: 'Lattice Health — David Park', subject: 'Procurement asked for a SOC 2 letter', snippet: 'Hey, our procurement team had one last item on the list. Can you send the SOC 2 attestation letter to…', tag: 'warm', ago: '4h', av: 'D', avBg: '#0ea5e9' },
{ from: 'Foundry Group — Thomas Brun', subject: 'Loved the demo recording — sharing internally', snippet: 'Just to close the loop: I forwarded the recording to two engineering directors. They\'re penciled in for…', tag: 'calm', ago: '14h', av: 'T', avBg: '#dc2626' },
{ from: 'Ironclad Mfg — Priya Anand', subject: 'Term sheet v2 attached', snippet: 'Here\'s our redline. Two small changes on the indemnification and one substantial on the data residency…', tag: 'urgent', ago: '18h', av: 'P', avBg: '#475569' },
{ from: 'Mosaic Health — Lin Chen', subject: 'Intro to procurement (cc\'d)', snippet: 'Looping in our head of procurement. They have time this Thursday or next Monday for a first call…', tag: 'fyi', ago: '1d', av: 'L', avBg: '#7c3aed' }
];
const dgEl = document.getElementById('digest');
dgEl.innerHTML = EMAILS.map((e, i) => `
<div class="digest-item" data-i="${i}">
<span class="digest-av" style="background:${e.avBg}">${e.av}</span>
<div>
<div class="digest-meta"><span class="digest-from">${e.from}</span> · ${e.ago} ago</div>
<div class="digest-subject">${e.subject}</div>
<div class="digest-snippet">${e.snippet}</div>
</div>
<span class="digest-tag ${e.tag}">${e.tag === 'urgent' ? 'reply today' : e.tag === 'warm' ? 'this week' : e.tag === 'calm' ? 'reviewed' : 'fyi'}</span>
</div>
`).join('');
dgEl.querySelectorAll('.digest-item').forEach(item => {
const e = EMAILS[+item.dataset.i];
item.addEventListener('click', () => {
toast({ kind: e.tag === 'urgent' ? 'urgent' : 'warn', title: e.subject, body: `From <strong>${e.from}</strong> · Opening Gmail thread…` });
});
});
// ─────── Stuck issues ───────
const STUCK = [
{ id: 'ENG-1284', title: 'Fix flaky test in payment-rollback path', who: 'Mira O.', whoBg: '#f97316', age: '5d', ageClass: '' },
{ id: 'ENG-1271', title: 'Refactor session token rotation (security review feedback)', who: 'Sam D.', whoBg: '#10b981', age: '3d', ageClass: 'warm' },
{ id: 'GROW-462', title: 'Wire Mixpanel funnel to onboarding step 3', who: 'Jules K.', whoBg: '#a855f7', age: '4d', ageClass: '' },
{ id: 'ENG-1268', title: 'Migrate cron worker to durable queue', who: 'Sam D.', whoBg: '#10b981', age: '6d', ageClass: '' },
{ id: 'DESIGN-118', title: 'Settings page redesign — needs eng pairing', who: 'Nora L.', whoBg: '#0ea5e9', age: '2d', ageClass: 'warm' }
];
document.getElementById('stuck').innerHTML = STUCK.map((s, i) => `
<div class="stuck-row" data-i="${i}">
<div>
<div class="id">${s.id}</div>
<div class="title">${s.title}</div>
</div>
<span class="who"><span class="av" style="background:${s.whoBg}">${s.who[0]}</span>${s.who}</span>
<span class="age ${s.ageClass}">stuck ${s.age}</span>
<button class="nudge" data-i="${i}">Nudge</button>
</div>
`).join('');
document.querySelectorAll('.stuck-row').forEach(row => {
const s = STUCK[+row.dataset.i];
row.querySelector('.nudge').addEventListener('click', e => {
e.stopPropagation();
toast({ kind: 'success', title: 'Nudge sent', body: `Sent ${s.who} a gentle Slack DM about <strong>${s.id}</strong>.` });
});
row.addEventListener('click', () => {
toast({ title: s.id, body: `<strong>${s.title}</strong> · stuck ${s.age} · assigned ${s.who}` });
});
});
// ─────── Schedule ───────
const NOW_HOUR = 8 * 60 + 42; // 8:42 AM
const SCHED = [
{ time: '7:30 AM', dur: '30m', title: 'Morning run', desc: 'Bay-side loop · Strava', source: 'CAL', t: 7*60 + 30 },
{ time: '9:00 AM', dur: '15m', title: 'Sync with Mira', desc: 'Pipeline review · 1:1', source: 'GMEET', t: 9*60 },
{ time: '10:00 AM', dur: '45m', title: 'Lattice Health follow-up', desc: 'Review SOC 2 + draft reply', source: 'FOCUS', t: 10*60 },
{ time: '11:30 AM', dur: '30m', title: 'Eng standup', desc: 'Fri release + Q3 capacity', source: 'GMEET', t: 11*60 + 30 },
{ time: '2:00 PM', dur: '60m', title: 'Pioneer Robotics — term sheet review', desc: 'Joint with Sam · legal walks through redlines', source: 'ZOOM', t: 14*60 },
{ time: '4:30 PM', dur: '20m', title: 'PR review block', desc: 'Clear the 4 PRs in queue', source: 'FOCUS', t: 16*60 + 30 }
];
document.getElementById('schedule').innerHTML = SCHED.map((s, i) => {
const past = s.t + parseInt(s.dur) < NOW_HOUR;
const now = s.t <= NOW_HOUR && NOW_HOUR < s.t + parseInt(s.dur);
const cls = past ? 'past' : (now ? 'now' : '');
return `
<div class="sched-row ${cls}" data-i="${i}">
<div class="time">${s.time}<span class="dur">${s.dur}</span></div>
<div class="dot"></div>
<div>
<div class="title">${s.title}</div>
<div class="desc">${s.desc}</div>
</div>
<span class="source">${s.source}</span>
</div>
`;
}).join('');
document.querySelectorAll('.sched-row').forEach(r => {
const s = SCHED[+r.dataset.i];
r.addEventListener('click', () => toast({ title: s.title, body: `${s.time} · ${s.dur} · ${s.source.toLowerCase()}<br/>${s.desc}` }));
});
// ─────── PRs ───────
const PRS = [
{ num: '#1284', title: 'Add session-token rotation primitive', files: '6 files', author: 'Sam', age: 'opened 18h ago', ageClass: 'urgent' },
{ num: '#1271', title: 'Migrate cron worker to durable queue (RFC implementation)', files: '14 files', author: 'Sam', age: '2d ago', ageClass: 'urgent' },
{ num: '#1268', title: 'Mixpanel funnel hooks for onboarding step 3', files: '4 files', author: 'Jules', age: '1d ago', ageClass: 'warm' },
{ num: '#1262', title: 'Settings page redesign · scaffolding only', files: '22 files', author: 'Nora', age: '4h ago', ageClass: 'calm' }
];
document.getElementById('prList').innerHTML = PRS.map((p, i) => `
<div class="pr-row" data-i="${i}">
<div>
<span class="num">${p.num} · @${p.author}</span>
<div class="title">${p.title}</div>
</div>
<span class="files">${p.files}</span>
<span class="age ${p.ageClass}">${p.age}</span>
</div>
`).join('');
document.querySelectorAll('.pr-row').forEach(r => {
const p = PRS[+r.dataset.i];
r.addEventListener('click', () => toast({ title: p.num + ' · ' + p.title, body: `${p.files} · @${p.author} · ${p.age}<br/>Opening on GitHub…` }));
});
// ─────── Toasts ───────
const toastBox = document.getElementById('toastBox');
function toast(opts) {
const { kind = '', title = '', body = '' } = (typeof opts === 'string') ? { body: opts } : opts;
const node = document.createElement('div');
node.className = `toast ${kind}`;
node.innerHTML = `<div class="t-title">${title}</div><div class="t-body">${body}</div>`;
toastBox.appendChild(node);
setTimeout(() => {
node.classList.add('fade');
setTimeout(() => node.remove(), 280);
}, 4200);
}
// ─────── Side actions + header ───────
const map = {
draft: { kind: 'success', title: 'Reply drafted', body: 'A short "we\'re ready when you are" is in your <strong>Lattice Health</strong> Gmail draft. Send when ready.' },
snooze1: { kind: 'warn', title: 'Snoozed', body: 'Lattice Health bumped to 11 AM.' },
generatePlan: { kind: '', title: 'Plan drafted', body: 'Next week\'s plan ready in Notion · 4 priorities · 14 commitments inferred from your week.' },
standup: { kind: 'success', title: 'Standup posted', body: '#eng-standup got a 4-line digest of yesterday\'s wins + today\'s focus.' },
snooze: { kind: 'warn', title: 'Briefing snoozed', body: 'See you in an hour. We\'ll re-pull fresh data.' },
email: { kind: 'success', title: 'Email scheduled', body: 'You\'ll get a copy of this briefing in your inbox at 7 PM.' }
};
document.querySelectorAll('[data-action]').forEach(b => {
b.addEventListener('click', () => {
const k = b.dataset.action;
if (map[k]) toast(map[k]);
});
});
document.getElementById('doneBtn').addEventListener('click', () => {
toast({ kind: 'success', title: 'Briefing done', body: '6 of 6 sections reviewed. See you tomorrow at 6:42 AM.' });
});
document.getElementById('snoozeBtn').addEventListener('click', () => {
toast({ kind: 'warn', title: 'Briefing snoozed', body: 'Re-pulling at 9:42 AM.' });
});
})();
</script>
</body>
</html>
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,16 @@
{
"title": "Quiver Live · Personal Portfolio",
"slug": "quiver-live-portfolio",
"pinned": true,
"preview": {
"type": "html",
"entry": "index.html"
},
"document": {
"format": "html_template_v1",
"templatePath": "template.html",
"generatedPreviewPath": "index.html",
"dataPath": "data.json",
"dataJson": {}
}
}
@@ -0,0 +1,453 @@
{
"summary": {
"brand": "quiver",
"pro": "Live",
"marketState": "NYSE Open · 14:32:18 EST",
"lastSync": "14:32:18 EST",
"totalLabel": "Total portfolio value · USD",
"accountsLabel": "All accounts (Robinhood + IBKR)",
"totalValue": "$284,521.64",
"todayPnl": "+$3,182.40",
"todayPct": "+1.13%",
"todayDirClass": "up",
"todayArrow": "▲",
"allTimePnl": "+$84,521.64",
"allTimePct": "+42.3%",
"allTimeDirClass": "up",
"allTimeArrow": "▲",
"alphaLabel": "vs S&P · YTD",
"alpha": "+18.7pp",
"areaPoints": "10,150 10,128 60,124 110,116 160,118 210,108 260,114 310,98 360,90 410,96 460,80 510,72 560,76 610,60 660,54 710,46 760,40 810,32 860,28 870,28 870,150",
"linePoints": "10,128 60,124 110,116 160,118 210,108 260,114 310,98 360,90 410,96 460,80 510,72 560,76 610,60 660,54 710,46 760,40 810,32 860,28",
"markerX": "860",
"markerY": "28",
"axisTicks": [
{ "label": "May '24" },
{ "label": "Jul" },
{ "label": "Sep" },
{ "label": "Nov" },
{ "label": "Jan '25" },
{ "label": "Mar" },
{ "label": "Today" }
]
},
"kpis": [
{
"label": "Today's P&L",
"value": "+$3,182.40",
"valueClass": "up",
"sub": "+1.13% · 8 winners / 2 losers",
"subClass": "up"
},
{
"label": "Buying power",
"value": "$12,438.21",
"valueClass": "",
"sub": "Cash + 2× margin available",
"subClass": ""
},
{
"label": "Best position · NVDA",
"value": "+187.4%",
"valueClass": "up",
"sub": "Avg cost $321.55 · Now $924.31",
"subClass": "up"
},
{
"label": "Alpha vs S&P 500 · YTD",
"value": "+18.7pp",
"valueClass": "alpha",
"sub": "Sharpe 1.42 · Beta 1.18 · Max DD −9.4%",
"subClass": ""
}
],
"featured": {
"iconText": "N",
"iconBg": "linear-gradient(135deg,#76b900,#4d8400)",
"iconFg": "#0a1502",
"symbol": "NVDA",
"name": "NVIDIA Corporation",
"sector": "Semiconductors · Mkt cap $2.27T",
"exchangeName": "NASDAQ",
"exchangeUrl": "https://www.nasdaq.com/market-activity/stocks/nvda",
"yahooUrl": "https://finance.yahoo.com/quote/NVDA",
"tradingViewUrl": "https://www.tradingview.com/symbols/NASDAQ-NVDA/",
"price": "$924.31",
"delta": "▲ +$21.74 +2.41% today",
"dirClass": "up",
"stats": [
{ "label": "Open", "value": "906.80" },
{ "label": "High", "value": "929.18" },
{ "label": "Low", "value": "902.44" },
{ "label": "Volume", "value": "38.42M" },
{ "label": "P/E", "value": "62.4" },
{ "label": "52W range", "value": "412 — 974" }
],
"areaPoints": "40,260 40,220 80,210 120,200 160,194 200,184 240,180 280,170 320,160 360,150 400,138 440,128 480,120 520,108 560,100 600,86 640,76 680,64 720,56 760,48 800,40 840,30 850,30 850,260",
"linePoints": "40,220 80,210 120,200 160,194 200,184 240,180 280,170 320,160 360,150 400,138 440,128 480,120 520,108 560,100 600,86 640,76 680,64 720,56 760,48 800,40 840,30",
"priceY": "30",
"priceLabelY": "21",
"priceLabelTextY": "34",
"priceLabelText": "924.31"
},
"ai": {
"tag": "Quiver AI · Signal #2417",
"headlineAccent": "Add to NVDA",
"headlineRest": "on Blackwell ramp + AI capex inflection.",
"sub": "Three converging catalysts and resilient channel checks. Conviction has stepped up since last week's hyperscaler guidance.",
"conviction": "87%",
"convictionWidth": "87%",
"targetLabel": "12-mo target",
"target": "$1,050",
"upside": "+13.6%",
"size": "+12 sh",
"sizeSub": "≈ $11,092 · 4.2% port",
"catalysts": [
{
"n": "01",
"title": "Blackwell shipments accelerating",
"text": "Foxconn and Wiwynn guidance both implied an Aug volume step-up. Backlog visibility extends through Q1 '26."
},
{
"n": "02",
"title": "Hyperscaler capex revised up",
"text": "MSFT, META, AMZN combined FY '25 capex now +34% YoY. AI infra is the binding allocation."
},
{
"n": "03",
"title": "Sentiment de-risked",
"text": "Short interest at 6-month high but RSI cooling from overbought. Setup mirrors the May '24 base before the +47% leg."
}
]
},
"holdingsPanel": {
"title": "Holdings · 9 positions",
"subtitle": "Live · last quote 14:32:18 EST"
},
"holdings": [
{
"symbol": "NVDA",
"companyName": "NVIDIA Corporation",
"icon": "N",
"iconBg": "linear-gradient(135deg,#76b900,#3d6800)",
"iconFg": "#0a1502",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/nvda",
"shares": "100",
"avgCost": "321.55",
"last": "924.31",
"marketValue": "$92,431",
"pnl": "+$60,276",
"pnlPct": "+187.4%",
"dirClass": "up",
"alloc": "32.5%",
"allocWidth": "32%"
},
{
"symbol": "AAPL",
"companyName": "Apple Inc.",
"icon": "A",
"iconBg": "linear-gradient(135deg,#0c0c0c,#3a3a3a)",
"iconFg": "#ffffff",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/aapl",
"shares": "220",
"avgCost": "152.10",
"last": "228.74",
"marketValue": "$50,323",
"pnl": "+$16,861",
"pnlPct": "+50.4%",
"dirClass": "up",
"alloc": "17.7%",
"allocWidth": "18%"
},
{
"symbol": "MSFT",
"companyName": "Microsoft Corp.",
"icon": "M",
"iconBg": "linear-gradient(135deg,#0078d4,#005a9e)",
"iconFg": "#ffffff",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/msft",
"shares": "85",
"avgCost": "328.40",
"last": "438.12",
"marketValue": "$37,240",
"pnl": "+$9,326",
"pnlPct": "+33.4%",
"dirClass": "up",
"alloc": "13.1%",
"allocWidth": "13%"
},
{
"symbol": "META",
"companyName": "Meta Platforms",
"icon": "M",
"iconBg": "linear-gradient(135deg,#1877f2,#0c5fc3)",
"iconFg": "#ffffff",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/meta",
"shares": "55",
"avgCost": "312.20",
"last": "596.18",
"marketValue": "$32,790",
"pnl": "+$15,619",
"pnlPct": "+91.0%",
"dirClass": "up",
"alloc": "11.5%",
"allocWidth": "11%"
},
{
"symbol": "GOOGL",
"companyName": "Alphabet Class A",
"icon": "G",
"iconBg": "linear-gradient(135deg,#4285f4,#34a853)",
"iconFg": "#ffffff",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/googl",
"shares": "160",
"avgCost": "128.90",
"last": "186.40",
"marketValue": "$29,824",
"pnl": "+$9,200",
"pnlPct": "+44.6%",
"dirClass": "up",
"alloc": "10.5%",
"allocWidth": "10%"
},
{
"symbol": "AMZN",
"companyName": "Amazon.com Inc.",
"icon": "A",
"iconBg": "linear-gradient(135deg,#ff9900,#cc7a00)",
"iconFg": "#1a0e00",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/amzn",
"shares": "90",
"avgCost": "142.50",
"last": "218.94",
"marketValue": "$19,705",
"pnl": "+$6,880",
"pnlPct": "+53.6%",
"dirClass": "up",
"alloc": "6.9%",
"allocWidth": "7%"
},
{
"symbol": "TSLA",
"companyName": "Tesla, Inc.",
"icon": "T",
"iconBg": "linear-gradient(135deg,#cc0000,#660000)",
"iconFg": "#ffffff",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/tsla",
"shares": "42",
"avgCost": "288.40",
"last": "312.55",
"marketValue": "$13,127",
"pnl": "+$1,015",
"pnlPct": "+8.4%",
"dirClass": "up",
"alloc": "4.6%",
"allocWidth": "5%"
},
{
"symbol": "AMD",
"companyName": "Advanced Micro Devices",
"icon": "A",
"iconBg": "linear-gradient(135deg,#ed1c24,#7a0000)",
"iconFg": "#ffffff",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/amd",
"shares": "52",
"avgCost": "158.90",
"last": "142.06",
"marketValue": "$7,387",
"pnl": "−$876",
"pnlPct": "−10.6%",
"dirClass": "down",
"alloc": "2.6%",
"allocWidth": "3%"
},
{
"symbol": "USD",
"companyName": "Settled cash",
"icon": "$",
"iconBg": "linear-gradient(135deg,#facc15,#a17400)",
"iconFg": "#1a1100",
"exchName": "—",
"exchUrl": "#",
"shares": "—",
"avgCost": "—",
"last": "1.00",
"marketValue": "$1,694",
"pnl": "—",
"pnlPct": "—",
"dirClass": "",
"alloc": "0.6%",
"allocWidth": "1%"
}
],
"watchlistPanel": {
"title": "Watchlist · curated",
"subtitle": "12 tickers · 14:32 EST"
},
"watchlist": [
{
"symbol": "PLTR",
"companyName": "Palantir",
"price": "$58.42",
"pct": "▲ 4.81%",
"dirClass": "up",
"sparkColor": "#22e58c",
"sparkPoints": "0,28 20,30 40,26 60,24 80,28 100,22 120,18 140,20 160,14 180,10 200,6",
"volume": "4.21M",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/pltr"
},
{
"symbol": "SHOP",
"companyName": "Shopify",
"price": "$112.84",
"pct": "▲ 2.13%",
"dirClass": "up",
"sparkColor": "#22e58c",
"sparkPoints": "0,26 20,28 40,22 60,24 80,20 100,22 120,18 140,16 160,12 180,14 200,10",
"volume": "6.84M",
"exchName": "NYSE",
"exchUrl": "https://www.nyse.com/quote/XNYS:SHOP"
},
{
"symbol": "TSM",
"companyName": "TSMC",
"price": "$208.77",
"pct": "▲ 1.62%",
"dirClass": "up",
"sparkColor": "#22e58c",
"sparkPoints": "0,24 20,22 40,26 60,20 80,18 100,20 120,16 140,14 160,12 180,10 200,8",
"volume": "12.4M",
"exchName": "NYSE",
"exchUrl": "https://www.nyse.com/quote/XNYS:TSM"
},
{
"symbol": "COIN",
"companyName": "Coinbase",
"price": "$236.10",
"pct": "▲ 3.48%",
"dirClass": "up",
"sparkColor": "#22e58c",
"sparkPoints": "0,28 20,30 40,26 60,28 80,22 100,18 120,20 140,14 160,10 180,12 200,6",
"volume": "8.92M",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/coin"
},
{
"symbol": "CRWD",
"companyName": "CrowdStrike",
"price": "$352.09",
"pct": "▼ 1.24%",
"dirClass": "down",
"sparkColor": "#ff4f6d",
"sparkPoints": "0,14 20,12 40,16 60,14 80,18 100,20 120,18 140,22 160,24 180,28 200,30",
"volume": "3.24M",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/crwd"
},
{
"symbol": "SMCI",
"companyName": "Super Micro",
"price": "$48.93",
"pct": "▲ 5.62%",
"dirClass": "up",
"sparkColor": "#22e58c",
"sparkPoints": "0,30 20,32 40,28 60,30 80,26 100,20 120,16 140,18 160,12 180,8 200,4",
"volume": "18.2M",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/smci"
},
{
"symbol": "UBER",
"companyName": "Uber Technologies",
"price": "$72.41",
"pct": "▲ 1.18%",
"dirClass": "up",
"sparkColor": "#22e58c",
"sparkPoints": "0,24 20,26 40,22 60,20 80,22 100,18 120,16 140,18 160,14 180,12 200,12",
"volume": "7.42M",
"exchName": "NYSE",
"exchUrl": "https://www.nyse.com/quote/XNYS:UBER"
},
{
"symbol": "NFLX",
"companyName": "Netflix",
"price": "$728.50",
"pct": "▼ 0.66%",
"dirClass": "down",
"sparkColor": "#ff4f6d",
"sparkPoints": "0,16 20,14 40,18 60,16 80,18 100,16 120,20 140,18 160,22 180,20 200,24",
"volume": "2.86M",
"exchName": "NASDAQ",
"exchUrl": "https://www.nasdaq.com/market-activity/stocks/nflx"
}
],
"newsPanel": {
"title": "Live signals & news",
"subtitle": "Filtered to your book"
},
"news": [
{
"time": "14:31",
"source": "AI · Quiver Research",
"headline": "▲ Hyperscaler capex tracker rolls forward — MSFT + META combined FY '25 guide now +34% YoY, reinforcing NVDA/SMCI thesis.",
"impactLabel": "+ Bull",
"impactClass": "pos"
},
{
"time": "14:18",
"source": "Bloomberg",
"headline": "Tesla cuts Model Y prices in China by 6% as BYD pressure intensifies in Q2 sales window.",
"impactLabel": "High risk",
"impactClass": "high"
},
{
"time": "13:54",
"source": "Reuters",
"headline": "FOMC minutes signal one more cut probable in Sept — yields fall 9 bps on the long end.",
"impactLabel": "+ Bull",
"impactClass": "pos"
},
{
"time": "13:30",
"source": "AI · Earnings whisper",
"headline": "CRWD whisper revised −2.1% after channel checks show enterprise renewal slippage.",
"impactLabel": "Watch",
"impactClass": "med"
},
{
"time": "12:48",
"source": "CNBC",
"headline": "Apple confirms Vision Pro 2 component orders for Q4 — 1.4M unit run rate implied.",
"impactLabel": "+ Bull",
"impactClass": "pos"
},
{
"time": "11:22",
"source": "AI · Anomaly detector",
"headline": "▲ SMCI dark-pool prints up 220% vs 30-day baseline — likely block accumulation.",
"impactLabel": "Watch",
"impactClass": "med"
}
],
"footer": {
"live": "Live data — refresh on demand",
"source": "Quotes via IEX Cloud · Robinhood + IBKR linked",
"version": "Quiver Live · v1.4.0"
}
}
@@ -0,0 +1,19 @@
{
"generatedAt": "2026-05-06T19:32:18.000Z",
"generatedBy": "agent",
"notes": "Personal trading portfolio dashboard — total value, today's P&L, featured ticker chart, holdings table, watchlist, news/signals, and an AI-generated investment recommendation. Designed to be refreshable on demand: price/holdings/news fields can be rewritten in data.json without re-authoring the template. All numbers, percentages, and SVG polyline points are pre-formatted into display-ready strings so the template stays free of formatting/expression logic; direction is encoded via an explicit dirClass field. External links (exchange, Yahoo, TradingView, per-row exchange page) are bound as plain attribute interpolations — no inline scripting. Refresh runner should write a fresh data.json that matches the template's binding shape; daemon validates against bounded JSON limits and re-derives index.html from template.html + data.json. No credentials, OAuth tokens, cookies, raw provider responses, or HTTP envelopes are persisted; forbidden keys (raw, payload, body, headers, cookie, authorization, token, secret, credential, password) are not present in data.json.",
"sources": [
{
"label": "Brokerage holdings (mocked)",
"type": "connector"
},
{
"label": "Quotes (mocked)",
"type": "connector"
},
{
"label": "AI recommendation",
"type": "derived"
}
]
}