feat: preview templates, thumbnail features, 2 new org templates

This commit is contained in:
Rotimi Best
2026-05-23 12:24:57 +01:00
parent 4fdd4115b9
commit 78ab13f54b
106 changed files with 8320 additions and 2040 deletions
+153
View File
@@ -0,0 +1,153 @@
---
name: add-landing-template
description: Add a new org landing-page template to the ClassroomIO monorepo. Use when the user asks to "create/add a new landing template", "add a new template like X to the org landing pages", "build a new theme for the org landing page", or hands over a design reference (image, URL, prototype) for a new landing visual style.
---
# Add a new org landing-page template
Adds one new theme (e.g. `editorial`, `vibrant`) to the org landing-page system. The work spans three packages — `prototypes/`, `packages/ui/` + `packages/storybook/`, and `apps/dashboard/` — and **must be done in three phases with approval gates between them**. Do not skip ahead.
## Workflow
Use `AskUserQuestion` to gate each phase transition. The user must confirm before you move from Phase 1 → 2 → 3. If the user types a custom answer instead of selecting an option, treat any negative response as "not yet" and iterate within the current phase.
Track the work with `TaskCreate`/`TaskUpdate` — one task per numbered step below so progress is visible.
## Phase 1 — Prototype (approval required)
**Goal:** lock the visual design before any framework code.
1. Pick a short, descriptive name for the template (`editorial`, `vibrant`, `terminal`, etc.). Avoid generic adjectives.
2. Build a single-file HTML mockup at `prototypes/org-landing-page/<theme>.html`. Match the conventions of the other prototypes there (Inter font, inline CSS in `<style>`, no JS frameworks).
3. **Color discipline**: define one `--primary` CSS variable for the accent. Derived shades use `color-mix(in oklab, var(--primary) X%, ...)`. Other neutrals (bg, fg, borders, cards) live as CSS variables too. Never hardcode hex inline — the Phase-2 port to `var(--landing-*)` becomes mechanical.
4. Compose the page sections an org landing page actually needs: nav, hero, course catalog grid (this is the centerpiece — orgs sell courses), resources/links, FAQ or callout, footer. Do **not** invent sections without a data source in `OrgLandingPageProps` (no per-org testimonials, no faculty cards, no instructor bios — they have no prop backing and will be misleading).
5. Open it in the browser. Iterate with the user on typography, spacing, color, hierarchy, layout.
**Phase 1 approval gate** — call `AskUserQuestion`:
> Question: "Phase 1 complete — the prototype at `prototypes/org-landing-page/<theme>.html` matches your direction. Move on to Phase 2 (build the template in packages/ui + Storybook)?"
> Options: "Yes, proceed to Phase 2" / "Not yet — keep iterating on the prototype"
> Header: "Phase 1 → 2"
If the user picks "not yet" or writes a correction, stay in Phase 1 and iterate.
## Phase 2 — packages/ui + Storybook (approval required)
**Goal:** the template renders correctly in Storybook with the shared `mockOrgLandingPageProps` fixture, fully isolated from dashboard.
6. Add `'<theme>'` to `OrgLandingPageTheme` in `packages/ui/src/custom/org-landing-page/types.ts`.
7. Add the theme's entry to `LANDING_THEME_VARS` in `packages/ui/src/custom/org-landing-page/theme-style.ts`. The 20 variables fall into two groups:
- **Surfaces** — `--landing-bg`, `--landing-bg-section`, `--landing-card`, `--landing-card-soft`, `--landing-fg`, `--landing-fg-muted`, `--landing-fg-faint`, `--landing-border`, `--landing-border-soft`, `--landing-accent`, `--landing-accent-fg`
- **Buttons** — primary/secondary/tertiary with `-bg`, `-fg`, `-bg-hover`, plus `-border` on secondary
If the theme shares the app's chrome (light bg, dark fg, app primary): use `baseTokenVars` and override only what differs. If the theme has its own palette (cream like editorial, dark like terminal): declare all hex/values explicitly. **Decide what each button tier means per theme** — for vibrant, primary = accent; for editorial, primary = fg-inverse (dark on cream); for terminal, primary = light pill on dark.
8. Build three subcomponents in `packages/ui/src/custom/org-landing-page/<theme>/`:
- `nav.svelte`
- `hero.svelte` — **defensive defaults are required**: `courses = []`, `orgName = ''`, `labels` optional. These heroes are mounted from course pages that won't pass the full prop set.
- `course-card.svelte` — accepts `course`, optional `disableCourseLinks`, optional `labels`. Used both inside the template's own catalog grid AND on the `/courses` listing page.
**Color rule**: read every color from `var(--landing-*)`. The only allowed inline hex is decorative (painterly gradients, Mac traffic-light dots, dark IDE mockup overlays) where the value is intentionally invariant of theme. No `bg-background`, `text-foreground`, `bg-primary`, etc. — those are app-theme tokens, not landing-theme tokens.
**Text rule**: hardcoded user-facing strings route through `labels?.foo ?? 'English default'`. See the existing `OrgLandingPageLabels` fields in `types.ts`.
**Button rule**: for any CTA that isn't the template's signature primary CTA, use `<LandingButton variant="primary|secondary|tertiary">` from `@cio/ui/custom/org-landing-page` — it automatically adopts the theme's button palette.
9. Build the top-level composer `packages/ui/src/custom/org-landing-page/<theme>.svelte`:
- Wrap the root in `style={themeStyle('<theme>')}` plus `class="ui:bg-[var(--landing-bg)] ui:text-[var(--landing-fg)]"`
- Render in order: `<ThemeNav>` (or as a snippet inside `<ThemeHero>`) → `<ThemeHero>` → catalog section (using `<ThemeCourseCard>`) → `<OrgLandingPageLinks variant="<theme>" {labels} {links} />` → `<OrgLandingPageEmbed variant="<theme>" {labels} {embed} />` → `<OrgLandingPageCallout variant="<theme>" {labels} {callout} />` → `<OrgLandingPageFooter variant="<theme>" {orgName} {logoUrl} {footer} />`
10. Wire variant branches into shared section components:
- `callout.svelte` — add entries to all 5 token maps (`sectionClasses`, `headingClasses`, `descriptionClasses`, `buttonClasses`, `eyebrowClasses`) and update the `defaultEyebrow` switch
- `links.svelte` — add an `{:else if variant === '<theme>'}` branch
- `landing-page-footer.tokens.ts` — add an `if (variant === '<theme>')` block in `getFooterTokens`
- `secondary-action-button.svelte` — add an entry to `themeButtonClasses`
- `embed.svelte` — only add a branch if the theme needs distinct embed styling (terminal/editorial do; most templates fall through to the default)
11. Export from `packages/ui/src/custom/org-landing-page/index.ts`:
- `<Theme>LandingPage`, `<Theme>LandingNav`, `<Theme>LandingHero`, `<Theme>LandingCourseCard`
12. Add a Storybook story in `packages/storybook/src/templates/org-landing-page/org-landing-page.stories.svelte` — import the new page component and add `<Story name="<Theme>"><<Theme>LandingPage {...mockProps} /></Story>`.
13. Verify Phase 2: `pnpm --filter @cio/ui build`, open Storybook (`pnpm --filter @cio/storybook dev`), navigate to **Templates → Org Landing Page → <Theme>**, click around all sections. Run `npx --no-install svelte-check --workspace=packages/ui --no-tsconfig > /tmp/check.log 2>&1` once, then `grep org-landing-page /tmp/check.log` — zero errors expected in any file under `org-landing-page/`.
**Phase 2 approval gate** — call `AskUserQuestion`:
> Question: "Phase 2 complete — the template renders in Storybook. Move on to Phase 3 (wire it into the dashboard so it shows up in org settings + the live org pages)?"
> Options: "Yes, proceed to Phase 3" / "Not yet — fix things in Storybook first"
> Header: "Phase 2 → 3"
## Phase 3 — Dashboard integration (final approval)
**Goal:** the template selectable in `/settings/landingpage`, rendering correctly on `/`, `/courses`, `/course/[slug]`, and the course editor preview.
14. Add `'<theme>'` to `OrgLandingPageTheme` in `apps/dashboard/src/lib/utils/types/org.ts`.
15. **Three lazy switches + one eager map** — all four must include the new theme or things will silently fall back to minimal:
- `apps/dashboard/src/lib/features/org/utils/landing-page.ts`:
- `landingPageThemes` const array
- `importThemeComponent(theme)` switch — used by the public org root page
- `importThemeNavHero(theme)` switch — used by `/courses`, `/course/[slug]`, course-landing editor
- `importThemeCourseCard(theme)` switch — used by `/courses` per-card rendering
- `themeCourseGridClass(theme)` — only update if the template uses a non-default grid (segmented borders, special gap)
- `apps/dashboard/src/lib/features/org/utils/landing-page-components.ts`:
- All three eager maps: `landingPageThemeComponents`, `landingPageNavComponents`, `landingPageHeroComponents`
- **Critical**: this map is only used by the settings-page live preview. The org-site routes use the lazy switches above. Forgetting the lazy ones means /settings looks right but the live page renders minimal.
16. Theme picker in settings: `apps/dashboard/src/lib/features/settings/pages/landingpage.svelte` → append to `themeCards` with `preview: '/templates/<theme>.png'` and translation keys.
17. Translations: `apps/dashboard/src/lib/utils/translations/en.json` → add `settings.landing_page.theme.cards.<theme>.title` and `.description`.
18. Preview image: drop `apps/dashboard/static/templates/<theme>.png`. **Must be under 500KB.** If the source is larger, compress with `sips -Z 900 source.png --out target.png` (resizes to max 900px wide while preserving aspect ratio). JPEGs are acceptable when PNG compression isn't enough — update the `themeCards` `preview` path accordingly.
19. Verify Phase 3:
- `pnpm --filter @cio/ui build`
- `npx --no-install svelte-check --workspace=apps/dashboard --no-tsconfig > /tmp/dash-check.log 2>&1` (run once)
- `grep -E "(landing-page-components|landingpage\.svelte|types/org\.ts|org/utils/landing-page\.ts)" /tmp/dash-check.log` — zero errors expected in any touched file
- Run the dashboard and click through: select the new template in settings, view it on the live org root, `/courses`, `/course/[slug]`. Verify the nav/hero, course cards, Filter aside, Clear Filters button, and footer all render with the right colors on each route.
**Phase 3 final approval** — call `AskUserQuestion`:
> Question: "Phase 3 complete — the <theme> template is live in the dashboard and renders on all org-site routes. Anything else?"
> Options: "All good, we're done" / "There's a visual issue to fix"
> Header: "Final check"
## Anti-checklist (mistakes to prevent)
Read this before starting. These are real bugs that have appeared in this codebase:
- **Inline hex in components** — the wrapper sets `--landing-*` vars; components read from them. Hardcoded hex breaks theme switching and the `/courses` cross-page rendering. Exception: decorative painterly fills, Mac traffic-light dots, and dark IDE mockup overlays that are intentionally invariant of theme.
- **Missing defensive defaults** — heroes that consume `courses` or `orgName` must default them (`courses = []`, `orgName = ''`). They crash when mounted from course pages that don't pass the full prop set.
- **Per-call-site button styling** — use `<LandingButton variant="...">` instead. The /courses Filter aside's Clear Filters button is the canonical example.
- **Hardcoded user-facing strings** — route through `labels?.X ?? 'Default'`. The labels surface is consumed by the dashboard's translations layer.
- **Only updating one of the dashboard registries** — `landingPageThemeComponents` (eager, settings preview) and the three `importTheme*` switches (lazy, public routes) must all include the new theme. Forgetting `importThemeComponent` makes the live org page silently render minimal.
- **Re-running expensive checks** — `svelte-check` typechecks the whole package on each invocation. Run it once, capture to `/tmp/check.log`, grep the captured output as many times as you need.
- **Adding non-prop-backed sections** — testimonials, instructor cards, faculty bios, statistics aren't in `OrgLandingPageProps`. Either drop them, or extend the props type for everyone (significant API surface change — discuss before doing).
- **Changing the layout pattern when extracting a cell** — if the original catalog uses `border-t/border-l` on the grid + `border-r/border-b` on each child (bordered-cell pattern), preserve that pattern. Two failure modes to avoid:
- Don't swap it for `gap-px bg-border` — that loses the outer top/left border and breaks visually when there are fewer cards than fill the grid.
- Don't move the right/bottom border from the card to the grid via `[&>*]:border-r [&>*]:border-b`. If a `<BlurFade>` (or any wrapper) sits between the grid and the card, the `[&>*]:` selector lands on the wrapper, not the card — different render. Keep `border-r/border-b` ON the card component itself; the parent owns only `border-t/border-l`.
## File-touch summary (~30 files for a typical theme)
| Path | Purpose |
|---|---|
| `prototypes/org-landing-page/<theme>.html` | Phase 1 design mockup |
| `packages/ui/src/custom/org-landing-page/types.ts` | Theme union |
| `packages/ui/src/custom/org-landing-page/theme-style.ts` | CSS variable contract |
| `packages/ui/src/custom/org-landing-page/<theme>/nav.svelte` | Nav component |
| `packages/ui/src/custom/org-landing-page/<theme>/hero.svelte` | Hero component |
| `packages/ui/src/custom/org-landing-page/<theme>/course-card.svelte` | Card component |
| `packages/ui/src/custom/org-landing-page/<theme>.svelte` | Page composer |
| `packages/ui/src/custom/org-landing-page/callout.svelte` | Variant branch |
| `packages/ui/src/custom/org-landing-page/links.svelte` | Variant branch |
| `packages/ui/src/custom/org-landing-page/landing-page-footer.tokens.ts` | Footer tokens |
| `packages/ui/src/custom/org-landing-page/secondary-action-button.svelte` | Button variant |
| `packages/ui/src/custom/org-landing-page/embed.svelte` | Optional variant branch |
| `packages/ui/src/custom/org-landing-page/index.ts` | Exports |
| `packages/storybook/src/templates/org-landing-page/org-landing-page.stories.svelte` | Storybook story |
| `apps/dashboard/src/lib/utils/types/org.ts` | Dashboard theme union |
| `apps/dashboard/src/lib/features/org/utils/landing-page.ts` | Lazy switches + themes array |
| `apps/dashboard/src/lib/features/org/utils/landing-page-components.ts` | Eager maps (settings preview) |
| `apps/dashboard/src/lib/features/settings/pages/landingpage.svelte` | Theme picker card |
| `apps/dashboard/src/lib/utils/translations/en.json` | Title + description |
| `apps/dashboard/static/templates/<theme>.png` | Preview image (<500KB) |
+60
View File
@@ -0,0 +1,60 @@
---
name: frontend-design
description: Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
license: Complete terms in LICENSE.txt
---
This skill guides creation of distinctive, production-grade frontend interfaces that avoid generic "AI slop" aesthetics. Implement real working code with exceptional attention to aesthetic details and creative choices.
The user provides frontend requirements: a component, page, application, or interface to build. They may include context about the purpose, audience, or technical constraints.
## Design Thinking
Before coding, understand the context and commit to a BOLD aesthetic direction:
- **Purpose**: What problem does this interface solve? Who uses it?
- **Tone**: Pick an extreme: brutally minimal, maximalist chaos, retro-futuristic, organic/natural, luxury/refined, playful/toy-like, editorial/magazine, brutalist/raw, art deco/geometric, soft/pastel, industrial/utilitarian, etc. There are so many flavors to choose from. Use these for inspiration but design one that is true to the aesthetic direction.
- **Constraints**: Technical requirements (framework, performance, accessibility).
- **Differentiation**: What makes this UNFORGETTABLE? What's the one thing someone will remember?
**CRITICAL**: Choose a clear conceptual direction and execute it with precision. Bold maximalism and refined minimalism both work - the key is intentionality, not intensity.
Then implement working code (HTML/CSS/JS, React, Vue, etc.) that is:
- Production-grade and functional
- Visually striking and memorable
- Cohesive with a clear aesthetic point-of-view
- Meticulously refined in every detail
## Internal Component Library
**ALWAYS prioritise components from `packages/ui` before reaching for raw HTML elements or third-party UI libraries.**
Import using the `@cio/ui` package name. Examples:
- `import { Button } from '@cio/ui/base/button'`
- `import { Chip } from '@cio/ui/custom/chip'`
- `import { cn } from '@cio/ui/tools'`
- `import { BackButton } from '@cio/ui'` (re-exported root for some components)
Available base components (covers the majority of common UI needs):
`accordion`, `alert`, `avatar`, `badge`, `breadcrumb`, `button`, `button-group`, `card`, `chart`, `checkbox`, `collapsible`, `command`, `copy-button`, `dark-mode`, `dialog`, `drawer`, `dropdown-menu`, `empty`, `field`, `hover-card`, `input`, `input-group`, `item`, `kbd`, `label`, `menubar`, `navigation-menu`, `page`, `pagination`, `password`, `popover`, `progress`, `radio-group`, `select`, `separator`, `sheet`, `sidebar`, `skeleton`, `sonner`, `spinner`, `switch`, `table`, `tabs`, `textarea`, `toggle`, `toggle-group`, `tooltip`
Available custom components (domain-specific, higher-level):
`animation`, `back-button`, `chat-textarea`, `checkbox-field`, `checkbox-option-card`, `chip`, `code`, `course-card`, `document-card`, `domain-input`, `editor`, `empty`, `exercise-question`, `file-drop-zone`, `icon-button`, `image-cropper`, `input-field`, `media-player`, `mention-popover`, `moving-icons`, `multi-select-list`, `newsfeed-reactions`, `org-landing-page`, `password`, `pricing-card`, `pricing-toggle`, `question-type-picker`, `radio-item`, `radio-option-card`, `resource-list-row`, `safe-html-content`, `search`, `simple-logo-nav`, `textarea-field`, `underline-tabs`, `user-avatar`, `widget-layout-preview`, `youtube-link-form`
Only build a component from scratch when none of the above is a reasonable fit. When extending an existing component, prefer composition over modification of the source file.
## Frontend Aesthetics Guidelines
Focus on:
- **Typography**: Choose fonts that are beautiful, unique, and interesting. Avoid generic fonts like Arial and Inter; opt instead for distinctive choices that elevate the frontend's aesthetics; unexpected, characterful font choices. Pair a distinctive display font with a refined body font.
- **Color & Theme**: Commit to a cohesive aesthetic. Use CSS variables for consistency. Dominant colors with sharp accents outperform timid, evenly-distributed palettes.
- **Motion**: Use animations for effects and micro-interactions. Prioritize CSS-only solutions for HTML. Use Motion library for React when available. Focus on high-impact moments: one well-orchestrated page load with staggered reveals (animation-delay) creates more delight than scattered micro-interactions. Use scroll-triggering and hover states that surprise.
- **Spatial Composition**: Unexpected layouts. Asymmetry. Overlap. Diagonal flow. Grid-breaking elements. Generous negative space OR controlled density.
- **Backgrounds & Visual Details**: Create atmosphere and depth rather than defaulting to solid colors. Add contextual effects and textures that match the overall aesthetic. Apply creative forms like gradient meshes, noise textures, geometric patterns, layered transparencies, dramatic shadows, decorative borders, custom cursors, and grain overlays.
NEVER use generic AI-generated aesthetics like overused font families (Inter, Roboto, Arial, system fonts), cliched color schemes (particularly purple gradients on white backgrounds), predictable layouts and component patterns, and cookie-cutter design that lacks context-specific character.
Interpret creatively and make unexpected choices that feel genuinely designed for the context. No design should be the same. Vary between light and dark themes, different fonts, different aesthetics. NEVER converge on common choices (Space Grotesk, for example) across generations.
**IMPORTANT**: Match implementation complexity to the aesthetic vision. Maximalist designs need elaborate code with extensive animations and effects. Minimalist or refined designs need restraint, precision, and careful attention to spacing, typography, and subtle details. Elegance comes from executing the vision well.
Remember: Claude is capable of extraordinary creative work. Don't hold back, show what can truly be created when thinking outside the box and committing fully to a distinctive vision.