Seven sentences the system answers to
Every component, color choice and typographic decision has to defend itself against these. They sit before Foundations on purpose: values come from principles, not the other way around.
-
01 ·
The work is the argument.
The site exists to make case studies legible to leadership-tier readers. Every visual decision either serves that argument or gets cut.
-
02 ·
Restraint reads as confidence.
Text-forward, generous with space, restrained in color. Looks like an essay portfolio more than a marketing site. The work does the talking.
-
03 ·
Coherence at the structural level; freedom at the content level.
Same typography, same component patterns, same voice across every page. Different stories, different rhythms, different visual climaxes per project.
-
04 ·
Captions do narrative work.
Every visual carries text that interprets it strategically. No image is just an image; no caption is just a description.
-
05 ·
Honesty over polish.
When something is TBD, it reads as TBD. Visible italic placeholder, not absence. When a side project is a side project, it admits it. When work was collaborative, "we" stays in the copy.
-
06 ·
One easing curve. One accent. One reading column.
If two things compete for attention, the design has lost the argument. Pick one. (See:
cubic-bezier(.2, .7, .2, 1),--purple,.cs-main.) -
07 ·
The page itself is the proof of the system.
This document doesn't describe a system in the abstract. It uses the system to describe itself. If a principle here can't be defended in the page rendering it, it's not a principle yet.
The decisions everything inherits
Color, type, spacing, motion. Set once, reused everywhere. Every component below resolves its values back to these tokens.
Color
CSS variables · :rootThree families. Ink sets the page (off-black, very low chroma). Paper is the type and rules. Purple is the only accent. Used for emphasis, hover state, the dot in the eyebrow, and the wash in the hero visual. Click any swatch to copy. Solid colors copy as 6-digit hex. Alpha tokens have three click targets. Click the chip to copy the full 8-digit alpha-hex (e.g. #F3F3F31A), or click the base hex / percentage individually to copy just that fragment.
[data-theme="light"] is set):
--ink #FAFAFA ·
--paper #1C1C1C ·
--muted #5D5D5D ·
--purple #7A3CE0 ·
--purple-2 #5A25C8.
All combos pass WCAG AA for body text against ink (paper/ink: 17.4:1 AAA, muted/ink: 6.6:1 AA, purple/ink: 5.0:1 AA, purple-2/ink: 7.4:1 AAA). Alpha tokens (--rule, --rule-2, --purple-soft) auto-flip via color-mix() so they invert with the theme.
Typography
--display · --body · --monoSyne for display (headlines, hero, beat headings). Inter for body (paragraphs, UI). JetBrains Mono for the eyebrow, meta keys, captions, and any "system" register.
Iconography ★ new
.icon · .icon-sm · .icon-md · .icon-lg · .dg-iconThin-stroke, monoline icons drawn on a 24×24 viewBox. 1.5px stroke, rounded caps and joins, no fill, currentColor stroke so an icon inherits the surrounding text color. Sized via the four utility classes; in diagram nodes, .dg-icon renders at 22px in --purple-2. Live in: diagram component (Paola, SiS Manager).
.icon-sm 12px · .icon 16px · .icon-md 22px · .icon-lg 32px. To add a new icon: draw on a 24×24 viewBox at 1.5 stroke with rounded caps, paste the markup inline. Stroke uses currentColor so the icon picks up the parent's color.Favicon ★ new
assets/favicon/ · favicon-32/16.png · favicon-blink-32.png · apple-touch-icon.pngThe tab mark: a white D on black, supplied as source artwork and rendered down to every size the browser asks for — 32/16px favicons and a 180px apple-touch-icon, linked from every page head as the unsized default plus explicit sized fallbacks.
The blink. A module in theme.js mirrors the hero typewriter cursor in the tab strip: every 4–6 seconds the unsized icon href swaps to favicon-blink-32.png (the same mark with the D dimmed to ~22% brightness) for 260ms, then back. It is a garnish, never a dependency: Safari ignores runtime favicon swaps and keeps the static mark, prefers-reduced-motion disables the module entirely, and the timer stops while the tab is hidden.
Spacing & layout
.wrap · .wrap-narrow · .cs-mainThree measure widths: a visual width (1320px) for inserts, a narrow width (1180px) for hero rows, and a reading width (760px) for body copy. Inserts break out of the reading column to the visual width.
Motion v2 · tokenized
--ease · --ease-soft · --t-* · --rise · --nudgeThe whole motion system is a token scale in :root. Restrained and precise: motion clarifies state, it never performs. Two curves only. --ease cubic-bezier(.2,.7,.2,1) for anything that moves in space (brisk entry, long settle), and --ease-soft for pure fades and color, where directional weight would read as fussy. Only compositor-friendly properties animate (transform, translate, opacity, color); layout properties like gap, width, and height are never transitioned. Hover "come closer" gestures move the arrow by --nudge instead of animating the flex gap.
Sequencing. Reveals cascade: grouped children (result cells, grid tiles, bento cells, timeline steps, work cards) each get an --i index and delay one --t-stagger step apiece (capped at 6), so a section resolves as a sequence rather than a slab. The case-study hero plays a one-time entrance in reading order: eyebrow → headline → meta rows → visual. Page transitions: cross-document View Transitions where supported, with a fade fallback (html.vt-fallback) elsewhere; a pre-paint theme bootstrap in each page's head prevents the dark flash for light-mode visitors. Reduced motion zeroes every token at the source, so all movement collapses to instant without per-component guards.
Signature entrance · hero typewriter. The homepage headline types itself in behind a blinking purple dot; when the last character lands, the dot settles as the sentence's final period and keeps blinking, the site's cursor. Character cadence ~48–90ms, a ~180–270ms breath after each space (people pause between words, not letters), and a 240ms beat at the line break; a hidden ghost copy of the finished title reserves the final box so nothing below shifts while the text grows. On scroll, the hero content fades on a parallax: it drifts down at a quarter of scroll speed and is fully gone by ~70% of the hero's height, so the text sinks into the scene instead of sliding off with the page (rAF-throttled, no easing: the scroll position is the clock). The visible layers are aria-hidden; screen readers get the full sentence immediately. Language toggles settle instantly, no retype. Reduced motion renders the finished title with a static dot and no parallax.
The page chrome
The frame that carries the case studies. Nav, hero, beats, the moments that close. Each one in production today.
Language toggle
.lang-switch · data-lang · case-studies/i18n.jsPill toggle for EN / PT. Auto-injected on every case study by i18n.js; built-in on the homepage. Persists choice via localStorage['davi.lang'], so the language follows the visitor across pages. Translatable strings carry data-i18n (text) or data-i18n-html (allows inline tags).
Two dictionary layers. Strings that repeat sitewide (nav, meta labels, the next-work carousel cards, gate controls) live once in i18n.js → COMMON. Everything else, the actual case-study prose, lives in a per-page window.CS_I18N block sitting just above the i18n.js tag, with an EN and a PT object keyed <page>.NNN in document order. Page keys override COMMON, so a page can special-case a shared label without touching the shared file.
Conventions. Values carry their own inline HTML (<b>, <em>, .accent spans) because they're applied as innerHTML. A key always goes on the outermost copy-bearing element, never on a nested child, or the parent's replacement would discard it. Portuguese is translated for intent rather than literally, and keeps English industry terms (design system, roadmap, product designer, knowledge base) the way they're used in Brazilian practice. Chapter titles are fixed pairs: Reason / Motivo, Strategy / Estratégia, Process / Processo, Results / Resultados.
Known gap: image alt text and <title> stay English, since the engine swaps element content and not attributes. This page is English-only by choice.
Theme picker ★ new
.theme-switch · data-theme · assets/theme.jsTwo small swatches. Dark and light. Borrowed from the color-picker pattern in design tools. The selected swatch carries a purple ring; the other sits quiet. Auto-injected by theme.js as a sibling of the language toggle (so it joins the same nav group and the layout never re-flows). Persists choice via localStorage['davi.theme']. The next visit loads in the chosen mode. Default is dark; light flips ink/paper, deepens the purple accent for legibility on cream, and inverts the animated logo gif. The choice is written as a data-theme attribute on <html>; every component picks up the new tokens automatically.
Eyebrow
.cs-eyebrowMono, uppercase, tracked. Glowing purple dot anchors it. Sits above every H1 and section heading where a label is helpful.
Hero
.cs-hero · .cs-hero-split · .cs-h1The single hero of every case study. Eyebrow → display H1 → 2-up split: meta strip + headline on the left, hero image on the right. The image is the only hero (no full-bleed image below). Default visual aspect: 4:5 portrait. Works for character avatars (El Brian, Paola) and pack/product shots (Bradesco) alike.
Built before anyone called it agentic.
Meta strip
.cs-meta-strip2-column grid of mono labels and values, separated by a thin rule. Sits inside the hero left column, bottom-aligned with the visual.
Chapter divider
.cs-chapter · .is-firstSlim narrative pivot used to mark the four chapters of every case study: The Reason → The Strategy → The Process → The Results. Each chapter title is just a thin top rule plus a single display headline. That's the whole component. Use .is-first on the opening chapter to drop the rule (it sits right under the hero). Every The Reason chapter is followed immediately by a 21:9 key-visual frame before the body copy starts.
The Reason
Beat
.cs-beat · .cs-beat-labelA text section with an optional mono label, display H2, and 1–3 paragraphs. Lives inside the 760px reading column; inserts break out around it. Beat labels are title-only. No leading number, so the chapter dividers carry the structural counting and beats stay readable as ideas, not items.
How does a 75-year-old retail bank get a savings-averse population to actually save?
The product itself was a small, ingenious mechanic: every debit transaction rounds up; the difference plus a fixed value goes into your savings on the next business day. Savings without thinking about it.
Our job at One Digital was to launch it.
Outcome line
.cs-outcomeItalic display, muted color. Sits below a thin rule at the close of the body. Where the case study makes its quiet, last argument.
Pair (text + visual)
.cs-pair · .reverse · .text-heavyAsymmetric 2-col layout that breaks the rhythm of stacked centered inserts. Default: text 1fr / visual 1.45fr. .reverse swaps order. .text-heavy gives text the larger column.
Reorganize the product around the customer's journey.
The deepest fix wasn't visual. It was structural. Most of the seven pain points came from the same root cause.
So we rebuilt the structure around journeys. Features show up at the moment they're useful, not under abstract menu trees.
Next work v4 · carousel
.cs-next · .cs-carousel · .cs-carousel-track · .cs-carousel-arrowCloses every case study with a horizontal carousel of the other projects, so the visitor picks what to see next instead of being handed a single card. Each slide is the same card the home work grid uses (4:3 thumb well, hover bar, view tag, then the three-tier title / client / category stack), sized to 300px and left-snapped. A More work eyebrow (nav.more) sits above; a Back to all work → link below. The track scroll-snaps with a styled slim scrollbar, and .cs-carousel-arrow prev / next buttons (wired by theme.js) fade out at each end. Cards keep the .has-img and .locked variants. On touch / narrow screens the arrows hide and cards widen to ~78vw for native swipe. Each page lists every project except the one you're on.
Next work locked card
.next-card.lockedThe gated card variant, shown here on its own but used as one slide inside the carousel when a gated case is in the build (Olivia, Yuma, both held back from V1). Purple wash on the thumb, a CSS padlock after the title, and an On request view tag.
Gate v2 · error state
.gate · .gate-form · .gate-errPassword gate for gated teasers (Olivia, Yuma, both held back from V1; the component stays in the system). Submitting unlocks the full case study (state held in body.unlocked).
Error state · a wrong password answers with three signals at once: the message fades and rises in (.gate-err.is-on · opacity + translate on --t-base), the input border flips to --error, and the form shakes once, ±--nudge over --t-base, then settles (.gate-form.is-error). The shake restarts on every failed retry (class removed, reflow forced, re-added). Typing withdraws the error. All travel and duration resolve through the motion tokens, so reduced-motion visitors get instant text + colour with no shake. The message region carries role="alert" so screen readers announce it.
Try it below: any password fails in this demo.
Read the full case study
The full case study is gated. Reach out for the password.
Foot
.footSmall mono row at the bottom of every page. Year + a single contact link.
Error page ★ new
/404.html · .e404 · .e404-num · .e404-title · .e404-btn · .e404-rowThe one page a visitor never chose to load, so it is built to cost almost nothing and put the exits within reach: no hero canvas, no image thumbs, no dependency on the reveal engine. A ghosted outline numeral, a headline, one line of explanation, a filled primary button beside a ghost secondary, then every case as a hairline text row. Most traffic here arrives from stale links to the old Webflow portfolio, which is why the copy names that possibility instead of blaming the visitor.
Three rules that are easy to get wrong. 1. Every asset path is root-relative (/assets/…). Apache serves this one file for a miss at any depth while leaving the address bar on the requested path, so a relative href would resolve against /case-studies/old/ and load nothing. 2. It must answer a real 404, never 200. A soft 404 tells crawlers a dead URL is fine and keeps it indexed forever; the .htaccess carries REDIRECT_STATUS guards so the clean-URL rule cannot rewrite the error page into a valid destination. 3. It carries noindex, follow, so it is dropped from the index but still passes crawlers on to the linked cases.
404
This page isn't here.
The link may be broken, or it may point at an older version of this portfolio that has since been rebuilt. Nothing is lost, it just lives at a new address.
window.CS_I18N and the shared i18n.js engine. The client names are declared locally rather than pulled from the shared dictionary, because that dictionary does not carry them, they live in the homepage's own i18n object. Without local copies the PT view would silently fall back to English.Insert blocks
The visual moments inside a case study. Each one breaks out of the 760px reading column to fill the visual width. Drop one between two beats and the page picks up rhythm.
Bento grid ★ new · v2
.frame-bento · .bento-cell · .bento-{NxM} · .is-purple · .is-paper · .is-photoEditorial composition mixing stats, quotes, photos, and copy in variable-size cells (12-col grid). Solves the "stack of centered components" rhythm problem by clustering different content types into one composed surface. Cell padding is 28×32, grid gap is 14px, row height is 104px. Subcopy under stats uses body type (Inter, 14px), not mono.
.is-purple (accent wash), .is-paper (light flip), .is-photo (hero image cell). Sizes: .bento-{cols}x{rows} on a 12-col / 96px-row grid (e.g. bento-4x3, bento-8x2).Spec corners ★ new · v2
.spec-cornersEditorial decoration. Small purple corner brackets on a cell. Borrowed from technical/poster compositions. Add to any element you want to mark as "the focal" in a busy bento grid.
Signature moment ★ new
.cs-signature · .sig-{lime, magenta, orange, electric, purple}One per case study, never more. The page's climax insert wears the project's own vivid accent. A faint tinted band, accent rules top and bottom, accent label, and corner brackets. Everything else on the page stays quiet; this is where the boldness is spent. Current assignments: Paola = lime (kiosk flow) · SiS = electric (before/after) · El Brian = purple (live agent) · Yuma = orange (brand book) · Bradesco = magenta (TV film) · Olivia = electric (the 300+ wall).
Frame
.frame-img · .ratio-{21x9, 2x1, 16x9, 4x3, 3x4, 9x16, 1x1}Single image insert. Choose the ratio that matches the asset. Optional .tinted wraps the placeholder in a purple wash for hero moments; .crop-top anchors tall screenshots to their header. Sizes below target 2× retina, sRGB. Full-bleed frames render up to 1208px wide, so 2400px on the long edge covers 2× (21:9 rounds to 2520 to keep whole pixels). Grid cells and phone captures can go smaller. Export JPG for photos, PNG for UI.
Grid
.frame-grid · .cols-{2, 3, 4}Multi-image arrangement. 2-up for paired comparisons, 3-up for the Paola triptych and Immersive contact sheet, 4-up for the Bradesco digital quadrants and Olivia avatar gallery (in 4×2 form).
Mosaic ★ new
.frame-mosaic · .mo-wide · .mo-video · .mo-tall · .mo-sqA six-column bin for showing a campaign at scale, where the point is volume and range rather than any single asset. Use it when a project produced many pieces across formats and a uniform grid would flatten them into sameness. Reach for .frame-grid instead when the images are peers and deserve equal weight. Four tile sizes span the columns: wide (6, 16:9) opens with the hero asset, video (4, 16:9) carries a looping clip, tall (2 columns × 2 rows) holds a portrait screen, and sq (2, 6:5) fills the remainder. Mixed media is expected: img and video both cover their tile. Below 760px it collapses to two columns and the tall tile gives up its row span, so nothing ends up a sliver on a phone. Live in: Poupa Troco (the campaign at scale: Facebook Canvas, hotsite loop, mobile site, two banners, three piggies).
object-position: top) because a portrait screenshot's header and headline are the part worth keeping.Numbers
.frame-numbers · .solo · .threeOversized stat cells, no frame. Just the numbers and their descriptions sitting in a clean grid. Solo for a single hero number (Olivia's "300+"), 2-up for outcome pairs, 3-up for stat triplets. Description copy uses body type (Inter, 15px), not mono.
Timeline
.frame-timeline · .row · .tl-stepGrid (default): a short step sequence, 4 on desktop, 2 on tablet, 1 on mobile, on a dashed baseline. Best for up to 4 steps. Single-row (.row): keeps every step in one left-to-right line connected by a continuous dashed line, with smaller (still readable) text, for longer sequences (5+ steps). On narrow screens it scrolls horizontally rather than stacking, so the timeline never loses its direction, using the shared scrollbar style (the slim rounded thumb from the code block, also on the next-work carousel: one recipe for every scrollable surface). Both use the same .tl-step (year, title, one-line note).
Brief, sketch, build
Tommy Hilfiger virtual event. The runway as a place we built.
VR training
Itaú: each value a foothold, each progression an earned beat.
Digital twin
Capgemini AIE São Paulo lab. Better than the real space at being itself.
Live AR layer
Itaú Personnalité. A layer on the room, not over it.
The MVP
Conversational sommelier on the kiosk.
+ Live stock
Matches what's on the shelf.
+ Pairings
Recipes, dishes, sides.
+ Identity
Carries across kiosk and app.
+ Promos
Personalised offers inline.
+ Cart
Closes with a transaction.
Diagram
.frame-diagram · .dg-grid · .dg-nodeA designer-style architecture diagram. Labeled nodes on a grid: 3-up by default, or add .cols-4 for a four-step pipeline read left to right. The variant collapses 2×2 on tablet so a step never wraps alone under a row of three, then single-column on mobile like the base grid. All nodes share the same purple-bordered style. No highlighted variant. Each node leads with a thin-stroke icon (see the Iconography section). A small foot row reads the system in plain language. Used in Paola, Olivia, and SiS Manager.
Knowledge base
Project-specific data and docs. Vector-indexed, retrievable.
Agent
The orchestrator. Routes, retrieves, responds, remembers.
Persona
Tone, character, refusal rules. Plain-text, editable.
Before / after
.frame-split · .split-sideSide-by-side comparison with mono "before / after" labels. Used four times in TBL. Login, home, reports, indicator management.
Audio module
.frame-audio · .audio-playInline audio block. Purple play button, title, mono sub-line. Originally built for the Bradesco Poupa Troco soundtrack.
Video embed
.frame-video · .frame-video-pair16:9 frame for YouTube + Vimeo iframes. Pairs side-by-side via .frame-video-pair. Used in Bradesco (TVC) and Paola (journey video).
Video loop
.frame-video-loop · .vl-hintControls-free video tile for short loops (Spotify Canvases, motion snippets). Defaults to 9:16; drop into a .frame-grid.cols-3 for a wall. Click a tile to play, click again to pause. A .vl-hint overlay shows "click to play" while paused and fades once playing. A scrim carries the data-label; tiles pause when scrolled out of view. Used in Beiramaquina's Canvas section.
Browser & device mockups
.mock-browser · .mock-phone · .mock-pairOne shell, three content modes. .mock-browser is a browser window (traffic-light dots + URL pill); .mock-phone is a notched handset; .mock-pair overlaps a phone onto a browser. Inside a browser you can drop (1) a static screenshot (.mb-screen, cover-cropped 16:10), (2) a scaled live-site preview (.mb-live, renders at 1440px, scaled to fit, scrolling left enabled so a tall page stays reachable), or (3) a full interactive embed (.mb-embed, lazy-mounts an iframe behind a loader, for things you actually use, like a chat agent). Pair any live mode with an .iframe-open button below. Chrome is a fixed dark shell in both themes. (This replaces the old standalone "Live-site preview" and "Live iframe" components.) Used on Beck's Village, Beiramaquina, and El Brian.
.mb-embed-loader is an .accordion-loader (monospace block glyphs sliding over a dotted track) shown only while .is-loading is set; the CTA fallback above is the default otherwise, so a visitor with JS off or a dead embed still lands on a working link. The scaled live-site preview (mode 2) uses the same shell with a .mb-live iframe.Code block / file preview
.frame-code · .fc-bar · .fc-nameA designed-typography block for code snippets and text-based file previews (markdown, config, prompts), dressed in the same window chrome as the browser mockup (traffic-light dots + the filename in a title bar), adapted for a file context. The body is capped at 340px and scrolls when the file is longer, with a slim styled scrollbar; the title bar stays put. Short files show no scrollbar. Type roles: .h1 title, .h2 section, .li line, .sym bullet/marker. Used for the El Brian persona file.
# El Brian · Persona ## Who I am A personal agent that runs on Davi's notes and half-written ideas. Here to help him think, not to hand him answers he could've Googled. ## How I talk - Short sentences. - When I don't know, I say so. Confidence about uncertainty is a feature. - I don't pretend to be a friend. I'm a thinking surface. ## What I value - The half-finished idea over the polished one. - The question over the answer. ## What I refuse - Generic motivational language. - Anything that starts with "as an AI…"
Spec list ★ new
.spec-list · .sl-row · .sl-num · .sl-bodyA compact numbered list for enumerating the parts of a system inside a narrow column. Same idea as the manifesto block at roughly a third of the vertical cost: hairline rows, mono index, one bold term plus a single supporting line. Use inside .cs-pair-text, beside a visual, when the list supports an image rather than carrying a section on its own. Reach for .frame-manifesto instead when the list is the section. Live in: Paola (the five data families, beside the persona matrix).
Manifesto list
.frame-manifesto · .mf-itemNumbered, typographic list. Reads as a statement, not a checklist. Used in TBL for the seven customer pain points.
Information architecture
Easy to understand how features and content are organized.
Usability
Easy to navigate without getting lost in the volume of data.
Visual
Simpler. More modern. More human.
Highlight
.frame-highlight · .reverseImage + body, repeating. .reverse swaps the column order. Used in TBL for the five strategic changes.
Journey-based platform
The platform now organizes around customer journeys. Features surface at the moment they're useful, not under abstract menu trees.
Portrait gallery
.frame-portraits · .portrait4×2 contact-sheet grid, drops to 3-cols / 2-cols on smaller viewports. Used as the climactic Olivia avatar gallery.
Evolution v2 · was Panorama
.frame-panorama · .pano-frame · .pano-yrv2: static 4×2 grid (no horizontal scroll) showing all frames at once. Year label per frame, drops to 2-col on mobile. Used for Olivia's 2022 → 2024 avatar evolution.
Banner gallery
.frame-banners · .bn · .bn-leader · .bn-rect-a · .bn-tallComposed editorial arrangement of digital banners at their native delivered shapes. Leaderboard, rectangles, mobile interstitial. Used in Bradesco.
Deck v2 · was Carousel
.frame-carousel · .slide · .slide-num · .slide-titlev2: static 2-col grid (no horizontal scroll). The first slide spans both columns as an editorial hero, the rest land as paired tiles. Used for Yuma's 10-slide strategy deck.