11import { 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 " ;
44export 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+
14131function 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}
0 commit comments