Live UI Bento
Bento grid features section for React where every tile works: freeze a card, convert FX, clear approvals, scrub a chart.
- Feature Sections
- Fintech
- React only · 0 deps
- 1 file
Browser support · All evergreen browsers. Row collapse uses grid-template-rows interpolation (Chrome 107+, Safari 16+, Firefox 66+).
Install. One command, or copy the file.
$
npx shadcn@latest add https://freedesignmd.com/r/bento-live-ui.json- Byte-identical to the preview
- No Tailwind or config needed
- SSR-safe · keyboard accessible
- Respects reduced motion & transparency
components/features/bento-live-ui.tsx
1/**2 * BentoLiveUi, an asymmetric feature bento for a business bank where every3 * tile is a small working piece of the product instead of an icon: a card4 * you can freeze, an FX converter, an approval queue you can clear and a5 * spend chart that draws itself when it scrolls into view.6 *7 * Props: eyebrow, title, lede, card, rates, approvals, spend, categories.8 * CSS vars on the root: --fd-blu-bg, --fd-blu-ink, --fd-blu-forest, --fd-blu-lime.9 *10 * Demo images: /img/people/pN.webp (requester avatars). Copy them next to your11 * page or pass your own `avatar` urls.12 *13 * Browser support: all evergreen browsers. Collapsing rows use14 * grid-template-rows interpolation (Chrome 107+, Safari 16+, Firefox 66+),15 * older engines simply remove the row.16 * Accessibility: the freeze control is a real role="switch"; the converter is17 * a labelled input plus a radio group of currencies; approve / decline are18 * buttons with full labels and a polite live region; the chart is keyboard19 * scrubbable (arrow keys) and announces the focused week.20 * Reduced motion: the chart is drawn immediately, numbers do not tween, rows21 * leave without collapsing.22 */23import { useEffect, useId, useMemo, useRef, useState } from "react";24import type { ReactNode } from "react";25 26export interface BluCard {27 holder: string;28 company: string;29 last4: string;30 limit: number;31 spent: number;32}33export interface BluRate {34 code: string;35 name: string;36 rate: number;37 arrives: string;38}39export interface BluApproval {40 id: string;41 payee: string;42 memo: string;43 amount: number;44 requester: string;45 avatar: string;46 when: string;47}48export interface BluWeek {49 label: string;50 value: number;51}52export interface BluCategory {53 label: string;54 share: number;55}56 57export interface BentoLiveUiProps {58 eyebrow?: ReactNode;59 title?: ReactNode;60 lede?: ReactNode;61 card?: BluCard;62 rates?: BluRate[];63 approvals?: BluApproval[];64 spend?: BluWeek[];65 categories?: BluCategory[];66 className?: string;67}68 69const eur = (n: number, d = 2) =>70 "€" + n.toLocaleString("en-IE", { minimumFractionDigits: d, maximumFractionDigits: d });71const fmt = (n: number, d = 2) => n.toLocaleString("en-IE", { minimumFractionDigits: d, maximumFractionDigits: d });72 73const DEFAULT_CARD: BluCard = { holder: "Mara Lindqvist", company: "Okapi Studio GmbH", last4: "4417", limit: 4000, spent: 1284.6 };74const DEFAULT_RATES: BluRate[] = [75 { code: "GBP", name: "Pound sterling", rate: 0.8541, arrives: "Today, 16:00" },76 { code: "USD", name: "US dollar", rate: 1.0838, arrives: "Today, 18:30" },77 { code: "CHF", name: "Swiss franc", rate: 0.9412, arrives: "Today, 15:00" },78 { code: "SEK", name: "Swedish krona", rate: 11.482, arrives: "Tomorrow, 09:00" },79];80const DEFAULT_APPROVALS: BluApproval[] = [81 { id: "a1", payee: "Hetzner Online", memo: "Invoice 2026-1184, servers", amount: 612.4, requester: "Jonas", avatar: "/img/people/p8.webp", when: "2h ago" },82 { id: "a2", payee: "Druckerei Hansen", memo: "Lookbook print run, 400 copies", amount: 2380, requester: "Ayla", avatar: "/img/people/p6.webp", when: "Yesterday" },83 { id: "a3", payee: "Deutsche Bahn", memo: "Berlin to Milan, 2 seats", amount: 318.9, requester: "Kofi", avatar: "/img/people/p4.webp", when: "Mon" },84];85const DEFAULT_SPEND: BluWeek[] = [86 { label: "Aug 4", value: 2840 }, { label: "Aug 11", value: 3120 }, { label: "Aug 18", value: 2610 },87 { label: "Aug 25", value: 4480 }, { label: "Sep 1", value: 3790 }, { label: "Sep 8", value: 3350 },88 { label: "Sep 15", value: 5120 }, { label: "Sep 22", value: 4210 }, { label: "Sep 29", value: 3980 },89 { label: "Oct 6", value: 4630 }, { label: "Oct 13", value: 5480 }, { label: "Oct 20", value: 4592 },90];91const DEFAULT_CATS: BluCategory[] = [92 { label: "Contractors", share: 38 }, { label: "Software", share: 24 }, { label: "Travel", share: 21 }, { label: "Everything else", share: 17 },93];94 95function useReduced() {96 const [r, setR] = useState(false);97 useEffect(() => {98 const m = window.matchMedia("(prefers-reduced-motion: reduce)");99 setR(m.matches);100 const on = () => setR(m.matches);101 m.addEventListener("change", on);102 return () => m.removeEventListener("change", on);103 }, []);104 return r;105}106 107/** Tween a number toward a target with a critically damped spring (rAF). */108function useSpringNumber(target: number, reduced: boolean) {109 const [v, setV] = useState(target);110 const cur = useRef({ x: target, vel: 0 });111 useEffect(() => {112 if (reduced) { cur.current = { x: target, vel: 0 }; setV(target); return; }113 let raf = 0, last = performance.now();114 const tick = (t: number) => {115 const dt = Math.min(0.032, (t - last) / 1000); last = t;116 const s = cur.current;117 const f = -380 * (s.x - target) - 38 * s.vel;118 s.vel += f * dt; s.x += s.vel * dt;119 if (Math.abs(s.x - target) < Math.abs(target) * 0.00002 + 0.0005 && Math.abs(s.vel) < 0.01) { s.x = target; s.vel = 0; setV(target); return; }120 setV(s.x); raf = requestAnimationFrame(tick);121 };122 raf = requestAnimationFrame(tick);123 return () => cancelAnimationFrame(raf);124 }, [target, reduced]);125 return v;126}127 128export function BentoLiveUi({129 eyebrow = "Tallyard for business",130 title = <>Spend, approve,<br />pay. <em>Before lunch.</em></>,131 lede = "One account for a company of 4 or 40. Cards with limits that mean something, invoices that wait for the right person, and euros that leave at the mid-market rate.",132 card = DEFAULT_CARD,133 rates = DEFAULT_RATES,134 approvals = DEFAULT_APPROVALS,135 spend = DEFAULT_SPEND,136 categories = DEFAULT_CATS,137 className,138}: BentoLiveUiProps) {139 const reduced = useReduced();140 const tid = useId();141 return (142 <section className={`fd-blu${className ? " " + className : ""}`} aria-labelledby={tid}>143 <style>{css}</style>144 <header className="fd-blu-head">145 <div>146 <p className="fd-blu-eyebrow"><span className="fd-blu-dot" aria-hidden="true" />{eyebrow}</p>147 <h2 id={tid} className="fd-blu-title">{title}</h2>148 </div>149 <p className="fd-blu-lede">{lede}</p>150 </header>151 <div className="fd-blu-grid">152 <CardTile card={card} />153 <SpendTile spend={spend} categories={categories} reduced={reduced} />154 <FxTile rates={rates} reduced={reduced} />155 <ApprovalTile items={approvals} reduced={reduced} />156 </div>157 </section>158 );159}160 161/* ───────────── card freeze ───────────── */162const TXS = [163 { n: "Figma", w: "Today, 09:12", a: 45 },164 { n: "Café Kosmos", w: "Yesterday, 13:40", a: 18.6 },165];166function CardTile({ card }: { card: BluCard }) {167 const [frozen, setFrozen] = useState(false);168 const pct = Math.min(100, (card.spent / card.limit) * 100);169 const id = useId();170 return (171 <article className="fd-blu-tile fd-blu-t-card" data-frozen={frozen ? "" : undefined}>172 <div className="fd-blu-tag"><span>01</span>Cards</div>173 <h3 className="fd-blu-h">Freeze a card from a train platform.</h3>174 <div className="fd-blu-cardwrap">175 <div className="fd-blu-card" aria-hidden="true">176 <svg className="fd-blu-guil" viewBox="0 0 340 214" preserveAspectRatio="none">177 {Array.from({ length: 14 }).map((_, i) => (178 <path key={i} d={`M-10 ${40 + i * 12} C 80 ${10 + i * 12}, 180 ${90 + i * 9}, 350 ${30 + i * 13}`} />179 ))}180 </svg>181 <div className="fd-blu-card-top">182 <span className="fd-blu-wm">tallyard<i>.</i></span>183 <span className="fd-blu-kind">Debit · Business</span>184 </div>185 <div className="fd-blu-chip"><i /><i /><i /></div>186 <div className="fd-blu-card-bot">187 <div>188 <span className="fd-blu-holder">{card.holder}</span>189 <span className="fd-blu-co">{card.company}</span>190 </div>191 <span className="fd-blu-num">•••• {card.last4}</span>192 </div>193 <div className="fd-blu-frost">194 <svg viewBox="0 0 340 214" preserveAspectRatio="xMidYMid slice">195 {[[54, 48, 26], [278, 60, 34], [190, 170, 22], [92, 168, 16], [236, 118, 12], [140, 82, 10]].map(([x, y, r], i) => (196 <g key={i} transform={`translate(${x} ${y})`} style={{ ["--i" as string]: i }}>197 {[0, 60, 120].map((a) => (198 <g key={a} transform={`rotate(${a})`}>199 <line x1={-r} y1="0" x2={r} y2="0" />200 <line x1={r * 0.55} y1="0" x2={r * 0.75} y2={-r * 0.2} /><line x1={r * 0.55} y1="0" x2={r * 0.75} y2={r * 0.2} />201 <line x1={-r * 0.55} y1="0" x2={-r * 0.75} y2={-r * 0.2} /><line x1={-r * 0.55} y1="0" x2={-r * 0.75} y2={r * 0.2} />202 </g>203 ))}204 </g>205 ))}206 </svg>207 <span className="fd-blu-frozen">Frozen</span>208 </div>209 </div>210 </div>211 <ul className="fd-blu-tx" aria-label="Latest card activity">212 {frozen && (213 <li className="fd-blu-tx-blocked" key="blocked">214 <span className="fd-blu-tx-m">UB</span><span className="fd-blu-tx-n">Uber Trip, Lisbon<small>Just now, blocked</small></span><span className="fd-blu-mono">€23.40</span>215 </li>216 )}217 {TXS.map((t) => (218 <li key={t.n}><span className="fd-blu-tx-m">{t.n.slice(0, 2).toUpperCase()}</span><span className="fd-blu-tx-n">{t.n}<small>{t.w}</small></span><span className="fd-blu-mono">{eur(t.a)}</span></li>219 ))}220 </ul>221 <div className="fd-blu-row">222 <div className="fd-blu-sw-label">223 <span id={id} className="fd-blu-sw-title">{frozen ? "Card frozen" : "Card active"}</span>224 <span className="fd-blu-sw-sub" aria-live="polite">{frozen ? `Nothing can be charged to •••• ${card.last4}. Unfreeze any time.` : "Online, in store and contactless."}</span>225 </div>226 <button type="button" role="switch" aria-checked={frozen} aria-labelledby={id} className="fd-blu-switch" onClick={() => setFrozen((f) => !f)}>227 <span className="fd-blu-knob" />228 </button>229 </div>230 <div className="fd-blu-limit">231 <div className="fd-blu-limit-row"><span>Monthly limit</span><span className="fd-blu-mono">{eur(card.spent)} <s>/ {eur(card.limit, 0)}</s></span></div>232 <div className="fd-blu-bar" role="meter" aria-label="Monthly limit used" aria-valuemin={0} aria-valuemax={card.limit} aria-valuenow={card.spent}><i style={{ width: pct + "%" }} /></div>233 </div>234 </article>235 );236}237 238/* ───────────── spend chart ───────────── */239function SpendTile({ spend, categories, reduced }: { spend: BluWeek[]; categories: BluCategory[]; reduced: boolean }) {240 const ref = useRef<HTMLDivElement>(null);241 const [drawn, setDrawn] = useState(false);242 const [hover, setHover] = useState<number | null>(null);243 const gid = useId().replace(/:/g, "");244 const W = 640, H = 200, P = 8;245 const max = Math.max(...spend.map((s) => s.value)) * 1.12;246 const pts = spend.map((s, i) => [P + (i * (W - P * 2)) / (spend.length - 1), H - (s.value / max) * H] as const);247 const line = useMemo(() => {248 let d = `M${pts[0][0]} ${pts[0][1]}`;249 for (let i = 1; i < pts.length; i++) {250 const [x0, y0] = pts[i - 1], [x1, y1] = pts[i];251 const cx = (x0 + x1) / 2;252 d += ` C${cx} ${y0}, ${cx} ${y1}, ${x1} ${y1}`;253 }254 return d;255 }, [pts]);256 const area = `${line} L${pts[pts.length - 1][0]} ${H} L${pts[0][0]} ${H} Z`;257 const total = spend.reduce((a, s) => a + s.value, 0);258 const last = spend[spend.length - 1].value, prev = spend[spend.length - 2].value;259 const idx = hover ?? spend.length - 1;260 261 useEffect(() => {262 if (reduced) { setDrawn(true); return; }263 const el = ref.current;264 if (!el) return;265 const io = new IntersectionObserver(([e]) => { if (e.isIntersecting) { setDrawn(true); io.disconnect(); } }, { threshold: 0.35 });266 io.observe(el);267 return () => io.disconnect();268 }, [reduced]);269 270 const pick = (clientX: number) => {271 const r = ref.current?.querySelector("svg")?.getBoundingClientRect();272 if (!r) return;273 const x = ((clientX - r.left) / r.width) * W;274 let best = 0;275 pts.forEach((p, i) => { if (Math.abs(p[0] - x) < Math.abs(pts[best][0] - x)) best = i; });276 setHover(best);277 };278 const [hx, hy] = pts[idx];279 280 return (281 <article className="fd-blu-tile fd-blu-t-spend" ref={ref} data-drawn={drawn ? "" : undefined}>282 <div className="fd-blu-spend-top">283 <div>284 <div className="fd-blu-tag"><span>02</span>Spend</div>285 <h3 className="fd-blu-h">Every euro, by the week it left.</h3>286 </div>287 <div className="fd-blu-kpi">288 <span className="fd-blu-kpi-l">12 weeks</span>289 <span className="fd-blu-kpi-v">{eur(total, 0)}</span>290 <span className="fd-blu-kpi-d">{last > prev ? "Up " : "Down "}{fmt(Math.abs(((last - prev) / prev) * 100), 1)}% on last week</span>291 </div>292 </div>293 <div294 className="fd-blu-chart"295 tabIndex={0}296 role="group"297 aria-label="Weekly spend chart. Use left and right arrow keys to read each week."298 onPointerMove={(e) => pick(e.clientX)}299 onPointerLeave={() => setHover(null)}300 onKeyDown={(e) => {301 if (e.key === "ArrowRight" || e.key === "ArrowLeft") {302 e.preventDefault();303 const d = e.key === "ArrowRight" ? 1 : -1;304 setHover((h) => Math.max(0, Math.min(spend.length - 1, (h ?? spend.length - 1) + d)));305 }306 }}307 onBlur={() => setHover(null)}308 >309 <svg viewBox={`0 0 ${W} ${H + 24}`} preserveAspectRatio="none" aria-hidden="true">310 <defs>311 <linearGradient id={`g${gid}`} x1="0" y1="0" x2="0" y2="1">312 <stop offset="0" stopColor="var(--fd-blu-lime)" stopOpacity=".55" />313 <stop offset="1" stopColor="var(--fd-blu-lime)" stopOpacity="0" />314 </linearGradient>315 </defs>316 {[0.25, 0.5, 0.75].map((f) => <line key={f} className="fd-blu-gl" x1="0" x2={W} y1={H * f} y2={H * f} vectorEffect="non-scaling-stroke" />)}317 <path className="fd-blu-area" d={area} fill={`url(#g${gid})`} />318 <path className="fd-blu-line" d={line} pathLength={1} vectorEffect="non-scaling-stroke" />319 <line className="fd-blu-cross" x1={hx} x2={hx} y1="0" y2={H} vectorEffect="non-scaling-stroke" />320 </svg>321 <span className="fd-blu-pt" style={{ left: `${(hx / W) * 100}%`, top: `calc(30px + ${hy / (H + 24)} * (100% - 50px))` }} aria-hidden="true" />322 <span className="fd-blu-tip" style={{ left: `${Math.min(86, Math.max(14, (hx / W) * 100))}%` }}>323 <b>{eur(spend[idx].value, 0)}</b> w/c {spend[idx].label}324 </span>325 <div className="fd-blu-xl" aria-hidden="true">326 {spend.map((s, i) => <span key={s.label} data-on={i === idx ? "" : undefined} data-alt={i % 4 !== 0 && i !== spend.length - 1 ? "" : undefined}>{(i % 2 === 0 && i < spend.length - 2) || i === spend.length - 1 ? s.label : ""}</span>)}327 </div>328 <span className="fd-blu-sr" aria-live="polite">{hover !== null ? `Week of ${spend[idx].label}: ${eur(spend[idx].value, 0)}` : ""}</span>329 </div>330 <div className="fd-blu-cats">331 <div className="fd-blu-stack" aria-hidden="true">332 {categories.map((c, i) => <i key={c.label} style={{ flexGrow: c.share, ["--k" as string]: i }} />)}333 </div>334 <ul>335 {categories.map((c, i) => (336 <li key={c.label}><i style={{ ["--k" as string]: i }} aria-hidden="true" />{c.label}<span className="fd-blu-mono">{c.share}%</span></li>337 ))}338 </ul>339 </div>340 </article>341 );342}343 344/* ───────────── FX converter ───────────── */345function FxTile({ rates, reduced }: { rates: BluRate[]; reduced: boolean }) {346 const [amount, setAmount] = useState("12500");347 const [code, setCode] = useState(rates[0].code);348 const [flip, setFlip] = useState(0);349 const [sent, setSent] = useState(false);350 const r = rates.find((x) => x.code === code) ?? rates[0];351 const n = parseFloat(amount.replace(/[^0-9.]/g, "")) || 0;352 const fee = n > 10000 ? (n - 10000) * 0.0035 : 0;353 const out = useSpringNumber((n - fee) * r.rate, reduced);354 const inId = useId();355 return (356 <article className="fd-blu-tile fd-blu-t-fx">357 <div className="fd-blu-tag"><span>03</span>FX</div>358 <h3 className="fd-blu-h">Mid-market, no spread.</h3>359 <div className="fd-blu-fx">360 <label className="fd-blu-fx-box" htmlFor={inId}>361 <span className="fd-blu-fx-l">You send</span>362 <span className="fd-blu-fx-in">363 <input id={inId} inputMode="decimal" value={amount} onChange={(e) => setAmount(e.target.value.replace(/[^0-9.]/g, "").slice(0, 9))} aria-label="Amount in euros" />364 <span className="fd-blu-ccy">EUR</span>365 </span>366 </label>367 <button type="button" className="fd-blu-swap" aria-label="Refresh rate" onClick={() => setFlip((f) => f + 1)} style={{ transform: `rotate(${flip * 180}deg)` }}>368 <svg width="14" height="14" viewBox="0 0 14 14" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round"><path d="M4 2v10M1.5 9.5 4 12l2.5-2.5M10 12V2M7.5 4.5 10 2l2.5 2.5" /></svg>369 </button>370 <div className="fd-blu-fx-box fd-blu-fx-out">371 <span className="fd-blu-fx-l">They receive</span>372 <span className="fd-blu-fx-in"><output className="fd-blu-fx-v" aria-live="polite">{fmt(out)}</output><span className="fd-blu-ccy">{r.code}</span></span>373 </div>374 </div>375 <div className="fd-blu-ccys" role="radiogroup" aria-label="Currency">376 {rates.map((x) => (377 <button key={x.code} type="button" role="radio" aria-checked={x.code === code} title={x.name} onClick={() => setCode(x.code)}>{x.code}</button>378 ))}379 </div>380 <dl className="fd-blu-fx-meta">381 <div><dt>Rate</dt><dd className="fd-blu-mono">1 EUR = {r.rate.toFixed(4)}</dd></div>382 <div><dt>Fee</dt><dd className="fd-blu-mono">{fee ? eur(fee) : "€0.00"}</dd></div>383 <div><dt>Arrives</dt><dd>{r.arrives}</dd></div>384 </dl>385 <button type="button" className="fd-blu-send" data-sent={sent ? "" : undefined} onClick={() => setSent((v) => !v)}>386 {sent ? <>Scheduled, {r.arrives.toLowerCase()}<span className="fd-blu-undo">Undo</span></> : <>Send {r.code} {fmt((n - fee) * r.rate)}</>}387 </button>388 </article>389 );390}391 392/* ───────────── approval queue ───────────── */393function ApprovalTile({ items, reduced }: { items: BluApproval[]; reduced: boolean }) {394 const [list, setList] = useState(items.map((i) => ({ ...i, state: "open" as "open" | "approved" | "declined" })));395 const [msg, setMsg] = useState("");396 const open = list.filter((l) => l.state === "open");397 const decide = (id: string, state: "approved" | "declined") => {398 const it = list.find((l) => l.id === id)!;399 setList((ls) => ls.map((l) => (l.id === id ? { ...l, state } : l)));400 setMsg(`${state === "approved" ? "Approved" : "Declined"} ${eur(it.amount)} to ${it.payee}.`);401 };402 const paid = list.filter((l) => l.state === "approved").reduce((a, l) => a + l.amount, 0);403 return (404 <article className="fd-blu-tile fd-blu-t-ap">405 <div className="fd-blu-ap-top">406 <div className="fd-blu-tag"><span>04</span>Approvals</div>407 <span className="fd-blu-count" aria-label={`${open.length} waiting`}>{open.length}</span>408 </div>409 <h3 className="fd-blu-h">Invoices wait for the right yes.</h3>410 <ul className="fd-blu-ap">411 {list.map((l) => (412 <li key={l.id} className="fd-blu-ap-li" data-state={l.state} data-instant={reduced ? "" : undefined}>413 <div className="fd-blu-ap-in">414 <div className="fd-blu-ap-row">415 <img src={l.avatar} alt="" width={32} height={32} loading="lazy" />416 <div className="fd-blu-ap-txt">417 <span className="fd-blu-ap-payee">{l.payee}</span>418 <span className="fd-blu-ap-memo">{l.memo}</span>419 <span className="fd-blu-ap-who">{l.requester} asked, {l.when}</span>420 </div>421 <span className="fd-blu-ap-amt fd-blu-mono">{eur(l.amount)}</span>422 </div>423 <div className="fd-blu-ap-btns">424 <button type="button" className="fd-blu-no" disabled={l.state !== "open"} onClick={() => decide(l.id, "declined")} aria-label={`Decline ${eur(l.amount)} to ${l.payee}`}>Decline</button>425 <button type="button" className="fd-blu-yes" disabled={l.state !== "open"} onClick={() => decide(l.id, "approved")} aria-label={`Approve ${eur(l.amount)} to ${l.payee}`}>426 <svg width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M2.5 6.2 5 8.6l4.5-5" /></svg>427 Approve428 </button>429 </div>430 </div>431 </li>432 ))}433 </ul>434 {open.length === 0 && (435 <div className="fd-blu-empty">436 <p><b>Queue clear.</b> {eur(paid)} goes out in the 15:00 batch.</p>437 <button type="button" className="fd-blu-link" onClick={() => { setList(items.map((i) => ({ ...i, state: "open" }))); setMsg("Queue reset."); }}>Reset demo</button>438 </div>439 )}440 <span className="fd-blu-sr" role="status" aria-live="polite">{msg}</span>441 </article>442 );443}444 445const css = `446:where(.fd-blu,.fd-blu *){box-sizing:border-box;margin:0;padding:0;font:inherit;letter-spacing:inherit;color:inherit;text-decoration:none;list-style:none;border:0;background:none}447:where(.fd-blu,.fd-blu *)::before,:where(.fd-blu,.fd-blu *)::after{box-sizing:border-box}448:where(.fd-blu svg,.fd-blu img){display:block;flex-shrink:0}449:where(.fd-blu button,.fd-blu input){appearance:none;-webkit-appearance:none;outline:0;border-radius:0;cursor:pointer}450.fd-blu{451 --fd-ease-out:cubic-bezier(.22,1,.36,1);--fd-ease-in-out:cubic-bezier(.65,0,.35,1);--fd-spring:cubic-bezier(.34,1.56,.64,1);452 --fd-dur-1:120ms;--fd-dur-2:240ms;--fd-dur-3:420ms;--fd-dur-4:700ms;453 --fd-blu-bg:#EEEBE3;--fd-blu-ink:#141412;--fd-blu-tile:#FBFAF6;--fd-blu-mut:#6C6A62;--fd-blu-line:rgba(20,20,18,.1);454 --fd-blu-forest:#0E4D33;--fd-blu-lime:#C6F04D;--fd-blu-red:#B9381C;455 --fd-blu-sans:ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;456 --fd-blu-mono-f:ui-monospace,"SF Mono","JetBrains Mono",Menlo,monospace;457 display:block;width:100%;padding:clamp(56px,8vw,112px) clamp(16px,4vw,56px);background:var(--fd-blu-bg);color:var(--fd-blu-ink);458 font:400 15px/1.45 var(--fd-blu-sans);letter-spacing:-.005em;-webkit-font-smoothing:antialiased}459.fd-blu-mono{font-family:var(--fd-blu-mono-f);font-variant-numeric:tabular-nums;letter-spacing:-.02em}460.fd-blu-sr{position:absolute;width:1px;height:1px;overflow:hidden;clip:rect(0 0 0 0);white-space:nowrap}461.fd-blu-head{max-width:1240px;margin:0 auto 48px;display:grid;grid-template-columns:minmax(0,2fr) minmax(0,1fr);gap:32px;align-items:end}462.fd-blu-eyebrow{display:flex;align-items:center;gap:10px;font:500 13px/16px var(--fd-blu-mono-f);letter-spacing:.02em;color:var(--fd-blu-mut);margin-bottom:20px}463.fd-blu-dot{display:block;width:8px;height:8px;border-radius:2px;background:var(--fd-blu-forest);box-shadow:3px 3px 0 var(--fd-blu-lime)}464.fd-blu-title{font:650 clamp(2.25rem,6vw,5.5rem)/.94 var(--fd-blu-sans);letter-spacing:-.05em;color:var(--fd-blu-ink)}465.fd-blu-title em{font-style:normal;color:var(--fd-blu-forest);background:linear-gradient(transparent 62%,var(--fd-blu-lime) 62%,var(--fd-blu-lime) 90%,transparent 90%)}466.fd-blu-lede{font:400 17px/1.5 var(--fd-blu-sans);letter-spacing:-.01em;color:#3B3A35;max-width:420px;justify-self:end}467.fd-blu-grid{max-width:1240px;margin:0 auto;display:grid;grid-template-columns:repeat(12,minmax(0,1fr));grid-auto-rows:auto;gap:12px}468.fd-blu-tile{position:relative;background:var(--fd-blu-tile);border-radius:20px;padding:24px;box-shadow:0 0 0 1px var(--fd-blu-line);display:flex;flex-direction:column;min-width:0}469.fd-blu-tag{display:flex;align-items:center;gap:8px;font:500 12px/16px var(--fd-blu-mono-f);letter-spacing:.04em;text-transform:uppercase;color:var(--fd-blu-mut)}470.fd-blu-tag span{display:inline-block;padding:2px 5px;border-radius:4px;background:var(--fd-blu-ink);color:var(--fd-blu-tile);font-size:11px;line-height:14px}471.fd-blu-h{margin-top:10px;font:600 20px/1.2 var(--fd-blu-sans);letter-spacing:-.02em;color:var(--fd-blu-ink);max-width:22ch}472.fd-blu-t-card{grid-column:1 / span 5;grid-row:1 / span 2}473.fd-blu-t-spend{grid-column:6 / span 7}474.fd-blu-t-fx{grid-column:6 / span 3}475.fd-blu-t-ap{grid-column:9 / span 4}476 477/* card */478.fd-blu-cardwrap{flex:1;display:grid;place-items:center;padding:28px 0 24px;perspective:900px}479.fd-blu-card{position:relative;width:100%;max-width:420px;aspect-ratio:1.586;border-radius:16px;overflow:hidden;padding:20px 22px;display:flex;flex-direction:column;justify-content:space-between;color:#F1F5E6;480 background:radial-gradient(120% 90% at 85% 0%,#1C6B49 0%,transparent 60%),linear-gradient(155deg,#125A3C,#0A3825 70%);481 box-shadow:0 1px 0 rgba(255,255,255,.14) inset,0 30px 50px -28px rgba(14,77,51,.7),0 8px 18px -10px rgba(0,0,0,.35);482 transform:rotateX(8deg) rotateZ(-3deg);transition:transform var(--fd-dur-3) var(--fd-ease-out),filter var(--fd-dur-3) var(--fd-ease-in-out)}483.fd-blu-t-card:hover .fd-blu-card{transform:rotateX(0) rotateZ(-1deg) translateY(-4px)}484.fd-blu-guil{position:absolute;inset:0;width:100%;height:100%;opacity:.18}485.fd-blu-guil path{fill:none;stroke:var(--fd-blu-lime);stroke-width:.6}486.fd-blu-card-top,.fd-blu-card-bot{position:relative;display:flex;justify-content:space-between;align-items:flex-start;gap:12px}487.fd-blu-card-bot{align-items:flex-end}488.fd-blu-wm{font:700 22px/1 var(--fd-blu-sans);letter-spacing:-.06em}489.fd-blu-wm i{font-style:normal;color:var(--fd-blu-lime)}490.fd-blu-kind{font:500 10px/14px var(--fd-blu-mono-f);letter-spacing:.08em;text-transform:uppercase;opacity:.75}491.fd-blu-chip{position:relative;width:40px;height:30px;border-radius:6px;background:linear-gradient(135deg,#E3E7C9,#B9BE95);display:grid;grid-template-rows:repeat(3,1fr);padding:5px 0;box-shadow:inset 0 0 0 1px rgba(0,0,0,.15)}492.fd-blu-chip i{display:block;border-top:1px solid rgba(0,0,0,.18)}493.fd-blu-holder{display:block;font:600 14px/18px var(--fd-blu-sans);letter-spacing:-.01em}494.fd-blu-co{display:block;font:400 12px/16px var(--fd-blu-sans);opacity:.7}495.fd-blu-num{font:500 14px/18px var(--fd-blu-mono-f);letter-spacing:.06em}496.fd-blu-frost{position:absolute;inset:0;display:grid;place-items:center;background:linear-gradient(160deg,rgba(236,244,247,.82),rgba(205,222,230,.7));opacity:0;transition:opacity var(--fd-dur-3) var(--fd-ease-in-out);pointer-events:none}497.fd-blu-frost svg{position:absolute;inset:0;width:100%;height:100%}498.fd-blu-frost line{stroke:#fff;stroke-width:1.4;stroke-linecap:round}499.fd-blu-frost g[style]{transform-box:fill-box;transform-origin:center;opacity:0;scale:.3;rotate:-40deg;transition:opacity var(--fd-dur-3) var(--fd-ease-out),scale var(--fd-dur-4) var(--fd-spring),rotate var(--fd-dur-4) var(--fd-ease-out);transition-delay:calc(var(--i) * 60ms)}500.fd-blu-frozen{position:relative;font:600 12px/16px var(--fd-blu-mono-f);letter-spacing:.24em;text-transform:uppercase;color:#1C3A48;padding:8px 14px;border-radius:999px;background:rgba(255,255,255,.7);box-shadow:0 0 0 1px rgba(28,58,72,.18)}501.fd-blu-t-card[data-frozen] .fd-blu-card{filter:saturate(.25);transform:rotateX(0) rotateZ(0)}502.fd-blu-t-card[data-frozen] .fd-blu-frost{opacity:1}503.fd-blu-t-card[data-frozen] .fd-blu-frost g[style]{opacity:.9;scale:1;rotate:0deg}504.fd-blu-tx{display:flex;flex-direction:column;margin-bottom:8px}505.fd-blu-tx li{display:grid;grid-template-columns:30px minmax(0,1fr) auto;gap:10px;align-items:center;padding:7px 0;font:500 14px/18px var(--fd-blu-sans)}506.fd-blu-tx-m{display:grid;place-items:center;width:30px;height:30px;border-radius:8px;background:#EFEDE5;font:600 10px/1 var(--fd-blu-mono-f);color:var(--fd-blu-mut)}507.fd-blu-tx-n{display:flex;flex-direction:column;min-width:0}508.fd-blu-tx-n small{font:400 12px/16px var(--fd-blu-sans);color:var(--fd-blu-mut)}509.fd-blu-tx .fd-blu-mono{font-size:14px}510.fd-blu-tx-blocked{animation:fd-blu-in var(--fd-dur-3) var(--fd-ease-out) both}511.fd-blu-tx-blocked .fd-blu-tx-m{background:#DCE8EE;color:#1C3A48}512.fd-blu-tx-blocked small{color:#3E6F86}513.fd-blu-tx-blocked .fd-blu-mono{text-decoration:line-through;color:var(--fd-blu-mut)}514.fd-blu-row{display:flex;align-items:center;justify-content:space-between;gap:16px;padding:16px 0;border-top:1px solid var(--fd-blu-line)}515.fd-blu-sw-label{display:flex;flex-direction:column;gap:2px;min-width:0}516.fd-blu-sw-title{font:600 15px/20px var(--fd-blu-sans);letter-spacing:-.01em}517.fd-blu-sw-sub{font:400 13px/18px var(--fd-blu-sans);color:var(--fd-blu-mut)}518.fd-blu-switch{position:relative;flex:none;width:52px;height:30px;border-radius:999px;background:#D8D5CB;transition:background-color var(--fd-dur-2) var(--fd-ease-in-out)}519.fd-blu-switch[aria-checked="true"]{background:#5D8AA0}520.fd-blu-knob{position:absolute;top:3px;left:3px;width:24px;height:24px;border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(0,0,0,.25);transition:transform var(--fd-dur-2) var(--fd-spring),width var(--fd-dur-1)}521.fd-blu-switch[aria-checked="true"] .fd-blu-knob{transform:translateX(22px)}522.fd-blu-switch:active .fd-blu-knob{width:28px}523.fd-blu-switch[aria-checked="true"]:active .fd-blu-knob{transform:translateX(18px)}524.fd-blu-limit{padding-top:14px;border-top:1px solid var(--fd-blu-line)}525.fd-blu-limit-row{display:flex;justify-content:space-between;font:400 13px/18px var(--fd-blu-sans);color:var(--fd-blu-mut);margin-bottom:10px}526.fd-blu-limit-row .fd-blu-mono{color:var(--fd-blu-ink)}527.fd-blu-limit-row s{text-decoration:none;color:var(--fd-blu-mut)}528.fd-blu-bar{height:6px;border-radius:3px;background:#E4E1D7;overflow:hidden}529.fd-blu-bar i{display:block;height:100%;border-radius:3px;background:var(--fd-blu-forest)}530 531/* spend */532.fd-blu-spend-top{display:flex;justify-content:space-between;gap:16px;align-items:flex-start}533.fd-blu-kpi{display:flex;flex-direction:column;align-items:flex-end;text-align:right}534.fd-blu-kpi-l{font:500 12px/16px var(--fd-blu-mono-f);color:var(--fd-blu-mut);text-transform:uppercase;letter-spacing:.04em}535.fd-blu-kpi-v{font:600 32px/1.1 var(--fd-blu-sans);letter-spacing:-.04em;font-variant-numeric:tabular-nums}536.fd-blu-kpi-d{font:500 12px/16px var(--fd-blu-mono-f);color:var(--fd-blu-mut)}537.fd-blu-chart{position:relative;margin-top:12px;height:236px;padding-top:30px;border-radius:8px;outline:0;cursor:crosshair;touch-action:pan-y}538.fd-blu-chart:focus-visible{box-shadow:0 0 0 2px var(--fd-blu-bg),0 0 0 4px rgba(14,77,51,.7)}539.fd-blu-chart svg{width:100%;height:calc(100% - 50px);overflow:visible}540.fd-blu-gl{stroke:var(--fd-blu-line);stroke-width:1;stroke-dasharray:2 4}541.fd-blu-line{fill:none;stroke:var(--fd-blu-forest);stroke-width:2.25;stroke-linecap:round;stroke-dasharray:1;stroke-dashoffset:1;transition:stroke-dashoffset 1600ms var(--fd-ease-out)}542.fd-blu-area{opacity:0;transition:opacity var(--fd-dur-4) var(--fd-ease-out) 900ms}543.fd-blu-t-spend[data-drawn] .fd-blu-line{stroke-dashoffset:0}544.fd-blu-t-spend[data-drawn] .fd-blu-area{opacity:1}545.fd-blu-cross{stroke:var(--fd-blu-ink);stroke-width:1;stroke-dasharray:3 3;opacity:.35}546.fd-blu-pt{position:absolute;width:12px;height:12px;margin:-6px 0 0 -6px;border-radius:50%;background:var(--fd-blu-tile);box-shadow:0 0 0 2.5px var(--fd-blu-forest);transition:left var(--fd-dur-2) var(--fd-ease-out),top var(--fd-dur-2) var(--fd-ease-out);pointer-events:none}547.fd-blu-tip{position:absolute;top:0;transform:translateX(-50%);padding:5px 9px;border-radius:7px;background:var(--fd-blu-ink);color:var(--fd-blu-tile);font:400 12px/16px var(--fd-blu-sans);white-space:nowrap;transition:left var(--fd-dur-2) var(--fd-ease-out);pointer-events:none}548.fd-blu-tip b{font-weight:600;font-family:var(--fd-blu-mono-f);letter-spacing:-.02em;margin-right:4px;color:var(--fd-blu-lime)}549.fd-blu-xl{position:absolute;left:0;right:0;bottom:0;display:flex;justify-content:space-between;font:400 11px/14px var(--fd-blu-mono-f);color:var(--fd-blu-mut)}550.fd-blu-xl span{width:0;display:flex;justify-content:center;white-space:nowrap}551.fd-blu-xl span[data-on]{color:var(--fd-blu-ink)}552.fd-blu-cats{margin-top:18px;padding-top:16px;border-top:1px solid var(--fd-blu-line)}553.fd-blu-stack{display:flex;gap:3px;height:10px}554.fd-blu-stack i,.fd-blu-cats li i{display:block;border-radius:2px;background:color-mix(in srgb,var(--fd-blu-forest) calc(100% - var(--k) * 26%),var(--fd-blu-lime))}555.fd-blu-cats ul{display:flex;flex-wrap:wrap;gap:6px 20px;margin-top:12px}556.fd-blu-cats li{display:flex;align-items:center;gap:7px;font:400 13px/18px var(--fd-blu-sans);color:#3B3A35}557.fd-blu-cats li i{width:8px;height:8px}558.fd-blu-cats li .fd-blu-mono{color:var(--fd-blu-mut);font-size:12px}559 560/* fx */561.fd-blu-fx{position:relative;margin-top:18px;display:flex;flex-direction:column;gap:4px}562.fd-blu-fx-box{display:flex;flex-direction:column;gap:2px;padding:10px 12px;border-radius:12px;background:#F1EFE8;box-shadow:inset 0 0 0 1px var(--fd-blu-line);cursor:text;transition:box-shadow var(--fd-dur-1)}563.fd-blu-fx-box:focus-within{box-shadow:inset 0 0 0 1.5px var(--fd-blu-forest)}564.fd-blu-fx-out{background:var(--fd-blu-forest);color:#F1F5E6;box-shadow:none;cursor:default}565.fd-blu-fx-l{font:500 11px/14px var(--fd-blu-mono-f);letter-spacing:.04em;text-transform:uppercase;opacity:.65}566.fd-blu-fx-in{display:flex;align-items:baseline;justify-content:space-between;gap:8px}567.fd-blu-fx-in input,.fd-blu-fx-v{width:100%;min-width:0;font:600 24px/30px var(--fd-blu-mono-f);letter-spacing:-.04em;color:inherit;font-variant-numeric:tabular-nums;cursor:text}568.fd-blu-fx-v{display:block;cursor:default;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}569.fd-blu-ccy{font:600 12px/16px var(--fd-blu-mono-f);letter-spacing:.04em;opacity:.7}570.fd-blu-fx-out .fd-blu-ccy{color:var(--fd-blu-lime);opacity:1}571.fd-blu-swap{position:absolute;left:50%;top:50%;margin:-15px 0 0 -15px;width:30px;height:30px;border-radius:50%;display:grid;place-items:center;background:var(--fd-blu-tile);color:var(--fd-blu-ink);box-shadow:0 0 0 1px var(--fd-blu-line),0 2px 6px rgba(0,0,0,.08);transition:transform var(--fd-dur-3) var(--fd-spring);z-index:1}572.fd-blu-swap:focus-visible,.fd-blu-ccys button:focus-visible,.fd-blu-ap-btns button:focus-visible,.fd-blu-switch:focus-visible,.fd-blu-link:focus-visible{outline:2px solid rgba(14,77,51,.7);outline-offset:2px}573.fd-blu-ccys{display:flex;gap:4px;margin-top:12px;padding:3px;border-radius:999px;background:#F1EFE8}574.fd-blu-ccys button{flex:1;height:28px;border-radius:999px;font:600 12px/1 var(--fd-blu-mono-f);color:var(--fd-blu-mut);transition:background-color var(--fd-dur-2) var(--fd-ease-in-out),color var(--fd-dur-1)}575.fd-blu-ccys button:hover{color:var(--fd-blu-ink)}576.fd-blu-ccys button[aria-checked="true"]{background:var(--fd-blu-tile);color:var(--fd-blu-ink);box-shadow:0 1px 2px rgba(0,0,0,.1),0 0 0 1px var(--fd-blu-line)}577.fd-blu-fx-meta{margin-top:16px;display:grid;gap:6px}578.fd-blu-fx-meta div{display:flex;justify-content:space-between;gap:8px;font:400 13px/18px var(--fd-blu-sans)}579.fd-blu-fx-meta dt{color:var(--fd-blu-mut)}580.fd-blu-fx-meta dd{text-align:right}581 582.fd-blu-send{margin-top:auto;display:flex;align-items:center;justify-content:center;gap:10px;height:42px;border-radius:999px;background:var(--fd-blu-ink);color:var(--fd-blu-tile);font:600 14px/1 var(--fd-blu-sans);letter-spacing:-.01em;transition:transform var(--fd-dur-2) var(--fd-spring),background-color var(--fd-dur-2) var(--fd-ease-in-out)}583.fd-blu-fx-meta{margin-bottom:18px}584.fd-blu-send:active{transform:scale(.96);transition-duration:var(--fd-dur-1)}585.fd-blu-send[data-sent]{background:var(--fd-blu-lime);color:var(--fd-blu-ink)}586.fd-blu-send:focus-visible{outline:2px solid rgba(14,77,51,.7);outline-offset:2px}587.fd-blu-undo{font:500 12px/1 var(--fd-blu-mono-f);text-decoration:underline;text-underline-offset:3px;opacity:.7}588/* approvals */589.fd-blu-ap-top{display:flex;justify-content:space-between;align-items:center}590.fd-blu-count{min-width:26px;height:26px;padding:0 8px;border-radius:999px;display:grid;place-items:center;background:var(--fd-blu-lime);color:var(--fd-blu-ink);font:700 13px/1 var(--fd-blu-mono-f)}591.fd-blu-ap{margin-top:14px;display:flex;flex-direction:column}592.fd-blu-ap-li{display:grid;grid-template-rows:1fr;transition:grid-template-rows var(--fd-dur-3) var(--fd-ease-in-out) 380ms,opacity var(--fd-dur-2) 380ms}593.fd-blu-ap-in{overflow:hidden;min-height:0;border-top:1px solid var(--fd-blu-line);padding:12px 0;transition:padding var(--fd-dur-3) var(--fd-ease-in-out) 380ms,border-color var(--fd-dur-2) 380ms}594.fd-blu-ap-li:first-child .fd-blu-ap-in{border-top:0;padding-top:0}595.fd-blu-ap-li:not([data-state="open"]){grid-template-rows:0fr;opacity:0}596.fd-blu-ap-li:not([data-state="open"]) .fd-blu-ap-in{padding-top:0;padding-bottom:0;border-color:transparent}597.fd-blu-ap-li[data-instant]{transition:none}.fd-blu-ap-li[data-instant] .fd-blu-ap-in{transition:none}598.fd-blu-ap-row{display:grid;grid-template-columns:32px minmax(0,1fr) auto;gap:10px;align-items:start}599.fd-blu-ap-row img{width:32px;height:32px;border-radius:50%;object-fit:cover;background:#ddd}600.fd-blu-ap-txt{display:flex;flex-direction:column;min-width:0}601.fd-blu-ap-payee{font:600 14px/18px var(--fd-blu-sans);letter-spacing:-.01em}602.fd-blu-ap-memo{font:400 13px/17px var(--fd-blu-sans);color:#3B3A35;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}603.fd-blu-ap-who{font:400 12px/16px var(--fd-blu-sans);color:var(--fd-blu-mut)}604.fd-blu-ap-amt{font-size:14px;line-height:18px;font-weight:600}605.fd-blu-ap-btns{display:flex;justify-content:flex-end;gap:6px;margin-top:8px}606.fd-blu-ap-btns button{display:inline-flex;align-items:center;gap:6px;height:30px;padding:0 12px;border-radius:999px;font:600 13px/1 var(--fd-blu-sans);transition:transform var(--fd-dur-1),background-color var(--fd-dur-2) var(--fd-ease-in-out),color var(--fd-dur-2)}607.fd-blu-ap-btns button:active{transform:scale(.96);transition:transform var(--fd-dur-1)}608.fd-blu-no{color:var(--fd-blu-mut);box-shadow:inset 0 0 0 1px var(--fd-blu-line)}609.fd-blu-no:hover{color:var(--fd-blu-red)}610.fd-blu-yes{background:var(--fd-blu-ink);color:var(--fd-blu-tile)}611.fd-blu-yes:hover{background:var(--fd-blu-forest)}612.fd-blu-ap-li[data-state="approved"] .fd-blu-yes{background:var(--fd-blu-lime);color:var(--fd-blu-ink);transform:scale(1.06);transition:transform var(--fd-dur-2) var(--fd-spring),background-color var(--fd-dur-1)}613.fd-blu-ap-li[data-state="declined"] .fd-blu-no{color:var(--fd-blu-red);box-shadow:inset 0 0 0 1px var(--fd-blu-red)}614.fd-blu-ap-btns button:disabled{cursor:default}615.fd-blu-empty{margin-top:4px;padding:16px;border-radius:12px;background:#F1EFE8;display:flex;flex-direction:column;gap:8px;align-items:flex-start;animation:fd-blu-in var(--fd-dur-3) var(--fd-ease-out) both}616.fd-blu-empty p{font:400 14px/20px var(--fd-blu-sans)}617.fd-blu-empty b{font-weight:600}618.fd-blu-link{font:600 13px/18px var(--fd-blu-sans);color:var(--fd-blu-forest);text-decoration:underline;text-underline-offset:3px;border-radius:4px}619@keyframes fd-blu-in{from{opacity:0;transform:translateY(6px)}to{opacity:1;transform:none}}620 621@media (max-width:1080px){622 .fd-blu-t-card{grid-column:1 / span 6;grid-row:auto}623 .fd-blu-t-spend{grid-column:1 / -1;order:-1}624 .fd-blu-t-fx{grid-column:7 / span 6}625 .fd-blu-t-ap{grid-column:1 / -1}626}627@media (max-width:720px){628 .fd-blu-head{grid-template-columns:1fr;gap:20px}629 .fd-blu-lede{justify-self:start;font-size:16px}630 .fd-blu-grid{grid-template-columns:1fr}631 .fd-blu-tile{grid-column:1 / -1!important;padding:20px;border-radius:18px}632 .fd-blu-t-spend{order:0}633 .fd-blu-spend-top{flex-direction:column}634 .fd-blu-kpi{align-items:flex-start;text-align:left}635 .fd-blu-chart{height:210px}636 .fd-blu-xl span[data-alt]{visibility:hidden}637 .fd-blu-cardwrap{padding:24px 4px}638}639@media (prefers-reduced-motion:reduce){640 .fd-blu *{transition-duration:1ms!important;transition-delay:0ms!important;animation-duration:1ms!important}641}642@media (prefers-contrast:more){.fd-blu-tile{box-shadow:0 0 0 1px currentColor}}643`;644 645export function Demo() {646 return (647 <div className="fd-blud">648 <style>{`.fd-blud{background:#EEEBE3;min-height:100vh}.fd-blud-nav{box-sizing:border-box;max-width:1352px;margin:0 auto;padding:18px clamp(16px,4vw,56px);display:flex;justify-content:space-between;align-items:center;border-bottom:1px solid rgba(20,20,18,.1);font:500 13px/18px ui-sans-serif,-apple-system,BlinkMacSystemFont,"SF Pro Text","Inter","Segoe UI",Roboto,sans-serif;color:#6C6A62}.fd-blud-nav b{font:700 19px/1 ui-sans-serif,-apple-system,BlinkMacSystemFont,"Inter",sans-serif;letter-spacing:-.06em;color:#141412}.fd-blud-nav b i{font-style:normal;color:#0E4D33}`}</style>649 <div className="fd-blud-nav"><b>tallyard<i>.</i></b><span>Business · Pricing · Log in</span></div>650 <BentoLiveUi />651 </div>652 );653}More Feature Sections. Same file contract.
Questions about Live UI Bento
Run npx shadcn@latest add https://freedesignmd.com/r/bento-live-ui.json, or open the page and copy the file into your project. It has zero dependencies besides React. What you copy is the exact file that renders the live demo.
Bento grid features section for React where every tile works: freeze a card, convert FX, clear approvals, scrub a chart. It is a free, MIT-licensed React feature sections component from the freedesignmd library.
Yes. It is free and MIT-licensed, use, modify and ship it in personal and commercial projects without attribution.
All evergreen browsers. Row collapse uses grid-template-rows interpolation (Chrome 107+, Safari 16+, Firefox 66+).
Yes. It is a plain React component with its own styles, so it runs in Next.js, Vite, Remix and Lovable projects, and it needs no Tailwind setup. It has zero dependencies besides React.


