--- version: alpha name: twirl-design description: "A pale lavender canvas (#f3ecff), the first lavender/violet-family light canvas in this batch, distinct from the pink, peach, and cream canvases used elsewhere. Deep grape-black ink (#2e1a47) carries body text. Violet (#9333ea) and deep berry-magenta (#c11f74) are the two brand accents, both passing AA as on-canvas text and as white-label fills with a single tier, no separate dark-fill-ink or text-safe tier needed. Fredoka carries every headline, Inter carries anything meant to be read quickly. The homepage's hero is one cotton candy cone, four poofs inflating into place in sequence as you scroll, each in its own real flavor color." --- ## Brand Positioning Twirl runs a cotton candy and funnel cake cart, and it exists because most carnival-food sites either drown a genuinely simple, fast treat under circus-tent clip art and calliope-music energy, or go so minimal the actual product disappears. Twirl's bet: show the cotton candy actually spinning up, poof by poof, in real flavor colors, and stay plain everywhere else, a real flavor list, a real price, a real line. If a component performs "carnival magic" without being tied to a real flavor, a real price, or the spun sugar itself, it doesn't belong here. colors: accent: "#9333ea" accent-alt: "#c11f74" ink: "#2e1a47" ink-muted: "#6b5590" canvas: "#f3ecff" ## Logo / Wordmark System The mark is a single cotton candy poof on a stick, drawn as an irregular cloud outline, never a perfect circle, the exact lumpy silhouette of real spun sugar, not a smooth balloon shape. ```svg ``` - **Lockup**: mark, then a `0.5rem` gap, then "Twirl" in Fredoka, mark always left of the wordmark. - **Minimum size**: mark not smaller than `18px` tall, below that the cloud's lumps blur into a circle. - **Clearspace**: empty margin equal to the mark's own height on all four sides. - **Don't**: don't smooth the cloud into a circle, that's a balloon, not spun sugar. Don't recolor the poof, it's always `--accent`, never `--accent-alt`. Don't drop the stick, without it the mark is just a cloud. ## Color System ### Roles | Role | Token | Value | Used for | |---|---|---|---| | Canvas | `--bg` | `#f3ecff` | Page background | | Surface | `--panel` | `#ffffff` | Nav, flavor cards, form fields | | Surface raised | `--raised` | `#faf5ff` | Hover state, input focus fill | | Border | `--border` | `#e4d4fb` | Card edges, dividers, input outlines | | Ink | `--ink` | `#2e1a47` | Primary text, headlines | | Ink muted | `--dim` | `#6b5590` | Secondary text, meta lines, placeholders | | Violet | `--accent` | `#9333ea` | Primary button fill (white label), links, the mark's poof, "available" status | | Berry | `--accent-alt` | `#c11f74` | Secondary button fill (white label), "selling fast" status | **Unlike the four light-canvas systems earlier in this batch** (Pearl & Pop, Sunsplit, High Score, Flashpop), both of Twirl's accents pass AA directly, as text on the canvas and as a white label on their own fill, with no separate darker text-safe tier and no ink-label-on-fill workaround required. This is a genuine per-palette result, not a simplification: these particular hues sit dark enough in the violet-magenta range to clear both checks at once, where the earlier batch's oranges, yellows, and corals could not. **The cotton candy cone's four poof colors are illustrative, not tokens**, the same exception every other system in this batch already establishes: real spun-sugar flavors come in real colors, grape violet, bubblegum pink, blue raspberry, watermelon, not a two-token gradient wearing a cloud shape. `--accent` and `--accent-alt` never appear as poof fills in the hero, they're reserved for the interface. ### Contrast (computed from the hex values above via WCAG relative luminance) | Pairing | Ratio | Passes | |---|---|---| | `--ink` on `--bg` | 13.47:1 | AAA | | `--dim` on `--bg` | 5.49:1 | AA | | `--accent` on `--bg` (links, on-canvas text) | 4.68:1 | AA | | `--accent-alt` on `--bg` (links, on-canvas text) | 4.91:1 | AA | | white label on `--accent` fill | 5.38:1 | AA | | white label on `--accent-alt` fill | 5.65:1 | AA | typography: display: fontFamily: Fredoka fontWeight: 600 body: fontFamily: Inter fontWeight: 400 ## Type Scale | Token | Size | Weight | Family | Line-height | Letter-spacing | Used for | |---|---|---|---|---|---|---| | `--text-display` | `clamp(2.25rem, 5.5vw, 3.75rem)` | 600 | Fredoka | 1.1 | 0 | Homepage H1 only | | `--text-h2` | `1.5rem` | 600 | Fredoka | 1.2 | 0 | Section headings | | `--text-h3` | `1.1rem` | 600 | Fredoka | 1.25 | 0 | Flavor names | | `--text-body` | `1rem` | 400 | Inter | 1.65 | 0 | Paragraph copy | | `--text-small` | `0.85rem` | 400 | Inter | 1.5 | 0 | Nav, footer, helper text, meta lines | | `--text-label` | `0.75rem` | 600 | Inter | 1 | 0.06em, uppercase | Kickers, badges, form labels | Fredoka's rounded weight matches the soft, lumpy shape of the poof itself, reserved for the headline, section headings, and flavor names. Inter carries anything read as a sentence or clicked as a control, including every price and wait note. ## Spacing Scale Base unit `8px`. Tokens in use: `--space-2: 0.5rem`, `--space-3: 1rem`, `--space-4: 1.5rem`, `--space-6: 2rem`, `--space-8: 3rem`, `--space-12: 4rem`. ## Shape `--radius: 16px` on cards, panels, and inputs. `--radius-pill: 999px` on buttons and badges. The poof itself uses a hand-drawn irregular cloud path, the one shape in the system exempt from the two-shape scale, because real cotton candy is never a perfect circle. ## Iconography No icon library. The only non-text graphics are the logo mark and the hero's own cone (hand-built SVG). Wait time and stock notes are plain text, not a clock or sparkle icon next to unrelated words. ## Motion Principles Scroll spins up one cotton candy cone: four poofs inflate into place in sequence, each scaling from a point with a slight overshoot as it lands, in its own real flavor color. The moment a poof lands, the flavor card it represents slides in below. One-shot reveal, nothing loops, every flavor is written out in plain markup at all times regardless of scroll or script state. Scroll trigger uses a fixed pixel-distance range ("top top" / "+=…"), not a viewport-percentage range, after Sunsplit's pre-elapsed-trigger bug proved percentage triggers are unsafe for hero elements sitting close to the viewport top at load; verified at exactly `0` progress at scroll position `0`. `prefers-reduced-motion: reduce` renders the full cone already spun and every card already visible. ## Voice & Tone Warm, a little playful, plain about price and wait time. In practice: - **Button label**: "See today's flavors", not "Step right up for carnival magic!" - **Flavor description**: "Real grape flavoring, not just purple sugar, you can actually taste it.", not "A MAGICAL swirl of wonder!" - **Wait note**: "Each cone's spun fresh to order, about two minutes.", not "Fast as lightning!" - **Empty state**: "Blue raspberry's out for today, back tomorrow.", not "Oops, all gone!" ## Accessibility Baseline - Focus ring: `2px solid var(--accent)`, `2px` offset, applied via `:focus-visible` on every interactive element. - Reduced motion: the poof-inflate sequence and every card reveal check `prefers-reduced-motion: reduce` and render the finished state immediately. - Minimum tap target: `44px` in the smallest dimension for every button and input. - Color is never the only signal: a flavor's badge pairs "Available" or "Selling fast" with the word, never color alone. - Every flavor the cone can reveal is also written out in plain markup at all times, see Motion Principles. ## Components ### Navigation Single-line bar, `64px` tall, `--panel` background with a `--border` bottom hairline, sticky to the top. Left: the mark + wordmark lockup. Right: a "Flavors" link plus one primary button ("See today's flavors"). ### Buttons Three variants, one radius (`--radius-pill`): - **Primary**: `--accent` fill, white label, used once per view for the real action. - **Secondary**: transparent fill, `1px solid --border`, `--ink` label. - **Ghost**: text-only, `--dim` label, `--ink` on hover. All variants define `:hover`, `:active` (`transform: scale(0.97)`), `:disabled` (`opacity: .45`), and `:focus-visible`. ### The cone (signature interaction) Documented in full under Motion Principles. Structurally: a static stick and cone-base, four poof `` groups scroll-driven from `scale(0)`/`opacity: 0` to full size with a brief overshoot, in sequence, real flavor colors. ### Flavor card A flat `.panel`: `--panel` background, `1px solid --border`, `--radius`, no shadow. Holds the flavor name (Fredoka), a real flavoring note, the price, and an availability badge. Revealed by its poof landing above, present in the DOM at all times. ### Badge `--radius-pill`, `1px solid` the semantic color, semantic-color text on transparent fill: `--accent` for "Available," `--accent-alt` for "Selling fast." Never filled solid. ### Footer One row: cart name/tagline on the left, real page links on the right (Flavors, back to gallery). ## Do's and Don'ts ### Do - Let the cone's four poofs be real flavor colors, not a two-token gradient wearing a cloud shape. - Keep the poof outline lumpy and irregular, never a smooth circle. - Write every flavor the cone can reveal into plain markup at all times. - Say the real spin time, not a vague "fast" claim. ### Don't - Don't use circus-tent clip art, a calliope sound cue, or a ringmaster mascot anywhere. - Don't perform "carnival magic" or "step right up" copy, name the actual flavor and price instead. - Don't replay the cone's spin-up on every scroll pass, it plays once, like a real cone being spun. - Don't invent a third accent for a flavor state, "available" and "selling fast" are the only two. ## Known Constraints - Two routes exist (homepage, flavors). There is no checkout and no authenticated state, "See today's flavors" and the order form are not wired to anything real, this is a concept demo. - The cone holds exactly four poofs because that's the current flavor lineup. A fifth flavor rotates one out, it doesn't add a fifth poof. - No responsive breakpoint table beyond a single-column mobile collapse of the cone stage and flavor cards. ## Process Built through the impeccable-audited anti-slop workflow: never-list written before any HTML, page structure derived from the signature interaction (one real cone, actually spinning up) rather than a template, mechanical detector run clean, and the scroll trigger's start/end bounds checked to verify `0` progress at scroll position `0` before this system was considered done, using a fixed-pixel-distance trigger from the outset given the pre-elapsed-trigger lesson from Sunsplit earlier in this batch.