graph_3 Canvas

Build for facilitators.
Design with intent.

The shared language behind every Workshopr app. Tokens, components, and patterns for a platform that helps people run better workshops.

menu_book Learn chevron_right edit_note Plan chevron_right groups Facilitate chevron_right insights Reflect

Machine-Readable Single Source of Truth

This JSON file translates visual design tokens into a deterministic API contract for AI assistants. It ensures that every generated component adheres strictly to brand colors, component constraints, and architectural rules.

data_object workshopr-design-system.json
{
  "meta": {
    "name": "Workshopr Design System",
    "version": "3.1.0",
    "description": "Machine-readable mirror of the design tokens, component catalog, and rules. Hand-maintained, guarded against drift by check-drift.php. NOT the canonical intent source — see designSource/motionSource.",
    "entryPoint": "/design-system/START-HERE.md",
    "designSource": "/DESIGN.md",
    "motionSource": "/motion.md",
    "tokenSource": "/design-system/platform-tokens.css",
    "componentSource": "/includes/components/helpers.php",
    "styleGuide": "/design-system/workshopr-style-guide.md",
    "driftGuard": "/design-system/check-drift.php",
    "provenance": "Hand-maintained. Run `php design-system/check-drift.php` after edits; it fails if this mirror drifts from platform-tokens.css or helpers.php.",
    "lastUpdated": "2026-07-03",
    "architecture": "LEARN > PLAN > FACILITATE > REFLECT (naming-only phases; entire platform is red — see rules.phaseColors)"
  },
  "phases": {
    "learn": {
      "color": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FEE2E2",
      "veryLight": "#FEF2F2",
      "description": "Discovery, knowledge, content consumption",
      "apps": [
        "mainsite",
        "tips",
        "podcasts",
        "training"
      ]
    },
    "plan": {
      "color": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "description": "Building, creating, structuring workshops (naming-only phase — all red since July 2026, D1)",
      "apps": [
        "planner"
      ]
    },
    "facilitate": {
      "color": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "description": "Live sessions, real-time assistance (naming-only phase — all red since July 2026, D1)",
      "apps": [
        "intervention",
        "facilitator"
      ]
    },
    "reflect": {
      "color": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "description": "Growth, renewal, skill development (naming-only phase — all red since July 2026, D1)",
      "apps": [
        "coach"
      ]
    }
  },
  "apps": {
    "mainsite": {
      "phase": "learn",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FEE2E2",
      "veryLight": "#FEF2F2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)"
    },
    "tips": {
      "phase": "learn",
      "colorNote": "Unified to red family — retired legacy purple exception.",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)",
      "categories": {
        "1": {
          "color": "#4527A0",
          "name": "Fundamentals"
        },
        "2": {
          "color": "#5E35B1",
          "name": "Engagement"
        },
        "3": {
          "color": "#6930C3",
          "name": "Facilitation"
        },
        "4": {
          "color": "#7C4DFF",
          "name": "Difficult Moments"
        },
        "5": {
          "color": "#9575CD",
          "name": "Activities"
        },
        "6": {
          "color": "#B39DDB",
          "name": "Reflection"
        }
      }
    },
    "podcasts": {
      "phase": "learn",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)",
      "platforms": {
        "spotify": "#1DB954",
        "apple": "#FC3C44",
        "amazon": "#FF9900",
        "youtube": "#FF0000"
      }
    },
    "planner": {
      "phase": "plan",
      "colorNote": "Unified to red family — retired legacy blue exception.",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "accent": "#FEF2F2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)",
      "bodyClass": "planner-page",
      "scopeSelector": ".planner-page"
    },
    "coach": {
      "phase": "reflect",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)",
      "modes": {
        "prep": "#F87171",
        "facilitate": "#EF4444",
        "synthesize": "#DC2626",
        "debrief": "#B91C1C"
      },
      "ui": {
        "bg": "#FFFBF8",
        "surface": "#FFFFFF",
        "border": "#E8ECF1"
      }
    },
    "intervention": {
      "phase": "facilitate",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)",
      "colorNote": "Unified to red family — retired legacy purple exception.",
      "categories": {
        "1": {
          "color": "#7F1D1D",
          "name": "Energy & Engagement"
        },
        "2": {
          "color": "#991B1B",
          "name": "Conflict & Tension"
        },
        "3": {
          "color": "#B91C1C",
          "name": "Participation"
        },
        "4": {
          "color": "#C73E3E",
          "name": "Time & Pacing"
        },
        "5": {
          "color": "#E54D4D",
          "name": "Focus & Clarity"
        },
        "6": {
          "color": "#F87171",
          "name": "Connection & Trust"
        }
      }
    },
    "facilitator": {
      "phase": "facilitate",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "gradient": "linear-gradient(160deg, #1A0404 0%, #3B0A0A 40%, #7F1D1D 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)",
      "colorNote": "Unified to red family — retired legacy purple exception."
    },
    "dna": {
      "phase": "standalone",
      "phaseNote": "Unphased utility app — not part of the Learn/Plan/Facilitate/Reflect journey (see CLAUDE.md app table).",
      "colorNote": "Unified to red family — retired legacy green exception.",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)",
      "categories": {
        "1": {
          "color": "#7F1D1D",
          "name": "Strengths"
        },
        "2": {
          "color": "#B91C1C",
          "name": "Growth Areas"
        },
        "3": {
          "color": "#C73E3E",
          "name": "Style"
        },
        "4": {
          "color": "#E54D4D",
          "name": "History"
        },
        "5": {
          "color": "#FCA5A5",
          "name": "Goals"
        }
      }
    },
    "training": {
      "phase": "learn",
      "colorNote": "Training/Academy is a Learn-phase app and ships with Learn red (#E54D4D), matching DESIGN.md and the live academy CSS.",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)"
    },
    "companion": {
      "phase": "cross-phase",
      "colorNote": "iOS companion ships the same Learn Red family as the web platform (D1, Red Unification) — no separate mobile accent. Not CSS-driven: no --companion-primary token exists in platform-tokens.css because this app is a standalone React Native/Expo codebase, not a page rendered by this stylesheet. See app-companion.php for the mobile-specific palette (pageCream/stageCharcoal/etc.) sourced from workshopr-companion/src/theme/.",
      "primary": "#E54D4D",
      "dark": "#C73E3E",
      "light": "#FECACA",
      "veryLight": "#FEE2E2",
      "gradient": "linear-gradient(135deg, #E54D4D 0%, #C73E3E 100%)",
      "shadow": "0 4px 14px rgba(229, 77, 77, 0.25)",
      "platform": {
        "type": "native",
        "os": "iOS (React Native / Expo)",
        "route": "not a web route — separate app at workshopr-companion/, no platform.php page",
        "designSource": "workshopr-companion/DESIGN.md + workshopr-companion/src/theme/ (colors.ts, typography.ts, spacing.ts)"
      }
    }
  },
  "colors": {
    "semantic": {
      "success": {
        "base": "#22C55E",
        "dark": "#16A34A",
        "light": "#BBF7D0",
        "veryLight": "#DCFCE7"
      },
      "warning": {
        "base": "#F59E0B",
        "dark": "#D97706",
        "light": "#FDE68A",
        "veryLight": "#FEF3C7"
      },
      "error": {
        "base": "#EF4444",
        "dark": "#DC2626",
        "light": "#FECACA",
        "veryLight": "#FEE2E2"
      },
      "info": {
        "base": "#3B82F6",
        "dark": "#2563EB",
        "light": "#BFDBFE",
        "veryLight": "#DBEAFE"
      }
    },
    "ink": {
      "default": "#2C2416",
      "secondary": "#5C5242",
      "muted": "#78716C",
      "faint": "#A8A29E"
    },
    "backgrounds": {
      "page": "#FEF7F1",
      "pageAlt": "#FFFBF8",
      "surface": "#FFFFFF",
      "muted": "#F5F5F4",
      "subtle": "#FAFAF9",
      "header": "#F0EBE3"
    },
    "grays": {
      "comment": "Warm stone grays (Tailwind Stone palette)",
      "50": "#FAFAF9",
      "100": "#F5F5F4",
      "200": "#E7E5E4",
      "300": "#D6D3D1",
      "400": "#A8A29E",
      "500": "#78716C",
      "600": "#57534E",
      "700": "#44403C",
      "800": "#292524",
      "900": "#1C1917"
    },
    "navy": {
      "base": "#1A2744",
      "light": "#2E3D5C"
    },
    "text": {
      "dark": "#2C2416",
      "default": "#5C5242",
      "muted": "#78716C",
      "faint": "#A8A29E",
      "inverse": "#FFFFFF"
    }
  },
  "typography": {
    "fonts": {
      "heading": "'Fraunces', Georgia, serif",
      "body": "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif"
    },
    "scale": {
      "display": {
        "size": "2.875rem",
        "px": 46,
        "font": "heading",
        "usage": "Hero/display text"
      },
      "h1": {
        "size": "2rem",
        "px": 32,
        "font": "heading"
      },
      "h2": {
        "size": "1.5rem",
        "px": 24,
        "font": "heading"
      },
      "h3": {
        "size": "1.25rem",
        "px": 20,
        "font": "heading"
      },
      "h4": {
        "size": "1.125rem",
        "px": 18,
        "font": "body"
      },
      "body": {
        "size": "1rem",
        "px": 16,
        "font": "body"
      },
      "ui": {
        "size": "0.9375rem",
        "px": 15,
        "font": "body"
      },
      "small": {
        "size": "0.875rem",
        "px": 14,
        "font": "body"
      },
      "meta": {
        "size": "0.8125rem",
        "px": 13,
        "font": "body"
      },
      "caption": {
        "size": "0.75rem",
        "px": 12,
        "font": "body"
      },
      "micro": {
        "size": "0.6875rem",
        "px": 11,
        "font": "body"
      },
      "nano": {
        "size": "0.625rem",
        "px": 10,
        "font": "body"
      }
    },
    "weights": {
      "normal": 400,
      "medium": 500,
      "semibold": 600,
      "bold": 700
    },
    "lineHeights": {
      "tight": 1.2,
      "snug": 1.35,
      "normal": 1.5,
      "relaxed": 1.625
    },
    "letterSpacing": {
      "tight": "-0.02em",
      "normal": "0",
      "wide": "0.025em"
    }
  },
  "spacing": {
    "baseUnit": "4px",
    "scale": {
      "0.5": "2px",
      "1": "4px",
      "1.5": "6px",
      "2": "8px",
      "2.5": "10px",
      "3": "12px",
      "3.5": "14px",
      "4": "16px",
      "4.5": "18px",
      "5": "20px",
      "6": "24px",
      "8": "32px",
      "10": "40px",
      "12": "48px",
      "16": "64px"
    }
  },
  "layout": {
    "containers": {
      "xs": "480px",
      "sm": "640px",
      "md": "768px",
      "lg": "960px",
      "xl": "1120px",
      "2xl": "1280px"
    },
    "breakpoints": {
      "mobile": "480px",
      "mobileLandscape": "640px",
      "tablet": "768px",
      "tabletLandscape": "960px",
      "desktopSm": "1120px",
      "desktop": "1280px"
    }
  },
  "borders": {
    "radius": {
      "sm": "4px",
      "md": "6px",
      "lg": "8px",
      "xl": "12px",
      "2xl": "16px",
      "full": "9999px"
    }
  },
  "shadows": {
    "xs": "0 1px 2px rgba(0, 0, 0, 0.04)",
    "sm": "0 1px 3px rgba(0, 0, 0, 0.06)",
    "md": "0 4px 6px rgba(0, 0, 0, 0.07)",
    "lg": "0 10px 15px rgba(0, 0, 0, 0.08)",
    "xl": "0 20px 25px rgba(0, 0, 0, 0.10)"
  },
  "animations": {
    "durations": {
      "fast": "150ms",
      "base": "200ms",
      "slow": "300ms",
      "enter": "300ms",
      "exit": "200ms"
    },
    "easings": {
      "default": "ease",
      "in": "cubic-bezier(0.4, 0, 1, 0.2)",
      "out": "cubic-bezier(0, 0, 0.2, 1)",
      "inOut": "cubic-bezier(0.4, 0, 0.2, 1)",
      "spring": "cubic-bezier(0.22, 1, 0.36, 1)",
      "bounce": "cubic-bezier(0.34, 1.56, 0.64, 1)"
    },
    "transitions": {
      "fast": "150ms ease",
      "base": "200ms ease",
      "slow": "300ms ease"
    }
  },
  "icons": {
    "source": "Material Symbols Outlined",
    "sizes": {
      "xs": "14px",
      "sm": "16px",
      "md": "20px",
      "lg": "24px",
      "xl": "32px",
      "2xl": "48px"
    }
  },
  "zIndex": {
    "dropdown": 100,
    "sticky": 200,
    "overlay": 1000,
    "header": 1100,
    "modalBackdrop": 1100,
    "modal": 1200,
    "tooltip": 1300,
    "dialog": 10000,
    "dialogHigh": 10001,
    "dialogHighest": 10004
  },
  "focus": {
    "ringColor": "rgba(2, 132, 199, 0.5)",
    "ringOffset": "2px",
    "ringWidth": "2px"
  },
  "components": {
    "_comment": "All ws_* PHP helpers from /includes/components/helpers.php. Use these instead of writing raw HTML.",
    "_css": "/includes/components/components.css",
    "_helpers": "/includes/components/helpers.php",
    "_templates": "/includes/components/*.php",
    "ws_badge": {
      "function": "ws_badge(string $text, array $options = []): string",
      "description": "Inline label/tag for metadata display",
      "options": {
        "variant": {
          "type": "string",
          "values": [
            "default",
            "duration",
            "category",
            "status",
            "outline"
          ],
          "default": "default"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md",
            "lg"
          ],
          "default": "md"
        },
        "icon": {
          "type": "string",
          "description": "Material icon name"
        },
        "color": {
          "type": "string",
          "description": "Hex color for category variant"
        },
        "status": {
          "type": "string",
          "values": [
            "success",
            "warning",
            "error",
            "info"
          ],
          "description": "For status variant"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      },
      "convenience": {
        "ws_duration": "ws_duration(string $duration, bool $withIcon = true) — badge with variant=duration, icon=schedule, size=sm",
        "ws_category": "ws_category(string $category, ?string $color = null) — badge with variant=category",
        "ws_status": "ws_status(string $text, string $status = 'info') — badge with variant=status"
      }
    },
    "ws_duration": {
      "function": "ws_duration(string $duration, bool $withIcon = true): string",
      "description": "Convenience wrapper of ws_badge (variant=duration, icon=schedule, size=sm). Use for durations like \"45 min\".",
      "convenienceOf": "ws_badge"
    },
    "ws_category": {
      "function": "ws_category(string $category, ?string $color = null): string",
      "description": "Convenience wrapper of ws_badge (variant=category).",
      "convenienceOf": "ws_badge"
    },
    "ws_status": {
      "function": "ws_status(string $text, string $status = 'info'): string",
      "description": "Convenience wrapper of ws_badge (variant=status). Status: success|warning|error|info.",
      "convenienceOf": "ws_badge"
    },
    "ws_pill": {
      "function": "ws_pill(string $text, array $options = []): string",
      "description": "Read-only tag / display chip for metadata display",
      "options": {
        "variant": {
          "type": "string",
          "values": [
            "solid",
            "ghost",
            "tint"
          ],
          "default": "tint"
        },
        "color": {
          "type": "string",
          "values": [
            "learn",
            "plan",
            "facilitate",
            "reflect",
            "neutral"
          ],
          "default": "neutral"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md"
          ],
          "default": "md"
        },
        "icon": {
          "type": "string",
          "description": "Material Symbols leading icon name"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_toggle": {
      "function": "ws_toggle(string $name, array $options = []): string",
      "description": "Interactive sliding toggle switch for on/off states",
      "options": {
        "checked": {
          "type": "bool",
          "default": false
        },
        "label": {
          "type": "string",
          "description": "Text label shown next to the switch"
        },
        "description": {
          "type": "string",
          "description": "Supporting description text shown below the label"
        },
        "disabled": {
          "type": "bool",
          "default": false
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_cta_section": {
      "function": "ws_cta_section(string $title, array $options = []): string",
      "description": "Full-width bottom-of-page marketing conversion block",
      "options": {
        "subtitle": {
          "type": "string",
          "description": "Secondary descriptive paragraph body"
        },
        "eyebrow": {
          "type": "string",
          "description": "Small label shown above the main title"
        },
        "primaryBtn": {
          "type": "array",
          "description": "Options array for primary action button"
        },
        "secondaryBtn": {
          "type": "array",
          "description": "Options array for secondary action button"
        },
        "color": {
          "type": "string",
          "values": [
            "learn",
            "plan",
            "facilitate",
            "reflect",
            "neutral"
          ],
          "default": "learn"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_how_steps": {
      "function": "ws_how_steps(array $steps, array $options = []): string",
      "description": "Numbered 'How It Works' process step card grid",
      "options": {
        "color": {
          "type": "string",
          "values": [
            "learn",
            "plan",
            "facilitate",
            "reflect",
            "neutral"
          ],
          "default": "learn"
        },
        "columns": {
          "type": "int",
          "values": [
            2,
            3,
            4
          ],
          "default": 3
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_filter_bar": {
      "function": "ws_filter_bar(array $filters, array $options = []): string",
      "description": "Composite listing search input and filter selects row",
      "options": {
        "search": {
          "type": "array",
          "description": "Optional search input configuration"
        },
        "activeChips": {
          "type": "array",
          "description": "Active clearable filter chips display"
        },
        "color": {
          "type": "string",
          "values": [
            "learn",
            "plan",
            "facilitate",
            "reflect",
            "neutral"
          ],
          "default": "learn"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_button": {
      "function": "ws_button(string $text, array $options = []): string",
      "description": "Primary interactive element for actions",
      "options": {
        "variant": {
          "type": "string",
          "values": [
            "primary",
            "secondary",
            "ghost",
            "danger",
            "outline"
          ],
          "default": "primary"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md",
            "lg"
          ],
          "default": "md"
        },
        "icon": {
          "type": "string",
          "description": "Material icon name"
        },
        "iconPosition": {
          "type": "string",
          "values": [
            "left",
            "right"
          ],
          "default": "left"
        },
        "href": {
          "type": "string",
          "description": "Makes button an <a> tag"
        },
        "type": {
          "type": "string",
          "values": [
            "button",
            "submit",
            "reset"
          ],
          "default": "button"
        },
        "disabled": {
          "type": "bool",
          "default": false
        },
        "loading": {
          "type": "bool",
          "default": false
        },
        "fullWidth": {
          "type": "bool",
          "default": false
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array",
          "description": "Key-value pairs for additional HTML attributes"
        }
      },
      "convenience": {
        "ws_icon_button": "ws_icon_button(string $icon, array $options = []) — icon-only button with variant=ghost, size=sm"
      }
    },
    "ws_icon_button": {
      "function": "ws_icon_button(string $icon, array $options = []): string",
      "description": "Convenience wrapper of ws_button: icon-only button with variant=ghost, size=sm.",
      "convenienceOf": "ws_button"
    },
    "ws_input": {
      "function": "ws_input(string $name, array $options = []): string",
      "description": "Form text input field with label, validation, and helper text",
      "options": {
        "type": {
          "type": "string",
          "values": [
            "text",
            "email",
            "password",
            "number",
            "url",
            "tel",
            "search",
            "date",
            "time"
          ],
          "default": "text"
        },
        "label": {
          "type": "string"
        },
        "placeholder": {
          "type": "string"
        },
        "value": {
          "type": "string"
        },
        "required": {
          "type": "bool",
          "default": false
        },
        "disabled": {
          "type": "bool",
          "default": false
        },
        "error": {
          "type": "string",
          "description": "Error message (activates error state)"
        },
        "helper": {
          "type": "string",
          "description": "Helper text below field"
        },
        "icon": {
          "type": "string",
          "description": "Material icon name"
        },
        "iconPosition": {
          "type": "string",
          "values": [
            "left",
            "right"
          ],
          "default": "left"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md",
            "lg"
          ],
          "default": "md"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_select": {
      "function": "ws_select(string $name, array $selectOptions, array $options = []): string",
      "description": "Dropdown select field",
      "params": {
        "selectOptions": "Associative array of value => label pairs"
      },
      "options": {
        "label": {
          "type": "string"
        },
        "value": {
          "type": "string",
          "description": "Pre-selected value"
        },
        "placeholder": {
          "type": "string"
        },
        "required": {
          "type": "bool",
          "default": false
        },
        "disabled": {
          "type": "bool",
          "default": false
        },
        "error": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_card": {
      "function": "ws_card(string $content, array $options = []): string",
      "description": "Content container with optional link, hover, and elevation",
      "alternatives": {
        "ws_card_start / ws_card_end": "For wrapping arbitrary PHP/HTML content between card open/close tags (see their own top-level entries)"
      },
      "options": {
        "variant": {
          "type": "string",
          "values": [
            "content",
            "interactive",
            "flat"
          ],
          "default": "content"
        },
        "hoverable": {
          "type": "bool",
          "default": false
        },
        "elevated": {
          "type": "bool",
          "default": false
        },
        "bordered": {
          "type": "bool",
          "default": false
        },
        "tag": {
          "type": "string",
          "default": "div",
          "description": "HTML tag (auto-set to 'a' if href provided)"
        },
        "href": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_card_start": {
      "function": "ws_card_start(array $options = []): void",
      "description": "Opens a card wrapper so you can put arbitrary PHP/HTML between it and ws_card_end. Same options as ws_card.",
      "pairsWith": "ws_card_end"
    },
    "ws_card_end": {
      "function": "ws_card_end(): void",
      "description": "Closes a ws_card_start wrapper.",
      "pairsWith": "ws_card_start"
    },
    "ws_modal": {
      "function": "ws_modal(string $content, array $options = []): string",
      "description": "Dialog overlay for forms, confirmations, and detail views",
      "alternatives": {
        "ws_modal_start / ws_modal_end": "For wrapping arbitrary PHP/HTML content between modal open/close tags"
      },
      "options": {
        "id": {
          "type": "string",
          "required": true
        },
        "title": {
          "type": "string"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md",
            "lg",
            "xl",
            "full"
          ],
          "default": "md"
        },
        "variant": {
          "type": "string",
          "values": [
            "default",
            "danger",
            "branded"
          ],
          "default": "default"
        },
        "closable": {
          "type": "bool",
          "default": true
        },
        "backdrop": {
          "type": "bool",
          "default": true
        },
        "headerColor": {
          "type": "string",
          "description": "CSS color for header background"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      },
      "related": {
        "ws_modal_footer": "ws_modal_footer(array $options) — standardized button layout for modal footers. Options: cancel (string), primary (string|array), left (string)"
      }
    },
    "ws_modal_start": {
      "function": "ws_modal_start(array $options = []): void",
      "description": "Opens a modal wrapper so you can put arbitrary PHP/HTML between it and ws_modal_end. Same options as ws_modal.",
      "pairsWith": "ws_modal_end"
    },
    "ws_modal_end": {
      "function": "ws_modal_end(): void",
      "description": "Closes a ws_modal_start wrapper.",
      "pairsWith": "ws_modal_start"
    },
    "ws_modal_footer": {
      "function": "ws_modal_footer(array $options): string",
      "description": "Standardized button layout for modal footers.",
      "options": {
        "cancel": {
          "type": "string",
          "description": "Cancel button label"
        },
        "primary": {
          "type": "string",
          "description": "Primary button label, or [label, options]"
        },
        "left": {
          "type": "string",
          "description": "Optional left-aligned content/button"
        }
      }
    },
    "ws_tabs": {
      "function": "ws_tabs(array $items, array $options = []): string",
      "description": "Tab navigation for switching content panels",
      "params": {
        "items": "Array of {id, label, icon?, count?, active?, disabled?, gated?}"
      },
      "options": {
        "id": {
          "type": "string"
        },
        "variant": {
          "type": "string",
          "values": [
            "default",
            "pills",
            "underline"
          ],
          "default": "default"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md"
          ],
          "default": "md"
        },
        "scrollable": {
          "type": "bool",
          "default": false
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_accordion": {
      "function": "ws_accordion(array $items, array $options = []): string",
      "description": "Expandable content sections",
      "params": {
        "items": "Array of {title, content, open?, icon?}"
      },
      "options": {
        "id": {
          "type": "string"
        },
        "multiple": {
          "type": "bool",
          "default": false,
          "description": "Allow multiple panels open at once"
        },
        "variant": {
          "type": "string",
          "values": [
            "default",
            "bordered",
            "flush"
          ],
          "default": "default"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md"
          ],
          "default": "md"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_alert": {
      "function": "ws_alert(string $message, array $options = []): string",
      "description": "Contextual feedback message",
      "options": {
        "variant": {
          "type": "string",
          "values": [
            "info",
            "success",
            "warning",
            "error"
          ],
          "default": "info"
        },
        "title": {
          "type": "string"
        },
        "icon": {
          "type": "string",
          "description": "Material icon name"
        },
        "dismissible": {
          "type": "bool",
          "default": false
        },
        "bordered": {
          "type": "bool",
          "default": false
        },
        "filled": {
          "type": "bool",
          "default": false
        },
        "actions": {
          "type": "string",
          "description": "Action button HTML"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_progress": {
      "function": "ws_progress(int|float $value, array $options = []): string",
      "description": "Progress bar with optional label",
      "options": {
        "variant": {
          "type": "string",
          "values": [
            "default",
            "success",
            "warning",
            "error",
            "brand"
          ],
          "default": "default"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md",
            "lg"
          ],
          "default": "md"
        },
        "label": {
          "type": "string"
        },
        "showValue": {
          "type": "bool",
          "default": false
        },
        "striped": {
          "type": "bool",
          "default": false
        },
        "animated": {
          "type": "bool",
          "default": false
        },
        "indeterminate": {
          "type": "bool",
          "default": false
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_avatar": {
      "function": "ws_avatar(string $name, array $options = []): string",
      "description": "User avatar with initials fallback",
      "options": {
        "image": {
          "type": "string",
          "description": "Image URL"
        },
        "size": {
          "type": "string",
          "values": [
            "xs",
            "sm",
            "md",
            "lg",
            "xl"
          ],
          "default": "md"
        },
        "color": {
          "type": "string",
          "description": "Background color for initials"
        },
        "ring": {
          "type": "bool",
          "default": false
        },
        "status": {
          "type": "string",
          "values": [
            "online",
            "offline",
            "busy"
          ],
          "description": "Status indicator dot"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_hero": {
      "function": "ws_hero(string $title, string $accent = '', array $options = []): string",
      "description": "Full-width hero section with badge, title, subtitle, buttons, stats, and optional image",
      "options": {
        "badge": {
          "type": "string",
          "description": "Badge text above title"
        },
        "badgeIcon": {
          "type": "string",
          "description": "Material icon in badge"
        },
        "subtitle": {
          "type": "string",
          "description": "Description paragraph below title"
        },
        "buttons": {
          "type": "array",
          "description": "Array of button HTML strings (use ws_button())"
        },
        "stats": {
          "type": "array",
          "description": "Array of {value, label} items"
        },
        "statColor": {
          "type": "string",
          "default": "#E54D4D"
        },
        "image": {
          "type": "string",
          "description": "Image URL for hero visual"
        },
        "imageAlt": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_banner": {
      "function": "ws_banner(string $message, array $options = []): string",
      "description": "Full-width announcement/promotional banner",
      "options": {
        "variant": {
          "type": "string",
          "values": [
            "default",
            "brand",
            "success",
            "warning",
            "dark"
          ],
          "default": "default"
        },
        "icon": {
          "type": "string"
        },
        "action": {
          "type": "string",
          "description": "Action link text"
        },
        "href": {
          "type": "string"
        },
        "onclick": {
          "type": "string",
          "description": "JS onclick handler"
        },
        "dismissible": {
          "type": "bool",
          "default": false
        },
        "centered": {
          "type": "bool",
          "default": true
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_section_header": {
      "function": "ws_section_header(string $title, array $options = []): string",
      "description": "Centered section heading with optional subtitle, description, and label",
      "options": {
        "subtitle": {
          "type": "string",
          "description": "Secondary heading (italic, brand color)"
        },
        "description": {
          "type": "string",
          "description": "Paragraph below heading"
        },
        "tag": {
          "type": "string",
          "values": [
            "h2",
            "h3"
          ],
          "default": "h2"
        },
        "align": {
          "type": "string",
          "values": [
            "left",
            "center"
          ],
          "default": "center"
        },
        "label": {
          "type": "string",
          "description": "Small label above heading"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_feature_card": {
      "function": "ws_feature_card(string $title, string $description, array $options = []): string",
      "description": "Icon + title + description card for feature/benefit lists",
      "options": {
        "icon": {
          "type": "string",
          "description": "Material icon name (rendered via icon() helper)"
        },
        "svg": {
          "type": "string",
          "description": "Raw SVG string (alternative to icon)"
        },
        "variant": {
          "type": "string",
          "values": [
            "default",
            "compact",
            "coming-soon"
          ],
          "default": "default"
        },
        "href": {
          "type": "string",
          "description": "Makes card a link"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_level_up_grid": {
      "function": "ws_level_up_grid(array $options = []): string",
      "description": "Cross-promotional grid of all Workshopr apps grouped by facilitation phase (Learn, Plan, Facilitate, Reflect). Used on landing pages to drive users across the ecosystem. Phase colors applied automatically per card.",
      "options": {
        "current_app": {
          "type": "string",
          "description": "App key to exclude from the grid (e.g., 'intervention', 'planner', 'tips')"
        },
        "eyebrow": {
          "type": "string",
          "default": "More from Workshopr",
          "description": "Small label above the section title"
        },
        "title": {
          "type": "string",
          "default": "Level Up Your Facilitation",
          "description": "Section heading"
        },
        "apps": {
          "type": "array",
          "description": "Override the built-in app catalog"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      },
      "app_catalog": {
        "learn": [
          "library",
          "academy",
          "podcasts"
        ],
        "plan": [
          "planner",
          "hybrid"
        ],
        "facilitate": [
          "facilitator",
          "sidekick",
          "intervention",
          "tips"
        ],
        "reflect": [
          "dna",
          "coach",
          "roi"
        ]
      }
    },
    "ws_stat": {
      "function": "ws_stat(string $value, string $label, array $options = []): string",
      "description": "Single stat display with value and label",
      "options": {
        "color": {
          "type": "string",
          "description": "Value color (hex or CSS var)"
        },
        "icon": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      },
      "related": {
        "ws_stats": "ws_stats(array $stats, array $options = []) — group of stats with dividers. Stats: [{value, label, color?}]"
      }
    },
    "ws_stats": {
      "function": "ws_stats(array $stats, array $options = []): string",
      "description": "Group of ws_stat items with dividers.",
      "params": {
        "stats": "Array of {value, label, color?}"
      }
    },
    "ws_empty_state": {
      "function": "ws_empty_state(string $title, array $options = []): string",
      "description": "Placeholder for empty content areas",
      "options": {
        "description": {
          "type": "string"
        },
        "icon": {
          "type": "string",
          "default": "inbox"
        },
        "action": {
          "type": "string",
          "description": "Action button text"
        },
        "actionHref": {
          "type": "string"
        },
        "actionVariant": {
          "type": "string",
          "default": "primary"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_breadcrumb": {
      "function": "ws_breadcrumb(array $items, array $options = []): string",
      "description": "Navigation breadcrumb trail",
      "params": {
        "items": "Array of {label, href?}. Last item should have no href."
      },
      "options": {
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "separator": {
          "type": "string",
          "default": "/"
        }
      }
    },
    "ws_checklist": {
      "function": "ws_checklist(array $items, array $options = []): string",
      "description": "List with check icons for feature lists and requirements",
      "params": {
        "items": "Array of checklist item strings"
      },
      "options": {
        "icon": {
          "type": "string",
          "default": "check_circle"
        },
        "variant": {
          "type": "string",
          "values": [
            "default",
            "compact"
          ],
          "default": "default"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_task_checklist": {
      "function": "ws_task_checklist(array $items, array $options = []): string",
      "description": "Progress-tracked list of activation/setup tasks; each item links to where the task happens, with done state computed by the caller from real data",
      "params": {
        "items": "Array of items: label (required), done (bool), href (undone items render as links), description (hidden once done), phase (uppercase eyebrow), key (data-task attr for JS)"
      },
      "options": {
        "title": {
          "type": "string",
          "default": "Getting started"
        },
        "subtitle": {
          "type": "string"
        },
        "progress": {
          "type": "bool",
          "default": true,
          "description": "Show 'x of N done' count + progress bar"
        },
        "dismissible": {
          "type": "bool",
          "default": false,
          "description": "Render dismiss button (data-checklist-dismiss); consumer JS handles it"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_skeleton": {
      "function": "ws_skeleton(array $options = []): string",
      "description": "Loading placeholder skeleton. The 'workshop' variant mirrors the saved-workshop card used on the Planner, Facilitator and Synthesize homes, so the grid holds its shape while the plan list loads.",
      "options": {
        "variant": {
          "type": "string",
          "values": [
            "text",
            "circle",
            "rect",
            "card",
            "workshop",
            "avatar",
            "image",
            "button",
            "input"
          ],
          "default": "text"
        },
        "width": {
          "type": "string"
        },
        "height": {
          "type": "string"
        },
        "lines": {
          "type": "int",
          "default": 1,
          "description": "Number of text lines"
        },
        "count": {
          "type": "int",
          "default": 1,
          "description": "Number of sibling cards (workshop variant) — drops straight into an app's own grid"
        },
        "grid": {
          "type": "bool",
          "default": false,
          "description": "Wrap workshop cards in the component's own responsive grid (id/attrs then land on the wrapper)"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_toast_container": {
      "function": "ws_toast_container(): string",
      "description": "Toast notification container (call once in page footer). Toasts triggered via JS: wsToast('message', 'success')"
    },
    "ws_tooltip": {
      "function": "ws_tooltip(string $text, array $options = []): string",
      "description": "Tooltip attributes to add to an element",
      "options": {
        "position": {
          "type": "string",
          "values": [
            "top",
            "bottom",
            "left",
            "right"
          ],
          "default": "top"
        },
        "variant": {
          "type": "string",
          "values": [
            "default",
            "dark"
          ],
          "default": "default"
        },
        "arrow": {
          "type": "bool",
          "default": true
        }
      }
    },
    "ws_form_row": {
      "function": "ws_form_row(string $content, array $options = []): string",
      "description": "Form field wrapper with label, helper text, and error state",
      "options": {
        "label": {
          "type": "string"
        },
        "required": {
          "type": "bool",
          "default": false
        },
        "helper": {
          "type": "string"
        },
        "error": {
          "type": "string"
        },
        "inline": {
          "type": "bool",
          "default": false,
          "description": "Horizontal label + field layout"
        },
        "columns": {
          "type": "int",
          "description": "Grid columns this row spans"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_prompt_select": {
      "function": "ws_prompt_select(string $label, string $name, array $selectOptions, array $opts = []): string",
      "description": "Inline prompt + select dropdown (e.g. 'I need help with [Goal...]')",
      "options": {
        "value": {
          "type": "string"
        },
        "onchange": {
          "type": "string"
        },
        "variant": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_chip_input": {
      "function": "ws_chip_input(string $name, array $options = []): string",
      "description": "Email/tag input with add/remove chip functionality",
      "options": {
        "label": {
          "type": "string"
        },
        "placeholder": {
          "type": "string"
        },
        "type": {
          "type": "string",
          "default": "email"
        },
        "addIcon": {
          "type": "string"
        },
        "addText": {
          "type": "string"
        },
        "removeIcon": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_radio_card_group": {
      "function": "ws_radio_card_group(string $name, array $items, array $options = []): string",
      "description": "Card-style radio selector grid",
      "params": {
        "items": "Array of {value, label, icon?, description?, checked?, disabled?}"
      },
      "options": {
        "label": {
          "type": "string"
        },
        "columns": {
          "type": "int"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_checkbox_card_group": {
      "function": "ws_checkbox_card_group(array $items, array $options = []): string",
      "description": "Toggle switch list with icons",
      "params": {
        "items": "Array of {id, title, description?, icon?, iconColor?, checked?, highlight?}"
      },
      "options": {
        "label": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_listing_card": {
      "function": "ws_listing_card(array $card): string",
      "description": "Content listing card (exercise, workshop, etc.) from prepareCardData()"
    },
    "ws_suggestion_card": {
      "function": "ws_suggestion_card(array $options): string",
      "description": "Activity recommendation card with add button",
      "options": {
        "id": {
          "type": "string"
        },
        "name": {
          "type": "string"
        },
        "type": {
          "type": "string"
        },
        "duration": {
          "type": "string"
        },
        "category": {
          "type": "string"
        },
        "reason": {
          "type": "string"
        },
        "icon": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_activity_badge": {
      "function": "ws_activity_badge(string $type, array $options = []): string",
      "description": "Activity type badge (exercise/icebreaker/break/custom)",
      "params": {
        "type": "exercise | icebreaker | break | custom"
      },
      "options": {
        "text": {
          "type": "string"
        },
        "size": {
          "type": "string",
          "values": [
            "sm",
            "md"
          ],
          "default": "md"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_energy_arc_card": {
      "function": "ws_energy_arc_card(array $options = []): string",
      "description": "Session retrospective card for the Coach/Facilitator apps. Renders an eyebrow header (label + progress + status), a score with interpretive tinted-pill badge, a sparkline chart with time-axis labels, and a 2x2 grid of fixed-width bar-fill metrics. Light (canonical) and dark themes. Max width 340px.",
      "options": {
        "showHeader": {
          "type": "bool",
          "default": true,
          "description": "Set false when hosting the card inside a section that provides its own title (e.g. ws_planner_inspector_rail)."
        },
        "eyebrow": {
          "type": "string",
          "default": "Energy Arc"
        },
        "timeUsed": {
          "type": "string",
          "description": "Muted top-middle text, e.g. '8 of 8h used'"
        },
        "status": {
          "type": "array",
          "shape": {
            "text": "string",
            "type": "success|warning|danger|info|neutral"
          }
        },
        "score": {
          "type": "int",
          "range": "0..scoreMax"
        },
        "scoreMax": {
          "type": "int",
          "default": 100
        },
        "scoreBadge": {
          "type": "array",
          "shape": {
            "text": "string",
            "type": "success|warning|danger|info|neutral",
            "variant": "pill|note (default: pill)"
          }
        },
        "chartData": {
          "type": "array",
          "shape": "[{time: string, value: 0..100}]"
        },
        "timeLabels": {
          "type": "array",
          "description": "x-axis tick labels rendered evenly below the chart"
        },
        "metrics": {
          "type": "array",
          "shape": "[{label, value, fill 0..100, barColor?}]",
          "max": 4
        },
        "theme": {
          "type": "string",
          "values": [
            "light",
            "dark"
          ],
          "default": "light"
        },
        "accent": {
          "type": "string",
          "description": "Override accent color"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_planner_activity_inspector": {
      "function": "ws_planner_activity_inspector(array $options = []): string",
      "description": "Right-column rail in the Workshop Planner — selected-activity mode. Sibling of ws_planner_inspector_rail; shares the visual shell but surfaces activity-specific sections (identity, overview, energy impact, logistics, facilitator notes, when-it-runs, and actions). Render this when the user has an agenda item selected; render ws_planner_inspector_rail otherwise.",
      "options": {
        "type": {
          "type": "string",
          "values": [
            "exercise",
            "icebreaker",
            "break",
            "custom"
          ],
          "default": "exercise",
          "description": "Drives the left-border stripe, energy-bar fill, net-badge tint, bullet color, note rule, and button hover."
        },
        "duration": {
          "type": "string",
          "description": "Composed into the eyebrow, e.g. '60 min'"
        },
        "groupSize": {
          "type": "string",
          "description": "Composed into the eyebrow, e.g. '6–12 ppl'"
        },
        "title": {
          "type": "string",
          "description": "Activity title (required)"
        },
        "tags": {
          "type": "array",
          "description": "Small tag strings rendered under the title in mono uppercase"
        },
        "closable": {
          "type": "bool",
          "default": true,
          "description": "Show the × close control"
        },
        "overview": {
          "type": "string",
          "description": "Description paragraph in the display/serif font. Doubles as the Overview tab body when instructions are provided."
        },
        "instructions": {
          "type": "array|string|null",
          "description": "Step-by-step facilitator instructions. Array of strings renders as a numbered list; string is rendered as HTML (caller owns markup). When present, the Overview section becomes a tabbed panel: Overview · Instructions."
        },
        "activeTab": {
          "type": "string",
          "values": [
            "overview",
            "instructions"
          ],
          "default": "overview",
          "description": "Which tab renders active on first paint."
        },
        "energy": {
          "type": "array|null",
          "shape": {
            "net": "string (e.g. '+16')",
            "dimensions": "[{label, delta, fill 0..100}]"
          }
        },
        "logistics": {
          "type": "array",
          "description": "String bullets rendered in a 2-col mono grid"
        },
        "notes": {
          "type": "array|null",
          "shape": {
            "body": "string",
            "author": "string",
            "date": "string",
            "canAdd": "bool"
          }
        },
        "context": {
          "type": "array|null",
          "shape": {
            "time": "string",
            "description": "string"
          }
        },
        "actions": {
          "type": "array",
          "description": "Primary action row: [{label, icon?, action, href?}]. Grid auto-divides into equal columns."
        },
        "destructive": {
          "type": "array|null",
          "description": "Single destructive action, rendered full-width below the primary row."
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_planner_inspector_rail": {
      "function": "ws_planner_inspector_rail(array $options = []): string",
      "description": "Right-column inspector sidebar inside the Workshop Planner — default mode. Three opinionated sections: Energy (wraps ws_energy_arc_card), Coach suggests (list of icon+eyebrow+line+meta items), and Facilitators (avatar stack + presence blurb). Each section is optional. The Energy section auto-sets showHeader=false on the wrapped card and defaults the score-badge variant to 'note'. Render ws_planner_activity_inspector instead when the user has an agenda item selected.",
      "options": {
        "energy": {
          "type": "array|null",
          "shape": {
            "title": "string",
            "meta": "string",
            "card": "ws_energy_arc_card options"
          }
        },
        "coach": {
          "type": "array|null",
          "shape": {
            "title": "string",
            "meta": "string",
            "items": "[{icon, pre, line (HTML allowed for <em>), meta, action, href?}]"
          }
        },
        "facilitators": {
          "type": "array|null",
          "shape": {
            "title": "string",
            "meta": "string",
            "avatars": "[{initials, bg}]",
            "presence": "{name, status}"
          }
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_planner_library_rail": {
      "function": "ws_planner_library_rail(array $options = []): string",
      "description": "Left-column sidebar inside the Workshop Planner. Composes search, an optional top-level categories nav, one or more stacked sections (each optionally collapsible with an icon, and either a flat item list, an onboarding 'instructions' block, or a filter + category-buttons grid that drills into items via app-level JS), and an optional footer CTA. Every activity item is rendered via ws_planner_card (size=sm). Designed for v1 planner parity — see /planner/app2.php for the full adoption.",
      "options": {
        "search": {
          "type": "array|null",
          "shape": {
            "placeholder": "string",
            "kbd": "string",
            "name": "string",
            "value": "string"
          }
        },
        "categories": {
          "type": "array|null",
          "shape": {
            "title": "string",
            "count": "string",
            "items": "[{label,color,count?,active?,href?,id?}]"
          }
        },
        "sections": {
          "type": "array",
          "shape": "[{title, meta?, icon?, kind? ('items'|'instructions'), collapsible?, defaultOpen?, filter? {name,options [{value,label,selected?}]}, categoryButtons? [{id,name,icon?,count?,type?}], items: [ws_planner_card options | {icon,body} for instructions], showMore?, id?}]"
        },
        "footer": {
          "type": "array|null",
          "shape": {
            "label": "string",
            "button": {
              "text": "string",
              "icon": "string",
              "id": "string",
              "action?": "string",
              "href?": "string"
            }
          }
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_planner_card": {
      "function": "ws_planner_card(string $title, array $options = []): string",
      "description": "Agenda/timeline card used in the Workshop Planner (exercise/icebreaker/break/custom). Available in lg (timeline) and sm (palette) sizes with default/selected/disabled states. SM cards surface a swap icon on hover (and on focus-within / touch devices); disabled SM cards never reveal it. SM accepts an optional meta subtitle (e.g. '6-12 ppl') rendered under the title.",
      "params": {
        "title": "string — card title"
      },
      "options": {
        "type": {
          "type": "string",
          "values": [
            "exercise",
            "icebreaker",
            "break",
            "custom"
          ],
          "default": "exercise"
        },
        "duration": {
          "type": "string|int",
          "description": "e.g. '60m' or integer minutes"
        },
        "size": {
          "type": "string",
          "values": [
            "lg",
            "sm"
          ],
          "default": "lg"
        },
        "state": {
          "type": "string",
          "values": [
            "default",
            "selected",
            "disabled"
          ],
          "default": "default"
        },
        "draggable": {
          "type": "bool",
          "default": false,
          "description": "Show drag handle (lg only)"
        },
        "actions": {
          "type": "array",
          "description": "Action keys: swap, delete, edit (lg only)"
        },
        "meta": {
          "type": "string",
          "description": "Secondary line (sm only), e.g. '6-12 ppl'"
        },
        "href": {
          "type": "string"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_results_overlay": {
      "function": "ws_results_overlay(array $options): string",
      "description": "Operation outcome display panel",
      "options": {
        "id": {
          "type": "string"
        },
        "variant": {
          "type": "string"
        },
        "title": {
          "type": "string"
        },
        "icon": {
          "type": "string"
        },
        "buttonText": {
          "type": "string"
        },
        "buttonId": {
          "type": "string"
        },
        "listId": {
          "type": "string"
        },
        "class": {
          "type": "string"
        }
      }
    },
    "ws_audio_recorder": {
      "function": "ws_audio_recorder(array $options = []): string",
      "description": "Voice note capture via MediaRecorder API",
      "options": {
        "id": {
          "type": "string"
        },
        "entity_type": {
          "type": "string"
        },
        "entity_id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_audio_player": {
      "function": "ws_audio_player(string $src, array $options = []): string",
      "description": "Voice note playback component",
      "options": {
        "duration": {
          "type": "string"
        },
        "label": {
          "type": "string"
        },
        "note_id": {
          "type": "string"
        },
        "deletable": {
          "type": "bool",
          "default": false
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    },
    "ws_app_bar": {
      "function": "ws_app_bar(array $opts = []): string",
      "description": "Document bar every app shell wears under ws_header. One anatomy across Planner, Facilitator and Synthesize: app label + document title, contextual controls, save state, actions. The component owns height, gradient, title typography and action geometry; apps pass only their own markup through the slots.",
      "options": {
        "id": {
          "type": "string",
          "description": "Bar element ID"
        },
        "class": {
          "type": "string",
          "description": "Additional CSS classes on the bar (app hooks)"
        },
        "ariaLabel": {
          "type": "string",
          "default": "App toolbar",
          "description": "Landmark label"
        },
        "app": {
          "type": "array",
          "description": "App identity chip: label, badge, id, hidden"
        },
        "title": {
          "type": "array",
          "description": "Document title: mode (static|input|editable), text, id, inputId, editId, placeholder, maxlength, ariaLabel, hidden"
        },
        "center": {
          "type": "string",
          "description": "Raw HTML contextual controls; rendered only if non-empty"
        },
        "centerId": {
          "type": "string"
        },
        "centerClass": {
          "type": "string"
        },
        "centerHidden": {
          "type": "boolean"
        },
        "saveState": {
          "type": "array",
          "description": "Save-state pill: id, text, state, hidden"
        },
        "actions": {
          "type": "array",
          "description": "Action buttons; each: icon, label, showLabel, id, href, variant (default|primary|danger), class, hidden, disabled, attrs[]"
        },
        "actionsId": {
          "type": "string"
        },
        "actionsClass": {
          "type": "string"
        },
        "actionsHidden": {
          "type": "boolean"
        },
        "leadSlot": {
          "type": "string",
          "description": "Raw HTML appended to the lead cluster"
        },
        "trailLead": {
          "type": "string",
          "description": "Raw HTML at the trail's leading edge, before the actions (presence avatars, status pills)"
        },
        "trailSlot": {
          "type": "string",
          "description": "Raw HTML appended to the trail cluster (user menus etc.)"
        },
        "trailClass": {
          "type": "string"
        },
        "attrs": {
          "type": "array",
          "description": "Additional HTML attributes on the bar"
        }
      },
      "notes": "Height comes from --app-bar-height (60px). The center/leadSlot/trailSlot raw-HTML slots exist so an app can keep markup its JS already binds to; new work should use actions and title instead."
    },
    "ws_footer": {
      "function": "ws_footer(array $options = []): string",
      "description": "Global footer composite: 'Level Up Your Facilitation' phase grid + Letter CTA newsletter block + dark brand bar. The canonical bottom-of-page for all non-app-shell pages. Self-contained (emits its own styles, platform tokens, fonts, and the functional core toast/auth includes, each once per page) with a render-once guard so double inclusion yields one footer. Template: /includes/components/footer.php.",
      "options": {
        "_comment": "Reserved for future variants — the composite currently has no options."
      }
    },
    "ws_lifecycle": {
      "function": "ws_lifecycle(array $opts = []): string",
      "description": "Workshop Record lifecycle strip: Designed → Delivered → Synthesized. The status is derived by the caller (getWorkshopRecord/getWorkshopList in includes/workshop-record.php), never stored. Completed steps get a very-light red fill, pending steps stay outlined neutral — state via fill/weight only, per Red Unification.",
      "options": {
        "status": {
          "type": "string",
          "values": [
            "designed",
            "delivered",
            "synthesized"
          ],
          "default": "designed"
        },
        "variant": {
          "type": "string",
          "values": [
            "compact",
            "full"
          ],
          "default": "compact"
        },
        "cta": {
          "type": "array",
          "shape": {
            "label": "string",
            "href": "string"
          },
          "description": "Full variant only — the one context action for the current stage (e.g. Run in Facilitator, Synthesize this workshop)"
        },
        "id": {
          "type": "string"
        },
        "class": {
          "type": "string"
        },
        "attrs": {
          "type": "array"
        }
      }
    }
  },
  "rules": {
    "_comment": "Mandatory constraints for all AI-assisted development on this project",
    "componentFirst": {
      "rule": "ALWAYS use ws_* components before writing raw HTML",
      "detail": "Before creating any button, input, modal, card, badge, alert, or other UI element, check if a ws_* helper exists. If it does, use it. If it doesn't, suggest creating one.",
      "severity": "mandatory",
      "checkCommand": "grep 'function ws_' /includes/components/helpers.php"
    },
    "tokenFirst": {
      "rule": "ALWAYS use platform tokens for all styling",
      "detail": "Colors, spacing, typography, radii, shadows — use CSS variables from /design-system/platform-tokens.css. Never hardcode hex colors or pixel values when a token exists.",
      "severity": "mandatory",
      "source": "/design-system/platform-tokens.css",
      "examples": {
        "do": [
          "var(--space-4)",
          "var(--text-primary)",
          "var(--radius-lg)",
          "var(--shadow-md)",
          "var(--font-heading)"
        ],
        "dont": [
          "16px",
          "#1E1E26",
          "8px",
          "0 4px 6px rgba(0,0,0,0.07)",
          "'Fraunces'"
        ]
      }
    },
    "phaseColors": {
      "rule": "All apps use the unified mainsite red color family",
      "detail": "Phase colors have been unified to Learn Red (#E54D4D) as part of the Red Unification Plan. Traditional phase names (Learn, Plan, Facilitate, Reflect) remain for semantic grouping, but all render in Red.",
      "severity": "mandatory",
      "mapping": {
        "learn": {
          "color": "#E54D4D",
          "apps": [
            "mainsite",
            "tips",
            "podcasts",
            "training"
          ],
          "note": "Unified to red family."
        },
        "plan": {
          "color": "#E54D4D",
          "apps": [
            "planner"
          ],
          "note": "Unified to red family."
        },
        "facilitate": {
          "color": "#E54D4D",
          "apps": [
            "intervention",
            "facilitator"
          ],
          "note": "Unified to red family."
        },
        "reflect": {
          "color": "#E54D4D",
          "apps": [
            "coach"
          ],
          "note": "Unified to red family. DNA is standalone/unphased."
        }
      }
    },
    "appScopedOverrides": {
      "rule": "App-specific component overrides MUST use the app's body class scope",
      "detail": "Planner uses body class 'planner-page' with CSS scope '.planner-page'. This ensures ws_* components adapt to the app's phase color without affecting other apps.",
      "severity": "mandatory",
      "apps": {
        "planner": {
          "bodyClass": "planner-page",
          "cssScope": ".planner-page",
          "bridgeFile": "/planner/css/components/_component-bridge.css",
          "overrides": [
            ".planner-page .ws-btn--primary uses planner gradient",
            ".planner-page .ws-btn--primary icons must be white",
            ".planner-page .ws-btn--secondary uses planner-primary red (recolored June 2026)",
            ".planner-page .ws-input:focus uses planner-primary border"
          ]
        }
      }
    },
    "deprecatedCssClasses": {
      "rule": "Legacy button classes are deprecated in favor of ws_button() / .ws-btn",
      "detail": "css/styles.css still defines pre-design-system button classes (.btn-primary, .btn-secondary, .btn-tertiary, .btn-outline, .btn-sm, .btn-lg, .btn-block). New UI must use ws_button(). The modern base class is .ws-btn (NOT .ws-button), with modifiers like .ws-btn--primary / .ws-btn--secondary.",
      "severity": "recommended",
      "deprecated": [
        ".btn-primary",
        ".btn-secondary",
        ".btn-tertiary",
        ".btn-outline",
        ".btn-sm",
        ".btn-lg",
        ".btn-block"
      ]
    },
    "componentExemptions": {
      "rule": "Some app subsystems intentionally use bespoke CSS instead of ws_* components — do not flag their raw HTML as violations",
      "detail": "Planner briefing (.briefing-* in css/planner/briefing.css, used by planner/briefing.php) and planner analytics (.analytics-* in planner/analytics.php) are app-specific UI without ws_* wrappers by design. Coach ships a bespoke component library (see coach/CLAUDE.md) and does not use ws_*; its .btn-primary/.btn-secondary are coach-local, not the deprecated globals above.",
      "severity": "informational",
      "exemptScopes": [
        ".briefing-",
        ".analytics-",
        "coach/"
      ]
    },
    "iconLibrary": {
      "rule": "Use Material Symbols Outlined for ALL icons",
      "detail": "Never use Font Awesome, Heroicons, SVG icon sets, or emoji as icons. All icons come from Google Material Symbols Outlined with the standard optical size 24, weight 400, FILL 0, GRAD 0.",
      "severity": "mandatory",
      "cdn": "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@24,400,0,0",
      "helper": "icon(string $name) from /includes/icon.php renders the <span> tag"
    },
    "typography": {
      "rule": "Headings use Fraunces, body uses Inter (or Source Sans 3 for older pages)",
      "detail": "All heading elements (h1-h3, card titles, hero text) use var(--font-heading) = Fraunces. Body text, labels, buttons use var(--font-body) = Inter. Never use system fonts directly.",
      "severity": "mandatory"
    },
    "noFrameworks": {
      "rule": "Vanilla JS and CSS only — no React, Vue, Tailwind, or CSS frameworks",
      "detail": "The frontend is intentionally framework-free. Do not introduce npm-based UI frameworks, CSS utility frameworks, or JS component libraries unless explicitly requested.",
      "severity": "mandatory"
    },
    "newComponentProcess": {
      "rule": "New UI patterns go in the design system, not inline",
      "detail": "If you need UI that doesn't exist as a ws_* component: 1) Create the PHP function in /includes/components/helpers.php, 2) Create the template in /includes/components/, 3) Add CSS to /includes/components/components.css, 4) Add a demo page in /design-system/components/, 5) Then use it in the app.",
      "severity": "mandatory"
    },
    "databaseAccess": {
      "rule": "All database queries MUST use PDO prepared statements",
      "detail": "Never concatenate user input into SQL strings. Always use parameterized queries with $pdo->prepare() and $stmt->execute().",
      "severity": "mandatory"
    },
    "cssArchitecture": {
      "rule": "Follow the established CSS architecture for each app",
      "globalFiles": {
        "tokens": "/design-system/platform-tokens.css",
        "components": "/includes/components/components.css",
        "listingRedesign": "/css/listing-redesign.css",
        "detailFieldguide": "/css/detail-fieldguide.css"
      },
      "appFiles": {
        "planner": {
          "variables": "/planner/css/base/_variables.css",
          "main": "/planner/css/planner-v2.css",
          "bridge": "/planner/css/components/_component-bridge.css"
        },
        "facilitator": {
          "main": "/css/facilitator/facilitator.css",
          "participant": "/css/facilitator/participant.css",
          "summary": "/css/facilitator/summary.css"
        },
        "coach": {
          "main": "/coach/css/coach.css"
        },
        "library": {
          "landing": "/css/library/library-landing.css",
          "collections": "/css/library/library-collections.css",
          "goals": "/css/library/library-goals.css",
          "shared": "/css/listing-redesign.css",
          "detail": "/css/detail-fieldguide.css"
        }
      }
    },
    "globalHeader": {
      "rule": "Use the unified global header component for all app pages",
      "detail": "Include /includes/global-header.php with the appropriate property: library, planner, coach, intervention, tips, podcasts, dna, academy.",
      "file": "/includes/global-header.php"
    },
    "writingStyle": {
      "rule": "All user-facing content must follow the Workshopr writing style guide",
      "file": "/content/workshopr_writing_style_guide.md",
      "severity": "mandatory"
    },
    "designSystemUpdates": {
      "rule": "When CSS changes are made, update the design system documentation",
      "detail": "Update relevant files in /design-system/ and add a note to the change-log.",
      "severity": "recommended"
    }
  }
}