[{"data":1,"prerenderedAt":417},["ShallowReactive",2],{"blog-\u002Fblog\u002Fmy-pastor-once-said":3},{"id":4,"title":5,"body":6,"canonical":405,"date":406,"description":407,"draft":408,"duration":405,"extension":409,"lang":405,"meta":410,"navigation":411,"path":412,"place":405,"placeLink":405,"readingTime":413,"seo":414,"stem":415,"__hash__":416},"blog\u002Fblog\u002Fmy-pastor-once-said.md","My Pastor Once Said",{"type":7,"value":8,"toc":390},"minimark",[9,14,22,37,56,61,74,81,84,88,95,98,104,115,119,122,129,135,138,142,158,165,168,179,185,189,192,200,203,207,210,213,264,267,271,282,285,296,304,308,311,320,323,329,333,336,342,346,349,355,378,386],[10,11,13],"h3",{"id":12},"a-campaign-for-the-lines-that-stuck-what-i-designed-what-broke-what-the-numbers-said-and-where-it-landed","A campaign for the lines that stuck. What I designed, what broke, what the numbers said, and where it landed.",[15,16,17],"p",{},[18,19],"img",{"alt":20,"src":21},"The My Pastor Once Said landing page, a wall of quote cards fanning out around the words \"My Pastor Once Said…\" in BethelFlow blue.","\u002Fimages\u002Fblog\u002Fmy-pastor-once-said\u002Fhero.png",[15,23,24,25,29,30,29,33,36],{},"Every church has a few lines it never forgot. The one your pastor said at the end of every service. The sentence you were half-listening to at seventeen that you're still carrying at twenty-seven. ",[26,27,28],"em",{},"Shalom."," ",[26,31,32],{},"You are not a mistake.",[26,34,35],{},"Shout hallelujah."," You know the ones.",[15,38,39,40,49,50,55],{},"We wanted to collect them. Not in a database, but on people's phones, in group chats, on statuses. So we built ",[41,42,46],"a",{"href":43,"rel":44},"https:\u002F\u002Fwww.bethelflow.com\u002Fmy-pastor-once-said",[45],"nofollow",[47,48,5],"strong",{}," for ",[41,51,54],{"href":52,"rel":53},"https:\u002F\u002Fwww.bethelflow.com\u002F",[45],"BethelFlow",": type a line your pastor never stopped saying, add a photo, and walk out with a card built to pass on. This is the story of designing it. The idea, my part in it, the rough edges, and where it is now.",[57,58,60],"h2",{"id":59},"borrowing-a-good-idea","Borrowing a good idea",[15,62,63,64,69,70,73],{},"The seed was ",[41,65,68],{"href":66,"rel":67},"https:\u002F\u002Fcowrywise.com\u002Fmothers-day",[45],"Cowrywise's Mother's Day page",", ",[26,71,72],{},"\"Mama once said…\"",", a small, joyful thing that turned a feeling everyone already had into something you could make and send in under a minute. We wanted that shape, pointed at the people who shaped our faith.",[15,75,76,77,80],{},"The strategy underneath it is plain. The movement is the hook, and ",[26,78,79],{},"\"start a church on BethelFlow\""," is the quiet conversion sitting under it. Top of funnel dressed as a keepsake. That framing mattered, because it set the bar for the whole thing: it had to be the kind of page a person shares for their own reasons, not because we asked.",[15,82,83],{},"My part was the experience. Make it fast to pick up, cheap to run, and good enough that sharing it never feels like an ad.",[57,85,87],{"id":86},"designing-for-a-five-second-pickup","Designing for a five-second pickup",[15,89,90,91,94],{},"The rule I set myself: a stranger should understand what this is and start making one before they've decided whether to. That meant the payoff couldn't live at the end of a form. It had to be ",[26,92,93],{},"right there"," while you typed.",[15,96,97],{},"So the create flow is a small wizard, the line, then the photo, then the card, with a live preview sitting next to the form the whole time. Cowrywise reveals the card after you finish. We diverged and let you watch it assemble as you go. You type a name, it appears on the card. You paste the quote, the card breathes. The work and the reward share a screen.",[15,99,100],{},[18,101],{"alt":102,"src":103},"Step one of the create flow: a form asking \"What did they always say?\" beside a live preview of the card updating in real time.","\u002Fimages\u002Fblog\u002Fmy-pastor-once-said\u002Fcompose-line.png",[15,105,106,107,110,111,114],{},"The whole thing wears BethelFlow's blue with a single gold accent for the campaign. Close enough to the brand that it reads as ",[26,108,109],{},"them",", distinct enough that it reads as an ",[26,112,113],{},"occasion",". No purple, no sub-brand. Just BethelFlow in a good mood.",[57,116,118],{"id":117},"the-photo-never-leaves-your-browser","The photo never leaves your browser",[15,120,121],{},"Here's the decision I'm still happy about. Early on, I didn't want to store anyone's uploads. Partly cost, mostly trust. Asking someone to hand you a photo of their pastor on day one is a big ask, and \"where does this go?\" is a fair question.",[15,123,124,125,128],{},"So at first, nothing went anywhere. The draft, your words and your photo, lived in the browser's own IndexedDB, survived a reload, and rendered the card locally. Download straight from the page. ",[26,126,127],{},"\"We're not storing your images\""," wasn't marketing. It was just literally where the code stood. The photo genuinely never left the device.",[15,130,131],{},[18,132],{"alt":133,"src":134},"Step two: adding a photo, with the card preview now showing the portrait behind the quote.","\u002Fimages\u002Fblog\u002Fmy-pastor-once-said\u002Fcompose-photo.png",[15,136,137],{},"That worked beautifully right up until people wanted to do the one thing the whole campaign was about: send it to someone.",[57,139,141],{"id":140},"the-url-is-not-a-database","The URL is not a database",[15,143,144,145,149,150,153,154,157],{},"A couple of friends, ",[146,147],"mention",{"handle":148},"codiejay"," and ",[146,151],{"handle":152},"Tolu-Mals",", are the reason this part of the story exists at all. They made their cards and immediately hit the wall the whole campaign was pointed at: they wanted to ",[26,155,156],{},"send"," them, and sharing wasn't really there yet. Their nudging is what turned \"download and keep\" into \"share and pass on,\" and, a few detours later, what put photos on Vercel Blob.",[15,159,160,161,164],{},"Because sharing breaks the no-storage stance. A shared link needs two things I didn't have: a page that lives at a URL, and an image a link preview can actually show. My first instinct was to cling to the principle and smuggle the entire card ",[26,162,163],{},"into the link",", encoding the name, quote, and year as a blob of base64 in the path, so the URL itself carried the card and I still stored nothing.",[15,166,167],{},"Two walls, fast. A phone photo is two to five megabytes; it cannot live in a URL, full stop, no matter how you encode it. And even the text-only version produced links about three hundred and twenty characters long, the kind of link that looks less like a keepsake and more like something a stranger DMs you before stealing your bank details.",[15,169,170,171,174,175,178],{},"I gave up gracefully. Photos moved to Vercel Blob, but on my terms: the upload only fires when you actually reach for ",[26,172,173],{},"share",", it's deduped by a hash of the file so refreshing never piles up copies, and a weekly job sweeps anything older than ninety days. The promise softened honestly, ",[26,176,177],{},"we don't store it until you choose to send it",", instead of breaking. And in return we got real shareable pages, copy-to-clipboard, and link previews with the actual card in them.",[15,180,181],{},[18,182],{"alt":183,"src":184},"The result screen: \"Download and share,\" with the finished card and a row of share buttons.","\u002Fimages\u002Fblog\u002Fmy-pastor-once-said\u002Fshare-light.png",[57,186,188],{"id":187},"the-preview-that-rendered-nothing","The preview that rendered nothing",[15,190,191],{},"The links worked. The previews didn't. A shared card came up blank where the image should be. I went hunting in the usual places first: the upload, the cache, my own markup. It took a while before it clicked that the culprit wasn't the card at all. It was satori.",[15,193,194,195,199],{},"The link-preview image is generated with ",[196,197,198],"code",{},"next\u002Fog",", which is satori under the hood. My card had a scrim, the dark gradient that keeps white text legible over a bright photo, and I'd written it the way you write it in a browser: pinned to all four edges, no explicit size. Every browser understands that. Satori doesn't. It sized the scrim to zero by zero, painted nothing, and on any light-coloured photo the white quote simply dissolved into the background.",[15,201,202],{},"The fix was one of those humbling one-liners: give the overlay real dimensions. The renderer that isn't a browser needs to be told the things a browser would have inferred. Scrim painted, text back, previews readable.",[57,204,206],{"id":205},"shorter-prettier-links","Shorter, prettier links",[15,208,209],{},"The base64 links still bothered me. A keepsake shouldn't look like a ransom note. So the payload came out of the URL entirely. Each shared card became a tiny record with a random eight-character id, and the link just points at that. Roughly three hundred and twenty characters down to about fifty.",[15,211,212],{},"The part I cared about was not breaking anyone who'd already shared. The resolver just looks at the length and picks a path. A short id gets looked up, an old long token still decodes inline like before.",[214,215,220],"pre",{"className":216,"code":217,"language":218,"meta":219,"style":219},"language-ts shiki shiki-themes github-light github-dark","return param.length \u003C= 16 ? loadShareCard(param) : decodeCard(param)\n","ts","",[196,221,222],{"__ignoreMap":219},[223,224,227,231,235,239,242,245,248,252,255,258,261],"span",{"class":225,"line":226},"line",1,[223,228,230],{"class":229},"szBVR","return",[223,232,234],{"class":233},"sVt8B"," param.",[223,236,238],{"class":237},"sj4cs","length",[223,240,241],{"class":229}," \u003C=",[223,243,244],{"class":237}," 16",[223,246,247],{"class":229}," ?",[223,249,251],{"class":250},"sScJk"," loadShareCard",[223,253,254],{"class":233},"(param) ",[223,256,257],{"class":229},":",[223,259,260],{"class":250}," decodeCard",[223,262,263],{"class":233},"(param)\n",[15,265,266],{},"Nobody's link died. Old and new both resolve to the same card. Migrations you can skip are the best kind.",[57,268,270],{"id":269},"the-first-day-and-the-dip","The first day, and the dip",[15,272,273,274,277,278,281],{},"Launch day did better than I'd let myself hope. In a single day the page took ",[47,275,276],{},"168 views from 117 different people",", and over the run it made ",[47,279,280],{},"72 cards from 42 of them",". For a side campaign built around a feeling, that's a lot of people choosing to make something.",[15,283,284],{},"And then, as campaigns do, it breathed out. The day after launch it was 56 views, then 18 the day after, then single digits. A steady exhale over the week.",[15,286,287,288,291,292,295],{},"Some of that taper is just gravity. But some of it I did to myself. Midweek I shipped a security-headers change, closed my laptop, and went to bed pleased. The new Content-Security-Policy quietly broke image downloading for everyone: the browser refused to fetch an image the app had literally just made. PostHog is how I found out. ",[47,289,290],{},"42 failed shares",", almost all of them from a ",[26,293,294],{},"handful"," of people tapping a dead button over and over, exactly the way you jab an elevator button that isn't wired to anything.",[15,297,298,299,303],{},"That bug got its own post, because it deserved one. ",[41,300,302],{"href":301},"\u002Fblog\u002Fthe-button-that-did-nothing","The button that did nothing"," is the full autopsy. The short version: I'd been building on the one device where everything worked, and instrumenting failure so faintly that only luck told me it was happening.",[57,305,307],{"id":306},"most-of-them-were-on-a-phone","Most of them were on a phone",[15,309,310],{},"The same dashboard said something quieter and more important than any single bug. Most people arrived on a phone, and on a phone they finished at less than half the rate they did on a desktop. I had lovingly designed a three-step, side-by-side wizard for a screen most of my users weren't holding.",[15,312,313,314,149,316,319],{},"So the create flow got rebuilt for a thumb. The separate ",[26,315,225],{},[26,317,318],{},"photo"," steps merged into one compose screen with the card preview pinned to the top, so the payoff is always in frame instead of two taps away. \"Take a photo\" wires straight to the camera. Desktop keeps its roomy stepped wizard. The layout branches on the viewport rather than pretending one shape fits both.",[15,321,322],{},"The rest was performance, because a beautiful page that stutters on a mid-range Android is not a beautiful page. The marquee, that scrolling wall of example cards, got a lighter animation that pauses on hover and stands still for anyone who's asked for reduced motion. The decorative images lazy-load so they stop fighting the first paint. And the card-capture engine changed under the hood after WebKit kept dropping the photo out of exports.",[15,324,325],{},[18,326],{"alt":327,"src":328},"The scrolling wall of community cards, real lines people submitted.","\u002Fimages\u002Fblog\u002Fmy-pastor-once-said\u002Fmarquee.png",[57,330,332],{"id":331},"one-card-two-themes","One card, two themes",[15,334,335],{},"A small thing I'm fond of: the card and the whole page render in light and dark off the same live preview, so it looks intentional whichever way your phone is set. Same card, same words, different room.",[15,337,338],{},[18,339],{"alt":340,"src":341},"The result screen in dark mode, the card glowing against a near-black page.","\u002Fimages\u002Fblog\u002Fmy-pastor-once-said\u002Fshare-dark.png",[57,343,345],{"id":344},"where-it-landed","Where it landed",[15,347,348],{},"Here's the whole shape of it, start to finish.",[15,350,351],{},[18,352],{"alt":353,"src":354},"A showcase board of the finished product: the landing page, the three-step create flow, the light and dark cards, and the community wall.","\u002Fimages\u002Fblog\u002Fmy-pastor-once-said\u002Fshowcase.png",[15,356,357,358,361,362,29,365,361,368,29,371,361,374,377],{},"Looking back, the thread through every good decision was the same. I kept holding a principle, and users kept, gently, asking me to bend it. ",[26,359,360],{},"Don't store the photo"," met ",[26,363,364],{},"let me send this to my sister.",[26,366,367],{},"Keep it pure in the URL",[26,369,370],{},"a phone photo is five megabytes.",[26,372,373],{},"Ship the elegant wizard",[26,375,376],{},"I'm on a bus, on a phone, using one thumb."," The design got better every time I let the principle bend without letting it break: storing photos only at the moment of sharing, shortening links without stranding old ones, rebuilding the flow for the device people actually held.",[15,379,380,381,385],{},"It's evergreen, and it's still up. If your pastor had a line you never forgot, the one you can still hear in their voice, go ",[41,382,384],{"href":43,"rel":383},[45],"make a card",". Send it to someone who needs it today. We'd genuinely love to see the ones that stuck with you.",[387,388,389],"style",{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":219,"searchDepth":391,"depth":391,"links":392},2,[393,395,396,397,398,399,400,401,402,403,404],{"id":12,"depth":394,"text":13},3,{"id":59,"depth":391,"text":60},{"id":86,"depth":391,"text":87},{"id":117,"depth":391,"text":118},{"id":140,"depth":391,"text":141},{"id":187,"depth":391,"text":188},{"id":205,"depth":391,"text":206},{"id":269,"depth":391,"text":270},{"id":306,"depth":391,"text":307},{"id":331,"depth":391,"text":332},{"id":344,"depth":391,"text":345},null,"2026-08-09","The product-design story behind a BethelFlow campaign, a card generator for the lines our pastors never stopped saying. What we designed, the rough edges, the numbers, and where it landed.",false,"md",{},true,"\u002Fblog\u002Fmy-pastor-once-said","9min",{"title":5,"description":407},"blog\u002Fmy-pastor-once-said","KVL-YwffoSQVgJ3a71E3XVrshufWO2hSLdYUKCJcCmg",1787080278161]