A tablet-first web app that helps a player at the table find something to say in character. The user picks a conversational direction (Battle, Angry, Persuade, Deceive…) from a grid, scans a list of quotes for that direction, and taps one to speak it. Quotes come from two sources: hand-written presets and generated lines, and every quote is visibly marked as one or the other.
The committed direction is the three-column layout (1a) with a Day / Night mode toggle (2a / 2b).
The files in this bundle are design references created in HTML — prototypes showing intended look and behavior,
not production code to copy directly. The task is to recreate these designs in the target codebase's existing
environment using its established patterns, component library, and styling approach. If no environment exists yet
(the linked repo bill-ver3/dndapp was empty at the time of design), pick an appropriate stack — React + Vite with
CSS modules or Tailwind is a fine default — and implement the designs there.
Do not ship the HTML. Do match the measurements, colors, and type below closely.
High-fidelity. Colors, typography, spacing, and layout are final and should be reproduced closely. Interaction behavior is specified below but was not built as a working prototype — the mockups are static states.
Purpose: one-time (editable) sheet that conditions every quote the app offers.
Layout: 1024×768. CSS grid, two columns: 1fr 340px.
- Left column —
padding: 34px 36px, vertical flex,gap: 24px, page background#f0eadc. - Right column — dark rail, background
#231b21,padding: 34px 28px, vertical flex,gap: 16px.
Left column contents, top to bottom:
- Header block (
gap: 6px): eyebrow "NEW CHARACTER" (10px, letter-spacing.2em, uppercase,rgba(35,27,33,.45)); H1 "Who is speaking?" (Cardo 700, 34px, line-height 1.05); sub-copy 16px/1.5rgba(35,27,33,.62),max-width: 52ch. - Row: Name (grid
1.4fr) + Level (1fr),gap: 14px. Field:padding 13px 14px,1px solid rgba(35,27,33,.3), radius 4px, background#fff, value 19px. - Row: Race + Class, two equal columns — same field style, with a
▾chevron inrgba(35,27,33,.45)(selects). - Voice — label row with "Voice — pick up to four" left and "3 chosen" right in accent
#9a6b1f. Below, a wrapping flex of pill chips,gap: 8px. Chip:padding 10px 15px,border-radius: 999px, 15px text.- Selected: background
#231b21, text#f3ede0. - Unselected:
1px solid rgba(35,27,33,.28), transparent background. - Options shown: Blunt, Devout, Dry wit (selected); Formal, Grim, Boastful, Kind, Sly, Weary, Theatrical.
- Selected: background
- A sentence about them (optional) — textarea,
min-height: 76px, same field border/background, 17px/1.5. - Footer actions pushed to the bottom (
margin-top: auto,gap: 10px):- Primary "Start speaking" — background
#9a6b1f, text#fbf6ea,padding 15px 30px, radius 4px, 17px. - Secondary "Save for later" —
1px solid rgba(35,27,33,.3),padding 15px 22px.
- Primary "Start speaking" — background
Right rail contents: eyebrow "SAMPLE LINE"; a live sample quote in Cardo italic 24px/1.4 #f6f0e2; explanatory
copy 14px/1.55 rgba(243,237,224,.6); 1px divider rgba(243,237,224,.18); "YOUR CHARACTERS" list — rows with
1px solid rgba(243,237,224,.2), radius 4px, padding 12px 14px, name 16px #f3ede0 + meta 13px rgba(243,237,224,.5);
bottom italic footnote 13px/1.55 rgba(243,237,224,.45).
The sample line re-generates as the sheet changes, so the user can hear the voice before the session starts.
Purpose: the core loop. Pick a direction, scan lines, select one, speak it.
Layout: 1024×768. CSS grid, three columns: 212px 1fr 300px.
Page background (Day): #f0eadc plus two soft radial washes —
radial-gradient(circle at 20% 15%, rgba(154,107,31,.08), transparent 55%) and
radial-gradient(circle at 85% 80%, rgba(47,93,80,.07), transparent 50%).
border-right: 1px solid rgba(35,27,33,.16), padding: 20px 18px, background tint rgba(35,27,33,.035), vertical
flex gap: 18px. Sections, each with a 10px uppercase eyebrow (letter-spacing .18em, rgba(35,27,33,.45)):
- Speaking as — name Cardo 700 25px; sub-line 13px italic
rgba(35,27,33,.62). - 1px divider
rgba(35,27,33,.14). - Voice — the same voice chips at 12px /
padding 5px 9px, outlinedrgba(35,27,33,.28), plus a dashed "+ add" chip. - Scene — free text, 14px/1.45.
- Table light — the Day/Night toggle (see below).
- Saved — pushed to bottom (
margin-top: auto), 13pxrgba(35,27,33,.7), e.g. "14 lines · 3 this session".
Day/Night toggle: a segmented pill. Track: display:flex; gap:3px; padding:3px; border-radius:999px;
1px solid rgba(35,27,33,.22), background rgba(255,255,255,.5). Two equal segments (flex:1), 13px, centered,
padding: 8px 0, border-radius: 999px, labels "☀ Day" and "☾ Night". Active segment in Day mode: background
#231b21, text #f3ede0. In Night mode: active is background #c8992f, text #1a1512; inactive text
rgba(241,233,218,.6), track border rgba(241,233,218,.2), track background rgba(0,0,0,.3).
padding: 22px 22px 18px, vertical flex gap: 16px.
- Section head: "Directions" Cardo 700 20px, right-aligned meta "12 · tap to load lines" 12px
rgba(35,27,33,.5). - Grid:
grid-template-columns: repeat(3, 1fr),gap: 10px. Card:padding 13px 12px, radius 5px, vertical flexgap: 4px; title Cardo 700 17px; sub-label 12px/1.3rgba(35,27,33,.55).- Default:
1px solid rgba(35,27,33,.2), backgroundrgba(255,255,255,.5). - Selected:
1.5px solid #9a6b1f, background#9a6b1f, title#fbf6ea, sub-labelrgba(251,246,234,.85),box-shadow: inset 0 -2px 0 rgba(0,0,0,.14). - Directions shown: Battle (Rally, taunt, charge), Angry (Accuse, threaten), Persuade (Reason, flatter), Deceive (Bluff, misdirect), Intimidate (Loom, promise harm), Banter (Needle, joke), Mourn (Eulogy, regret), Plead (Beg, bargain), Interrogate (Press, corner).
- Default:
- Divider row:
border-top: 1px solid rgba(35,27,33,.14); left "Battle — 24 lines" (Cardo 700 20px + 14px muted count); right a source filter group,gap: 6px, chipspadding 6px 10pxradius 4px, 12px: All (active: background#231b21, text#f3ede0), ✦ Preset, ◈ Generated (outlinedrgba(35,27,33,.24)). - Quote rows: vertical flex
gap: 8px. Row:padding 12px 14px,1px solid rgba(35,27,33,.18), radius 4px, backgroundrgba(255,255,255,.72), and a 3px left border that encodes source —#9a6b1ffor preset,#2f5d50for generated. Inside: source glyph (Cardo 14px, in the same color), quote text 17px/1.45 (flex: 1), sub-category label 12pxrgba(35,27,33,.45)right-aligned (Rally / Taunt / Command).
Dark in both modes: background #231b21 in Day, #0f0d0c in Night. border-left: 1px solid rgba(35,27,33,.16),
padding: 20px 18px, vertical flex gap: 14px.
- Eyebrow "SELECTED LINE"
rgba(243,237,224,.5). - The quote, Cardo italic 23px/1.4
#f6f0e2. - Meta lines 13px/1.5
rgba(243,237,224,.62): source + direction/sub-category; "Matches voice: Blunt, Dry wit". - Divider
rgba(243,237,224,.18). - Buttons, full width,
padding: 13px, radius 4px, 15px, centered:- "Speak it" — background
#9a6b1f, text#fbf6ea. - "Save to character" —
1px solid rgba(243,237,224,.35), text#f3ede0. - "Reroll ◈ three more" —
1px solid rgba(243,237,224,.2), textrgba(243,237,224,.7).
- "Speak it" — background
- Bottom italic footnote 12px/1.5
rgba(243,237,224,.45): spoken lines are logged to the session.
Same DOM and metrics; only surface values change.
| Role | Day | Night |
|---|---|---|
| Page background | #f0eadc |
#1a1512 |
| Body text | #231b21 |
#f1e9da |
| Hairlines / muted text | rgba(35,27,33, α) |
rgba(241,233,218, α) — same α |
| Card fill | rgba(255,255,255,.5) |
rgba(241,233,218,.06) |
| Quote row fill | rgba(255,255,255,.72) |
rgba(241,233,218,.07) |
| Accent (text, borders) | #9a6b1f |
#c8992f |
| Accent (filled buttons/cards) | #9a6b1f |
#9a6b1f (unchanged — still reads on dark) |
| Secondary accent | #2f5d50 |
#4e8b78 |
| Selected-line rail | #231b21 |
#0f0d0c |
| Active dark chip ("All") | #231b21 / #f3ede0 |
#c8992f / #1a1512 |
| Warm wash | rgba(154,107,31,.08) |
rgba(200,153,47,.18) |
| Cool wash | rgba(47,93,80,.07) |
rgba(78,139,120,.14) |
Implement this as a data-theme="day" | "night" attribute on the app root with CSS custom properties, not two
component trees.
- Tap a direction card → becomes selected (accent fill), quote list below reloads for that direction; the "N lines" count and sub-category filter chips update. Only one direction selected at a time.
- Tap a quote row → becomes the selected line in the right rail. Row gets a subtle selected treatment (raise fill opacity, accent left border stays). Rail content swaps.
- Speak it → marks the line as said: it is appended to the session log, and in the list it renders muted with a
"said this session" suffix on its meta line (see
1cin the mockup for that treatment). Does not remove the line. - Save to character → adds to the character's saved lines; increments the "Saved" counter in the left rail.
- Reroll ◈ three more → requests three newly generated lines for the current direction + scene + voice, prepending them to the list. Show a skeleton/pending state on those three rows while in flight.
- Source filter (All / ✦ Preset / ◈ Generated) → client-side filter of the loaded list.
- Day/Night toggle → flips
data-theme, persisted tolocalStorage. Respectprefers-color-schemeon first load. - Voice chips in the left rail → open the character sheet, or edit in place; changing voice invalidates the generated portion of the list.
- Scene text → editable inline; used as generation context.
- Transitions: 120–160ms
ease-outon background/border color for selection and theme changes. No layout animation. - Loading: generated rows show shimmer placeholders at the quote row's height. Empty: a direction with no presets shows only the generate CTA. Error: generation failure shows an inline retry on the rail button.
character—{ id, name, level, race, class, voiceTags: string[] (max 4), note }scene— free text string, per session.theme—'day' | 'night', persisted.selectedDirection— direction id; drives the quote query.quotes— list of{ id, text, source: 'preset' | 'generated', direction, subcategory, saidThisSession }.sourceFilter—'all' | 'preset' | 'generated'.selectedQuoteId— drives the right rail.savedQuoteIds— per character, persisted.sessionLog— ordered list of spoken quote ids with timestamps.- Data needs: presets can be a static bundled JSON keyed by direction and subcategory. Generation is a request taking
{ character, scene, direction, alreadySaid }and returning three lines.
Color — Day
- Paper
#f0eadc· Ink#231b21· Rail/inverse text#f3ede0,#f6f0e2,#fbf6ea - Brass accent
#9a6b1f· Deep green (generated)#2f5d50 - Hairline
rgba(35,27,33,.14)· Borderrgba(35,27,33,.2)· Strong borderrgba(35,27,33,.3) - Muted text
rgba(35,27,33,.45)/.55/.62/.7 - Card fill
rgba(255,255,255,.5)· Row fillrgba(255,255,255,.72)
Color — Night
- Leather
#1a1512· Deep rail#0f0d0c· Ink#f1e9da - Lit brass
#c8992f· Lit green#4e8b78 - Hairlines & muted text:
rgba(241,233,218, α)mirroring the Day alphas
Typography
- Display: Cardo — 700 for titles and names; 400 italic for quotes in the rails.
- Text/UI: Newsreader — 300–600.
- Scale in use: 10px eyebrow (uppercase,
letter-spacing .18em–.2em) · 12px meta · 13px sub · 14px body · 15–16px controls · 17px quote row · 19px field value · 20px section head · 23–24px rail quote · 25px name · 34px page title. Line-heights: 1 for controls/eyebrows, 1.3–1.5 for body, 1.05–1.15 for display.
Spacing — 4 · 6 · 8 · 10 · 12 · 14 · 16 · 18 · 20 · 22 · 24 · 26 · 30 · 34 · 36
Radius — 4 (buttons, fields, rows) · 5 (direction cards, panels) · 6 (outer card) · 999 (chips)
Borders — 1px hairline; 1.5px on a selected direction card; 3px left edge on quote rows (source); 2px bottom rule
under a page title in the ledger variant.
Shadow — used sparingly: inset 0 -2px 0 rgba(0,0,0,.14) on the selected direction card. No drop shadows in-app.
None. No images, no icon set. The only glyphs are typographic: ✦ (preset), ◈ (generated), ☀ / ☾ (theme
toggle), ▾ (select chevron), + (add tab). If the codebase has an icon library, substituting equivalents is fine —
but keep ✦ / ◈ as the source marks, since they carry meaning in the legend and filters.
Fonts: Cardo and Newsreader, both Google Fonts (SIL Open Font License). Self-host in production.
Dialogue App Mockups.dc.html— all mockups. Open in a browser. Sections are ordered newest-first:- Turn 2 —
2aDay and2bNight of the committed layout. This is the design to build. - Turn 1 —
1athe original light three-column,1ba dark low-density alternative (rejected),1ca tabbed-ledger alternative (rejected),1dcharacter setup (build this too).
- Turn 2 —
support.js— runtime for the mockup file only. Not part of the design; do not port.
Accessibility notes for implementation: the 12px rgba(35,27,33,.45) meta text sits near the 4.5:1 floor on paper —
if it renders light in your stack, step it to .55. The 3px source border is a redundant cue alongside the ✦ / ◈
glyph, so color is never the only signal.