Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .bolt/skills/slides/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,33 @@ slide**, never a circus. All honors `prefers-reduced-motion`.

---

## Step 6.5 — UI/UX Pro Max design intelligence

Great slide decks are living web applications, so professional UI/UX standards apply:

1. **Accessibility & contrast (WCAG 4.5:1 minimum)**:
- Ensure text contrast never washes out against dark glass backgrounds (`--surface` or `--surface-2`). Normal text must achieve at least 4.5:1 contrast against its backdrop; large headlines (≥24px) must achieve at least 3:1.
- For light mode decks (`html { color-scheme: light; }`), never use faint gray body text. Ensure primary text is deep ink (e.g., `#0f172a` slate-900) and muted text is at least `#475569` (slate-600).
- Use `--focus-ring` for keyboard navigability. Never remove focus outlines without replacement.

2. **Touch targets & responsive interaction**:
- Minimum 44×44px hit-target size for all touch elements on phones and tablets.
- Interactive cards and clickable elements must include `cursor: pointer`.
- Micro-interactions: provide physical tactile feedback on press (`:active { transform: scale(0.96); }`) with 150–300ms durations. Never use scale transforms that cause layout shifts on hover.

3. **SVG iconography over emojis**:
- **No emoji UI icons:** Never use raw emojis (🚀, 💡, ⚙️) as icons inside buttons, cards, or feature matrices. Use clean inline SVGs with fixed viewboxes (`viewBox="0 0 24 24"`). Reserve emojis solely for the page favicon.

4. **Typography hierarchy & rhythm**:
- Pair heading and body typography purposefully (e.g., display geometric sans + clean neutral text, or editorial serif + modern grotesque).
- Maintain readable body line height (1.5–1.75) and cap paragraph widths at 65–75 characters per line to preserve reading comfort.
- Use tabular numerals (`font-feature-settings: 'tnum' 1;`) for metrics, financial figures, dates, and charts.

5. **Data visualization accessibility**:
- SVGs presenting information (`BarChart`, `LineChart`, `DonutChart`) must include `role="img"` and descriptive `aria-label`s summarizing the insight so non-visual presenters understand the data.

---

## Extend the system — invent new slides, components & visuals

The kit is a **floor, not a ceiling.** The bundled components cover a lot (comparison,
Expand Down Expand Up @@ -315,4 +342,5 @@ trigger or that it exists — just deliver the demo.
- [ ] **Responsive:** looks right narrow + wide — sections stack, nothing clips or
needs scrolling. Builds reveal in the intended order.
- [ ] Motion is restrained; reduced-motion respected.
- [ ] **UI/UX Pro Max audit:** No emojis used as UI icons (SVGs only); all text achieves WCAG contrast minimums (≥4.5:1); interactive elements provide visible `:focus-visible` rings and responsive touch targets (≥44px); SVG data visualizations include `role="img"` and descriptive `aria-label`.
- [ ] `npm install && npm run dev` runs with no console errors; `npm run build` passes.
77 changes: 77 additions & 0 deletions docs/superpowers/plans/2026-10-11-ui-ux-pro-max-enhancements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# UI/UX Pro Max Enhancements Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Integrate UI/UX Pro Max design intelligence into Bolt Slides, improving tokens, focus/touch ergonomics, ARIA component accessibility, and agent slide-authoring guidelines for open source contribution.

**Architecture:**
1. Layer 1: Token & CSS Foundations (`tokens.css`, `base.css`) - Focus rings, touch target safety, active feedback.
2. Layer 2: Component ARIA Standards (`Tabs.tsx`, `Accordion.tsx`, `Charts.tsx`, `Table.tsx`) - WAI-ARIA roles, attributes, and relationship bindings.
3. Layer 3: Agent Intelligence (`.bolt/skills/slides/SKILL.md`) - UI/UX Pro Max design system rules and accessibility criteria.

**Tech Stack:** React 18, Vite, TypeScript, Framer Motion, CSS custom properties.

**Spec:** `docs/superpowers/specs/2026-10-11-ui-ux-pro-max-enhancements-design.md`

## Global Constraints
- Engine integrity: Keep `src/deck/` core layout intact and preserve existing shortcuts and sync protocol.
- Theme tokens: All component styling must reference `var(--...)` tokens in `src/styles/tokens.css`.
- Zero new dependencies: Plain React + CSS + SVG only.
- Build safety: `npx tsc --noEmit` and `npm run build` must pass cleanly without warnings or errors.

## Review Focus
- Keyboard tab order and visible `:focus-visible` styling on all interactive controls.
- Mobile touch target hit areas $\ge 44\text{px}$ on viewports $\le 640\text{px}$.
- WAI-ARIA tab and accordion panel linking (`aria-controls`, `aria-labelledby`, `role="tabpanel"`, `role="region"`).
- Screen-reader accessibility for SVG charts (`role="img"`, `aria-label`).
- Preservation of `prefers-reduced-motion` across all components.

---

### Task 1: Design Tokens & CSS Foundations
**Files:**
- Modify: `src/styles/tokens.css`
- Modify: `src/styles/base.css`

- [ ] Add `--focus-ring` and `--touch-target-min` variables to `:root` in `src/styles/tokens.css`.
- [ ] Add `:focus-visible` styling for interactive buttons, tabs, accordions, and controls in `src/styles/base.css`.
- [ ] Ensure mobile touch target dimensions ($\ge 44\text{px}$) for `.noir-icon-btn` and dock elements in `@media (max-width: 640px)`.
- [ ] Add tactile `:active` state scaling and transitions for interactive buttons.
- [ ] Run `npx tsc --noEmit && npm run build` to verify CSS compilation and types.

---

### Task 2: Component Accessibility & ARIA Standardization
**Files:**
- Modify: `src/components/Tabs.tsx`
- Modify: `src/components/Accordion.tsx`
- Modify: `src/components/Charts.tsx`
- Modify: `src/components/Table.tsx`

- [ ] Update `Tabs.tsx` to link each tab with its panel using `id`, `aria-controls`, `role="tabpanel"`, and `aria-labelledby`.
- [ ] Update `Accordion.tsx` to link each accordion trigger with its body using `id`, `aria-controls`, `role="region"`, and `aria-labelledby`.
- [ ] Update `Charts.tsx` (`BarChart`, `LineChart`, `DonutChart`) to include `role="img"` and descriptive `aria-label`s.
- [ ] Update `Table.tsx` to render table caption semantics with `<caption>` inside `<table>` or `aria-describedby`.
- [ ] Run `npx tsc --noEmit && npm run build` to verify TypeScript types and bundle.

---

### Task 3: Enrich Agent Authoring Intelligence with UI/UX Pro Max
**Files:**
- Modify: `.bolt/skills/slides/SKILL.md`

- [ ] Add a dedicated "UI/UX Pro Max Design Principles" section in `.bolt/skills/slides/SKILL.md`.
- [ ] Document WCAG contrast minimums, typography pairing guidelines, touch targets, and SVG icon rules.
- [ ] Update the "Definition of Done (self-check)" checklist with accessibility and contrast items.
- [ ] Run `npx tsc --noEmit && npm run build` to ensure everything remains green.

---

### Task 4: Git Setup & Open Source Contribution Preparation
**Files:**
- Workspace root preparation
- Git branch & commit creation for PR readiness

- [ ] Verify clean git tracking or initialize repository branch if needed.
- [ ] Commit all changes with a descriptive open source contribution message (`feat(ui-ux): integrate UI/UX Pro Max design intelligence, accessibility, and focus systems`).
- [ ] Provide the user with exact GitHub fork and PR instructions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# UI/UX Pro Max System Enhancements for Bolt Slides - Design Specification

## Overview
This specification details the comprehensive integration of UI/UX Pro Max design intelligence into **Bolt Slides** (`stackblitz/bolt-slides`). The goal is to elevate Bolt Slides' accessibility, mobile responsiveness, keyboard interaction ergonomics, and agent slide-authoring guidelines to enterprise-grade open-source standards.

## Scope of Enhancements

### 1. Design Tokens & Focus System (`src/styles/tokens.css` & `src/styles/base.css`)
- **Focus Tokens**:
- Introduce `--focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--primary);` to provide high-visibility, brand-tinted focus rings for keyboard navigators.
- Introduce `--touch-target-min: 44px;` in accordance with WCAG 2.5.5 / UI/UX Pro Max criteria.
- **Universal Keyboard Accessibility**:
- Apply `:focus-visible` styling to `.noir-icon-btn`, `.tab`, `.acc-head`, `.noir-thumb`, `.ann-btn`, `.ann-color`, and `.ann-size`.
- Ensure outline/rings only display on keyboard navigation (`:focus-visible`), avoiding unwanted outlines on mouse clicks.
- **Mobile Touch Targets**:
- Ensure `.noir-icon-btn` and navigation hitboxes maintain at least a 44×44px hit-area on mobile viewports ($\le 640\text{px}$) via touch target expansion or sizing adjustments.
- **Tactile Feedback**:
- Introduce physical active states (`:active { transform: scale(0.96); }`) with quick easing transitions for crisp button responsiveness.

### 2. Component Accessibility & ARIA Standardization (`src/components/`)
- **Tabs (`src/components/Tabs.tsx`)**:
- Ensure each tab button has a unique `id={`tab-${pillId}-${i}`}` and `aria-controls={`tabpanel-${pillId}-${i}`}`.
- Wrap the content panel in `role="tabpanel"`, `id={`tabpanel-${pillId}-${active}`}`, and `aria-labelledby={`tab-${pillId}-${active}`}` with `tabIndex={0}`.
- **Accordion (`src/components/Accordion.tsx`)**:
- Connect each trigger button (`acc-head`) with `aria-controls={`acc-body-${id}-${i}`}` and `id={`acc-head-${id}-${i}`}`.
- Mark each accordion body with `role="region"`, `id={`acc-body-${id}-${i}`}`, and `aria-labelledby={`acc-head-${id}-${i}`}`.
- **Charts (`src/components/Charts.tsx`)**:
- Add `role="img"` and descriptive `aria-label` to `BarChart`, `LineChart`, and `DonutChart` so screen readers announce data trends and values rather than silently skipping SVG elements.
- **Table (`src/components/Table.tsx`)**:
- Link the caption using standard `<caption>` or `aria-describedby` semantics to comply with accessible table standards.

### 3. Agent Authoring Intelligence (`.bolt/skills/slides/SKILL.md`)
- **Enrich the Skill Guide with UI/UX Pro Max Intelligence**:
- Add **UI/UX Pro Max Design Principles**:
1. **WCAG Contrast Minimums (4.5:1 / 7:1)** for slide text and data visualizations.
2. **Typography Pairings & Hierarchy**: Curated pairings, body line height (1.5-1.75), line lengths (65-75ch max-width), and tabular figures.
3. **Touch Targets & Responsive Layout**: 44px minimum target sizes, mobile safe zones, prevention of horizontal overflow.
4. **SVG vs. Emoji Rule**: Strict prohibition of raw emoji glyphs for functional UI controls.
5. **Micro-interaction Guidelines**: Restrained 150-300ms transitions, easing curves, zero layout shifting on hover.
- Update **Definition of Done** in `SKILL.md` to include UI/UX Pro Max accessibility and contrast checks.

## Verification & Acceptance Criteria
- `npx tsc --noEmit` runs with 0 errors.
- `npm run build` runs with 0 errors.
- Mobile viewport styles maintain $\ge 44\text{px}$ touch target compliance.
- Keyboard navigation (Tab key) renders visible focus outlines across all interactive controls.
- WAI-ARIA tab, accordion, chart, and table semantics validate cleanly.
8 changes: 7 additions & 1 deletion src/components/Accordion.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState, type ReactNode } from 'react';
import { useId, useState, type ReactNode } from 'react';
import { AnimatePresence, motion, useReducedMotion } from 'framer-motion';

/* An expand/collapse accordion. Data-driven like Bento/StatGrid. Mono index,
Expand All @@ -18,6 +18,7 @@ export default function Accordion({
single?: boolean;
defaultOpen?: number | null;
}) {
const baseId = useId();
const reduce = useReducedMotion();
const [open, setOpen] = useState<number[]>(
defaultOpen == null ? [] : [defaultOpen]
Expand All @@ -35,9 +36,11 @@ export default function Accordion({
{items.map((it, i) => (
<div key={i} className={'acc-item mat' + (isOpen(i) ? ' open' : '')}>
<button
id={`acc-head-${baseId}-${i}`}
className="acc-head"
onClick={() => toggle(i)}
aria-expanded={isOpen(i)}
aria-controls={`acc-body-${baseId}-${i}`}
>
<span className="acc-index">{String(i + 1).padStart(2, '0')}</span>
<h3>{it.title}</h3>
Expand All @@ -46,6 +49,9 @@ export default function Accordion({
<AnimatePresence initial={false}>
{isOpen(i) && (
<motion.div
id={`acc-body-${baseId}-${i}`}
role="region"
aria-labelledby={`acc-head-${baseId}-${i}`}
className="acc-body"
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
Expand Down
15 changes: 13 additions & 2 deletions src/components/Charts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,15 @@ export function BarChart({
const { ref, inView } = useInView<HTMLDivElement>(0.3);
const animate = !isStatic && !reduce;
const max = Math.max(...data.map((d) => d.value)) || 1;
const chartSummary = `Bar chart showing ${data.map((d) => `${d.label}: ${d.value}`).join(', ')}`;
return (
<div className="ch ch-bars" ref={ref} style={{ height }}>
<div
className="ch ch-bars"
ref={ref}
style={{ height }}
role="img"
aria-label={chartSummary}
>
{data.map((d, i) => (
<div key={i} className="ch-col">
{showValues && (
Expand Down Expand Up @@ -89,8 +96,9 @@ export function LineChart({
.join(' ');
const area = `${line} L${w},${h} L0,${h} Z`;
const [ex, ey] = coords[coords.length - 1];
const chartSummary = `Line chart with ${points.length} data points ranging from ${min} to ${max}`;
return (
<div className="ch-wrap" ref={ref}>
<div className="ch-wrap" ref={ref} role="img" aria-label={chartSummary}>
<svg
className="ch-line"
viewBox={`0 0 ${w} ${h}`}
Expand Down Expand Up @@ -191,10 +199,13 @@ export function DonutChart({
return () => cancelAnimationFrame(raf);
}, [isStatic, inView, value, reduce]);

const chartSummary = label ? `${label}: ${value}%` : `${value}% donut chart`;
return (
<div
className="ch-donut"
style={{ '--ch-size': `${size}px` } as CSSProperties}
role="img"
aria-label={chartSummary}
>
<svg ref={ref} viewBox="0 0 140 140">
<circle
Expand Down
6 changes: 4 additions & 2 deletions src/components/Table.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { ReactNode } from 'react';
import { useId, type ReactNode } from 'react';
import { motion, useReducedMotion } from 'framer-motion';
import { useInView } from '../deck/useInView';

Expand Down Expand Up @@ -29,6 +29,7 @@ export default function Table({
highlightRow?: number;
caption?: string;
}) {
const capId = useId();
const { ref, inView } = useInView<HTMLDivElement>(0.25);
const reduce = useReducedMotion();
const align = (c: TableColumn) =>
Expand All @@ -40,7 +41,7 @@ export default function Table({
return (
<div ref={ref}>
<div className="dtable mat">
<table>
<table aria-describedby={caption ? capId : undefined}>
<thead>
<tr>
{columns.map((c, i) => (
Expand Down Expand Up @@ -85,6 +86,7 @@ export default function Table({
</div>
{caption && (
<div
id={capId}
className="foot dtable-caption"
style={{ maxWidth: 880, marginInline: 'auto' }}
>
Expand Down
10 changes: 9 additions & 1 deletion src/components/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ export default function Tabs({
{tabs.map((t, i) => (
<button
key={i}
id={`tab-${pillId}-${i}`}
className={'tab' + (i === active ? ' on' : '')}
onClick={() => setActive(i)}
role="tab"
aria-selected={i === active}
aria-controls={`tabpanel-${pillId}-${i}`}
tabIndex={i === active ? 0 : -1}
>
{i === active && (
Expand All @@ -60,7 +62,13 @@ export default function Tabs({
))}
</div>
</div>
<div className="tabs-panel">
<div
className="tabs-panel"
role="tabpanel"
id={`tabpanel-${pillId}-${active}`}
aria-labelledby={`tab-${pillId}-${active}`}
tabIndex={0}
>
<AnimatePresence mode="wait">
<motion.div
key={active}
Expand Down
33 changes: 32 additions & 1 deletion src/styles/base.css
Original file line number Diff line number Diff line change
Expand Up @@ -2053,11 +2053,14 @@ strong {
color: #e8e8ea;
font-size: 19px;
cursor: pointer;
transition: background 0.2s ease, color 0.2s ease, opacity 0.2s ease;
transition: background 0.2s ease, color 0.2s ease, opacity 0.2s ease, transform 0.1s var(--ease);
}
.noir-icon-btn:hover {
background: rgba(255, 255, 255, 0.12);
}
.noir-icon-btn:active:not(:disabled) {
transform: scale(0.94);
}
.noir-icon-btn:disabled {
opacity: 0.28;
cursor: default;
Expand All @@ -2069,6 +2072,18 @@ strong {
color: var(--accent-ink, #04140e);
background: var(--accent, linear-gradient(115deg, #c8f56e, #34dcd8));
}

/* ── UI/UX Pro Max: Keyboard focus-visible system & tactile micro-interactions ── */
:is(.noir-icon-btn, .tab, .acc-head, .noir-thumb, .ann-btn, .ann-color, .ann-size):focus-visible {
outline: none;
box-shadow: var(--focus-ring);
z-index: 2;
}

.tab:active,
.ann-btn:active:not(:disabled) {
transform: scale(0.96);
}
.noir-icon-btn.sm {
width: 30px;
height: 30px;
Expand Down Expand Up @@ -2744,10 +2759,26 @@ strong {
width: 34px;
height: 34px;
font-size: 17px;
position: relative;
}
.noir-icon-btn::after {
content: '';
position: absolute;
inset: -5px;
min-width: var(--touch-target-min, 44px);
min-height: var(--touch-target-min, 44px);
}
.noir-icon-btn.sm {
width: 28px;
height: 28px;
position: relative;
}
.noir-icon-btn.sm::after {
content: '';
position: absolute;
inset: -8px;
min-width: var(--touch-target-min, 44px);
min-height: var(--touch-target-min, 44px);
}
.noir-counter {
padding: 0 8px;
Expand Down
4 changes: 4 additions & 0 deletions src/styles/tokens.css
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@
--grain: 0.035;
--vignette: rgba(0, 0, 0, 0.22);

/* Focus & Accessibility (UI/UX Pro Max) */
--focus-ring: 0 0 0 2px var(--bg), 0 0 0 4px var(--primary);
--touch-target-min: 44px;

/* Responsive gutter — fluid side / vertical padding */
--gutter: clamp(28px, 6vw, 120px);
--gutter-y: clamp(40px, 8vh, 96px);
Expand Down