Playfair Display — Headings
Inter — Body
Body — Paste a recipe URL and ShopChopCook builds you a unified shopping list, a mise en place checklist, and a cook schedule.
Small / Muted — Works with most recipe sites.
Single-URL form used on the home page and all marketing pages. Validates YouTube links inline; shows contextual error messages for blocked sites (403), no structured data (422), and unreachable URLs (502). Warning display handled by RecipeFormUrlWarning. Component: components/recipe-form/UrlInput.vue.
| State | Trigger |
|---|---|
| YouTube warning | URL contains youtube.com or youtu.be — submit disabled |
| 422 error | Site has no structured recipe data |
| 403 error | Site blocked access |
| 502 error | URL unreachable |
SEO link cards on the home page. Source: pages/index.vue .seo-links section.
Multi-URL recipe form used on the plan page. Supports 1–3 URLs, saved recipe picker, edit/cancel flow, and YouTube + error warnings via RecipeFormUrlWarning. Component: components/recipe-form/PlanForm.vue.
| Prop / Event | Description |
|---|---|
v-model:urls | URL array — parent-owned, supports up to 3 entries |
v-model:editing | Editing state — parent reads this to show/hide plan tabs |
mealPlan | Null = planning mode; non-null = view mode |
isPlanOwner | Shows edit controls vs Remix plan button |
error / errorIs403 / errorIsNoRecipe | Error state set by parent after API call |
@plan | User submitted — parent calls parse + generate API |
@duplicate | User clicked Remix plan |
@cancel | User cancelled edit — parent should clear errors |
Used on the plan page cook schedule "Step by Step" tab. Component: components/live/LiveScheduleStep.vue.
| Element | Token |
|---|---|
| Active left border | var(--accent-strong) |
| Passive left border | var(--border) |
| Recipe tag | var(--accent) |
| Step label | var(--text) |
| Duration / meta | var(--text-muted) |
Active task cards in the HTML cook view (cook.vue, live.vue). Component: components/live/LiveActiveCard.vue.
| Element | Token |
|---|---|
| Active left border + Done btn | var(--accent) |
| Passive / paused left border | var(--border) |
| Countdown text | var(--accent) |
| Accent glow (active, hands-on) | box-shadow: 0 0 0 2px rgba(166,124,82,0.15) |
| Pause btn | btn-ghost |
| Done btn | btn-wood |
Upcoming task list in cook.vue and live.vue. Component: components/live/LiveUpcomingList.vue. Long-press to expand notes.
| Element | Token |
|---|---|
| Container | .card (var(--surface) + var(--border)) |
| Countdown / time | var(--accent) |
| Recipe tag | var(--text-muted) |
| Row divider | var(--border) |
| Start Now btn bg | var(--green) — #3F5F3D light / #4A7046 dark |
Semantic classes defined in main.styl. Components apply these for font rules and keep only color + layout in scoped styles. Five sizes total: 0.65 (badge), 0.72, 0.85, 1.0, 1.25, 1.8.
.text-recipe — 0.72rem · 600 · uppercase · 0.06em.text-badge — 0.65rem · 700 · uppercase · 0.08em.text-step — 1rem · 500 · line-height 1.35.text-step-active — Playfair Display · 1.25rem · 600 · line-height 1.25 — active cook view only.text-meta — 0.85rem · 500 · tabular-nums.text-note — 0.85rem · italic · line-height 1.5.text-timer — Playfair Display · 1.8rem · 700 · 0.02em · tabular-nums — active cook view onlyWebGL background + Canvas 2D overlay. Full-plan mode shown below. Source: components/NowLineGL.vue. Colors from composables/useChartColors.ts.
| Color token | Light | Dark | Used for |
|---|---|---|---|
active | #9B6F46 | #D2A874 | Hands-on bar — wood accent, dominant |
passive | rgba(160,170,180,0.45) | rgba(190,200,210,0.32) | Hands-off bar — metal/neutral, cool + quiet |
upcoming | rgba(166,124,82,0.48) | rgba(196,154,108,0.36) | Future task bar — desaturated wood, visible |
paused | rgba(120,120,140,0.55) | rgba(150,150,170,0.55) | Paused bar |
taskLabel | #ffffff | #F1E7D8 | Task name text inside bar |
recipeLabel | rgba(80,70,60,0.60) | rgba(200,170,130,0.55) | Recipe name — context, not focus |
nowLine | #7A5737 | #C49A6C | Vertical now-line — 2px, accent-strong |
nowGlow | rgba(166,124,82,0.18) | rgba(196,154,108,0.22) | Warm glow band behind now-line |
timeTick | rgba(0,0,0,0.15) | rgba(255,255,255,0.15) | Tick marks — quiet, non-distracting |
timeLabel | rgba(0,0,0,0.65) | rgba(255,255,255,0.65) | Time axis labels — clear hierarchy |
rowBg | rgba(0,0,0,0.035) | rgba(255,255,255,0.05) | Alternating row stripe — scanability |
WebGL bg (dark): vec3(0.155, 0.125, 0.095) / vec3(0.195, 0.160, 0.120) with gold glow at now-line position. Light: vec3(0.969, 0.961, 0.949) / vec3(0.950, 0.943, 0.933).
Static WebGL background + Canvas 2D overlay. Tasks grouped by recipe. Re-draws on resize and theme change. Source: components/PlanGanttGL.vue. Colors from composables/useChartColors.ts.
| Color token | Light | Dark | Used for |
|---|---|---|---|
active | #9B6F46 | #D2A874 | Hands-on bar — wood accent, dominant |
passive | rgba(160,170,180,0.45) | rgba(190,200,210,0.32) | Hands-off bar — metal/neutral, subordinate |
taskLabel | #ffffff | #F1E7D8 | Task name text inside bar |
recipeLabel | rgba(80,70,60,0.60) | rgba(200,170,130,0.55) | Recipe name — context, not focus |
timeTick | rgba(0,0,0,0.15) | rgba(255,255,255,0.15) | Tick marks — quiet, non-distracting |
timeLabel | rgba(0,0,0,0.65) | rgba(255,255,255,0.65) | Time axis labels — clear hierarchy |
rowBg | rgba(0,0,0,0.035) | rgba(255,255,255,0.05) | Alternating group stripe — scanability |
All colors shared via useChartColors() composable. WebGL bg same as NowLineGL (static, no pulse/glow). Layout: LABEL_H=13 + BAR_H=22 per task row, TASK_GAP=6 within recipe, GROUP_GAP=14 between recipes.
WebGL + Canvas 2D Gantt chart. Accepts raw RecipeGantt[] data and handles CPM scheduling internally. Renders each recipe as a horizontal row with colour-coded bars per task. Supports an optional currentMinute prop to draw a live "now" line. Source: components/GanttGL.vue.
currentMinute=20)currentMinute=20)| Color token | Light | Dark | Used for |
|---|---|---|---|
| Recipe colours | [0.478,0.329,0.188] / [0.620,0.455,0.239] / [0.678,0.545,0.341] | Cycling warm-wood palette per recipe | |
passive | rgb(122,130,139) | rgb(74,65,56) | Hands-off bar — muted, recedes behind active bars |
bg | rgb(247,245,242) | rgb(24,20,15) | Canvas background |
grid | rgba(0,0,0,0.06) | rgba(255,255,255,0.07) | Vertical tick lines |
sep | rgba(0,0,0,0.04) | rgba(255,255,255,0.05) | Row separator lines |
tickText | rgba(46,46,46,0.75) | rgba(234,224,213,0.65) | Time axis labels |
rowText | rgba(46,46,46,1) | rgba(234,224,213,1) | Recipe name + task labels |
NOW_COL | rgba(240,69,69,1) | Now-line — red, 2px | |
| Prop | Type | Notes |
|---|---|---|
gantt | RecipeGantt[] | Raw recipe + task data. CPM scheduling computed internally. |
currentMinute | number? | If provided, draws a red vertical now-line at that minute. |
Layout constants: LABEL_W=240 · PX_MIN=9px per minute · AXIS_H=32 · REC_H=34 recipe label row · ROW_H=56 task row · BAR_H=26 bar height · BAR_TOP=15 offset. Scrollable horizontally via .ggl-scroll.
Sticky top navigation bar. Uses a 3-column CSS grid (1fr auto 1fr) so the center nav stays truly centred regardless of what's in the left or right columns. Component: components/AppTopbar.vue. Shared styles in assets/css/main.styl.
| Slot | Class | Contents |
|---|---|---|
#left | .topbar-left | Logo + Plan/Cook toggle (PlanCookToggle). display:flex; gap:0.75rem. |
#center | .topbar-center | Section pills (PlanNav or CookNav). Centred with justify-content:center. |
| default | .topbar-actions | Auth buttons, share button, locale, theme toggle. justify-self:end. |
.section-pill| Property | Value | Notes |
|---|---|---|
| Font size (desktop) | 0.83rem | Reduced to 0.72rem below 700px |
| Font weight | 500 · active: 600 | |
| Default color | var(--text-muted) | #6B6560 light / #8A8076 dark |
| Active color | var(--wood-dark) | #A0744A light / #C49A6C dark |
| Active indicator | border-bottom: 2px solid var(--wood-dark) | Flush with topbar bottom border |
| Hover color | var(--text) | Border shows var(--border) on hover |
| Icon wrapper | .pill-icon | Hidden below 700px to save space |
.pct| Property | Value | Notes |
|---|---|---|
| Font size | 0.83rem | |
| Font weight | 500 | |
| Default color | var(--text-muted) | |
| Active color | var(--text) | Active segment gets background: var(--bg) |
| Border | 1px solid var(--border) | Wraps the whole toggle; divider between segments |
| Border radius | var(--radius) — 6px | |
| Mobile | .pct hidden; .pct-mobile shown | Single directional link, 0.72rem, hidden below 700px toggle |
.plan-share-btn| Breakpoint | Appearance |
|---|---|
| > 700px | Ghost button — 1px solid var(--border), icon + "Copy link" label, 0.82rem. Hover: border-color: var(--wood), bg: var(--ghost-hover) |
| ≤ 700px | Borderless icon button — no border, color: var(--text-muted), min-height: 44px, label hidden |
| ≤ 300px | Hidden (display:none) |
| Token | Light | Dark | Used for |
|---|---|---|---|
--surface | #FFFFFF | #231E17 | Topbar background |
--border | #DAD6CF | #3A342C | Bottom border, toggle border, pill hover indicator |
--text | #2E2E2E | #F0E8DC | Active pill, active toggle segment, share button text |
--text-muted | #6B6560 | #8A8076 | Inactive pills, toggle default, mobile icon buttons |
--wood-dark | #A0744A | #C49A6C | Active pill color + underline |
--wood | #D2B48C | #9A6E42 | Share button hover border |
--ghost-hover | #fdf8f3 | #2A231A | Share button hover background |
--bg | #F7F5F2 | #18140F | Active toggle segment background |
| Breakpoint | Changes |
|---|---|
| > 700px | Full layout. Auth links, locale, theme toggle visible. Plan/Cook toggle visible. Pill icons visible. |
| ≤ 700px | Auth links hidden (.topbar-desktop-only). Hamburger menu shown. Plan/Cook toggle collapses to single .pct-mobile link. Pill icons hidden. Topbar padding reduced to 0.5rem 1rem. Pills shrink to 0.72rem. |
| ≤ 350px | Shop + Chop pill links hidden (.plan-nav-pill). |
| ≤ 300px | Share button and fullscreen button hidden. |
| Prop | Type | Effect |
|---|---|---|
hero | boolean | Switches to .hero-topbar class — transparent background, used on the home page hero |
hideMenu | boolean | Suppresses the mobile hamburger menu — used on /cook/live where the fullscreen button is the sole mobile action |
Sprite icons via AppIcon component (<AppIcon name="…" :size="16" />). All use currentColor and respond to light/dark mode. Sprite defined in AppSprite.vue, injected once in app.vue.