Skip to content

Commit 2b4d075

Browse files
authored
Merge pull request #21 from lladawn/dev
Update share metadata and OG image for the agent pitch
2 parents 1f214dc + 7318cac commit 2b4d075

4 files changed

Lines changed: 223 additions & 84 deletions

File tree

app/layout.jsx

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,24 +17,25 @@ const analyticsEnabled = process.env.NEXT_PUBLIC_ENABLE_ANALYTICS === "true";
1717
const umamiScriptSrc = process.env.NEXT_PUBLIC_UMAMI_SRC || "https://breathe-umami.vercel.app/script.js";
1818
const umamiWebsiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID || "";
1919
const description =
20-
"Mumbl is a Slack app for saving the reasoning behind engineering work: the tradeoffs, gut calls, and hard-won lessons that usually disappear.";
20+
"Agent work still feels like a terminal. Mumbl is a workspace where your AI agents are collaborators you can see — working, blocked, or done, legible at a glance.";
2121

2222
export const metadata = {
2323
metadataBase: new URL(appUrl),
2424
title: {
25-
default: "mumbl",
25+
default: "mumbl — see your agents work",
2626
template: "%s · mumbl",
2727
},
2828
description,
2929
applicationName: "mumbl",
3030
keywords: [
3131
"mumbl",
32-
"slack private notes",
33-
"team reads",
34-
"field notes",
32+
"ai agents",
33+
"agent workspace",
34+
"agent observability",
35+
"spatial workspace",
36+
"claude code",
37+
"mcp",
3538
"engineering teams",
36-
"team heartbeat",
37-
"work culture",
3839
],
3940
authors: [{ name: "mumbl" }],
4041
creator: "mumbl",
@@ -43,7 +44,7 @@ export const metadata = {
4344
canonical: "/",
4445
},
4546
openGraph: {
46-
title: "mumbl - save the reasoning behind the work",
47+
title: "mumbl — a workspace where your AI agents are collaborators you can see",
4748
description,
4849
url: "/",
4950
siteName: "mumbl",
@@ -53,13 +54,13 @@ export const metadata = {
5354
url: "/opengraph-image",
5455
width: 1200,
5556
height: 630,
56-
alt: "mumbl - save the reasoning behind the work",
57+
alt: "A pixel workspace: AI collaborators at their stations, marked working, blocked and done.",
5758
},
5859
],
5960
},
6061
twitter: {
6162
card: "summary_large_image",
62-
title: "mumbl - save the reasoning behind the work",
63+
title: "mumbl — a workspace where your AI agents are collaborators you can see",
6364
description,
6465
images: ["/twitter-image"],
6566
},

app/opengraph-image.jsx

Lines changed: 180 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { ImageResponse } from "next/og";
22

3-
export const alt = "mumbl - save what you're thinking before you polish what you say";
3+
export const alt = "mumbl — a workspace where your AI agents are collaborators you can see";
44
export const size = {
55
width: 1200,
66
height: 630,
@@ -11,44 +11,159 @@ export default function Image() {
1111
return new ImageResponse(<ShareImage />, size);
1212
}
1313

14+
// Palette and sprite geometry are the same as the landing and /demo, so the
15+
// share card looks like the thing people land on. Satori has no <svg> rect
16+
// support worth relying on, so the pixel art is built from positioned divs —
17+
// which is what pixel art is anyway.
18+
const C = {
19+
teal: "#14312F",
20+
tealRaised: "#1B3B38",
21+
wall: "#1B3B38",
22+
ink: "#0A1918",
23+
cream: "#F0E2C8",
24+
muted: "#9DB3AC",
25+
ochre: "#E0A458",
26+
red: "#C25450",
27+
green: "#6FA88A",
28+
floor: "#6B4A2F",
29+
floorAlt: "#63432A",
30+
deskTop: "#A5713F",
31+
desk: "#8A5E38",
32+
monitor: "#14201F",
33+
screen: "#2E4A46",
34+
};
35+
36+
// [x, y, w, h, colourKey] on a 14x20 grid — identical to the demo's makePerson
37+
const PERSON = [
38+
[4, 1, 6, 3, "hair"],
39+
[3, 2, 8, 4, "hair"],
40+
[4, 4, 6, 4, "skin"],
41+
[4, 3, 6, 1, "hair"],
42+
[5, 6, 1, 1, "eye"],
43+
[8, 6, 1, 1, "eye"],
44+
[3, 8, 8, 7, "shirt"],
45+
[2, 9, 1, 5, "shirt"],
46+
[11, 9, 1, 5, "shirt"],
47+
[2, 13, 1, 2, "skin"],
48+
[11, 13, 1, 2, "skin"],
49+
[4, 15, 2, 4, "pants"],
50+
[8, 15, 2, 4, "pants"],
51+
[3, 19, 3, 1, "shoe"],
52+
[8, 19, 3, 1, "shoe"],
53+
];
54+
55+
function Person({ look, s }) {
56+
return (
57+
<div style={{ display: "flex", position: "relative", width: 14 * s, height: 20 * s }}>
58+
{look.glow ? (
59+
<div
60+
style={{
61+
position: "absolute",
62+
left: 1 * s,
63+
top: 0,
64+
width: 12 * s,
65+
height: 20 * s,
66+
background: look.glow,
67+
opacity: 0.22,
68+
}}
69+
/>
70+
) : null}
71+
{PERSON.map(([x, y, w, h, key], i) => (
72+
<div
73+
key={i}
74+
style={{
75+
position: "absolute",
76+
left: x * s,
77+
top: y * s,
78+
width: w * s,
79+
height: h * s,
80+
background: key === "eye" ? "#14201F" : key === "shoe" ? "#2A1D12" : look[key],
81+
}}
82+
/>
83+
))}
84+
</div>
85+
);
86+
}
87+
88+
function Pill({ label, color, left }) {
89+
return (
90+
<div
91+
style={{
92+
position: "absolute",
93+
left,
94+
top: 16,
95+
display: "flex",
96+
padding: "5px 11px",
97+
background: "rgba(7,11,11,0.92)",
98+
border: `2px solid ${color}`,
99+
color,
100+
fontSize: 17,
101+
fontWeight: 700,
102+
letterSpacing: 1.5,
103+
}}
104+
>
105+
{label}
106+
</div>
107+
);
108+
}
109+
110+
// Everything is measured so the character's feet land inside the room band —
111+
// at larger scales the sprites overflow and get clipped away entirely.
112+
function Station({ left, look, s }) {
113+
return (
114+
<div style={{ position: "absolute", left, top: 52, display: "flex", width: 14 * s, height: 180 }}>
115+
{/* monitor */}
116+
<div style={{ position: "absolute", left: -28, top: 0, width: 100, height: 52, background: C.monitor }} />
117+
<div style={{ position: "absolute", left: -16, top: 10, width: 76, height: 32, background: C.screen }} />
118+
<div style={{ position: "absolute", left: -8, top: 18, width: 44, height: 5, background: C.green, opacity: 0.7 }} />
119+
<div style={{ position: "absolute", left: -8, top: 29, width: 26, height: 5, background: "#9FD4C4", opacity: 0.5 }} />
120+
{/* desk */}
121+
<div style={{ position: "absolute", left: -52, top: 58, width: 152, height: 12, background: C.deskTop }} />
122+
<div style={{ position: "absolute", left: -52, top: 70, width: 152, height: 24, background: C.desk }} />
123+
{/* collaborator */}
124+
<div style={{ position: "absolute", left: 0, top: 96, display: "flex" }}>
125+
<Person look={look} s={s} />
126+
</div>
127+
</div>
128+
);
129+
}
130+
14131
function ShareImage() {
132+
const s = 4;
133+
const looks = {
134+
scout: { hair: "#3B2A1E", skin: "#D9A277", shirt: "#2F6B63", pants: "#2A2E33", glow: "#7FD4C6" },
135+
human: { hair: "#2B211A", skin: "#C98B5E", shirt: "#F0E2C8", pants: "#2A2E33", glow: null },
136+
builder: { hair: "#1E1B18", skin: "#8C5F3C", shirt: "#3E5F8A", pants: "#242830", glow: "#8FB6E8" },
137+
};
138+
15139
return (
16140
<div
17141
style={{
18142
width: "100%",
19143
height: "100%",
20144
display: "flex",
21145
flexDirection: "column",
22-
justifyContent: "space-between",
23-
background: "#fbfaf7",
24-
color: "#161412",
146+
background: C.teal,
147+
color: C.cream,
25148
fontFamily: "Arial, Helvetica, sans-serif",
26-
padding: "56px 66px",
149+
padding: "44px 60px 0",
27150
position: "relative",
28151
}}
29152
>
30-
<div
31-
style={{
32-
position: "absolute",
33-
inset: 0,
34-
backgroundImage:
35-
"linear-gradient(90deg, rgba(22,20,18,0.055) 1px, transparent 1px), linear-gradient(rgba(22,20,18,0.055) 1px, transparent 1px)",
36-
backgroundSize: "36px 36px",
37-
}}
38-
/>
153+
{/* brand row */}
39154
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", position: "relative" }}>
40-
<div style={{ display: "flex", alignItems: "center", gap: 22, fontSize: 42, fontWeight: 900 }}>
155+
<div style={{ display: "flex", alignItems: "center", gap: 20, fontSize: 40, fontWeight: 900 }}>
41156
<div
42157
style={{
43-
width: 76,
44-
height: 76,
45-
borderRadius: 24,
46-
background: "#f8dda0",
47-
border: "3px solid #161412",
158+
width: 68,
159+
height: 68,
160+
background: C.ochre,
161+
border: `3px solid ${C.ink}`,
162+
color: C.teal,
48163
display: "flex",
49164
alignItems: "center",
50165
justifyContent: "center",
51-
boxShadow: "6px 6px 0 #161412",
166+
boxShadow: `6px 6px 0 ${C.ink}`,
52167
}}
53168
>
54169
m
@@ -57,70 +172,64 @@ function ShareImage() {
57172
</div>
58173
<div
59174
style={{
60-
padding: "14px 18px",
61-
border: "2px solid #161412",
62-
borderRadius: 999,
63-
background: "#d8e8ff",
64-
color: "#173c66",
65-
fontSize: 24,
66-
fontWeight: 900,
175+
display: "flex",
176+
padding: "12px 18px",
177+
border: `2px solid ${C.ochre}`,
178+
background: "rgba(7,11,11,0.5)",
179+
color: C.ochre,
180+
fontSize: 22,
181+
fontWeight: 700,
182+
letterSpacing: 1,
67183
}}
68184
>
69-
slack beta · private first
185+
early build · mumbl.wtf/demo
70186
</div>
71187
</div>
72188

73-
<div style={{ display: "flex", gap: 36, alignItems: "flex-end", position: "relative" }}>
74-
<div style={{ flex: 1, display: "flex", flexDirection: "column" }}>
75-
<div style={{ fontSize: 76, lineHeight: 0.96, fontWeight: 900, letterSpacing: 0 }}>
76-
save what you're thinking before you polish what you say.
77-
</div>
78-
<div style={{ marginTop: 28, fontSize: 30, lineHeight: 1.35, color: "#5f5952", maxWidth: 760 }}>
79-
/mumbl saves the honest version privately. shape it into a field note later, or keep it for yourself.
80-
</div>
189+
{/* headline */}
190+
<div style={{ display: "flex", flexDirection: "column", position: "relative", marginTop: 48 }}>
191+
<div style={{ fontSize: 56, lineHeight: 1.06, fontWeight: 900, maxWidth: 1010 }}>
192+
agent work still feels like a terminal.
81193
</div>
82-
<div
83-
style={{
84-
width: 360,
85-
border: "3px solid #161412",
86-
borderRadius: 10,
87-
background: "#ffffff",
88-
boxShadow: "10px 10px 0 #161412",
89-
padding: 20,
90-
display: "flex",
91-
flexDirection: "column",
92-
gap: 14,
93-
}}
94-
>
95-
<CardLine
96-
title="# tiny-fires"
97-
text={'/mumbl i said "just needs polish" again, but honestly i think i\'m scared to name the rewrite.'}
98-
color="#f8dda0"
99-
/>
100-
<CardLine title="only visible to you" text="saved privately to mumbl." color="#d9f2df" />
101-
<CardLine title="team read" text="publish only when the useful part can help the team." color="#d8e8ff" />
194+
<div style={{ marginTop: 16, fontSize: 25, lineHeight: 1.35, color: C.muted, maxWidth: 900 }}>
195+
Most people use a code editor instead. Mumbl is a workspace where your AI agents are collaborators
196+
you can see.
102197
</div>
103198
</div>
104-
</div>
105-
);
106-
}
107199

108-
function CardLine({ title, text, color }) {
109-
return (
110-
<div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
200+
{/* the room */}
111201
<div
112202
style={{
113-
alignSelf: "flex-start",
114-
padding: "5px 10px",
115-
borderRadius: 999,
116-
background: color,
117-
fontSize: 18,
118-
fontWeight: 900,
203+
display: "flex",
204+
// anchored to the bottom edge rather than flowed, so the sprites'
205+
// feet always land inside the card no matter how the text wraps
206+
position: "absolute",
207+
left: 60,
208+
bottom: 0,
209+
width: 1080,
210+
height: 262,
211+
border: `3px solid ${C.ink}`,
212+
borderBottom: "none",
213+
background: C.floor,
214+
overflow: "hidden",
119215
}}
120216
>
121-
{title}
217+
<div style={{ position: "absolute", left: 0, top: 0, width: 1080, height: 64, background: C.wall }} />
218+
<div style={{ position: "absolute", left: 0, top: 60, width: 1080, height: 4, background: C.ink }} />
219+
{/* windows */}
220+
<div style={{ position: "absolute", left: 330, top: 12, width: 140, height: 40, background: "#E8B979" }} />
221+
<div style={{ position: "absolute", left: 330, top: 12, width: 140, height: 16, background: "#F2D4A2" }} />
222+
<div style={{ position: "absolute", left: 660, top: 12, width: 140, height: 40, background: "#E8B979" }} />
223+
<div style={{ position: "absolute", left: 660, top: 12, width: 140, height: 16, background: "#F2D4A2" }} />
224+
225+
<Pill label="WORKING" color={C.ochre} left={110} />
226+
<Pill label="BLOCKED" color={C.red} left={492} />
227+
<Pill label="DONE" color={C.green} left={872} />
228+
229+
<Station left={150} look={looks.scout} s={s} />
230+
<Station left={528} look={looks.human} s={s} />
231+
<Station left={900} look={looks.builder} s={s} />
122232
</div>
123-
<div style={{ fontSize: 22, lineHeight: 1.22, color: "#4e4842" }}>{text}</div>
124233
</div>
125234
);
126235
}

app/slack/page.jsx

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,25 @@
11
import HomeView from "../../src/components/HomeView";
22

3+
// The root metadata is the agent-workspace pitch, so this page must state its
4+
// own — it is a different product story to a different reader.
5+
const description =
6+
"Hit /mumbl in Slack to capture a thought the moment it happens, then publish the ones worth sharing as a team read.";
7+
38
export const metadata = {
49
title: "mumbl for slack",
5-
description: "Save the why behind your team's work — one line from Slack, published as team reads.",
10+
description,
11+
alternates: { canonical: "/slack" },
12+
openGraph: {
13+
title: "mumbl for slack — save the why behind your team's work",
14+
description,
15+
url: "/slack",
16+
type: "website",
17+
},
18+
twitter: {
19+
card: "summary_large_image",
20+
title: "mumbl for slack — save the why behind your team's work",
21+
description,
22+
},
623
};
724

825
export default function SlackPage() {

0 commit comments

Comments
 (0)