diff --git a/.specs/banner.md b/.specs/banner.md new file mode 100644 index 000000000..be17d7dfe --- /dev/null +++ b/.specs/banner.md @@ -0,0 +1,154 @@ +--- +name: banner +category: marketing +structure: monolithic +status: implemented +spec_version: 2 +checksum: aa5819dc3d093d74004588fd8276547f0e79d431c2a081b4fe6997f20fcb08ab +created: 2026-09-22 +last_updated: 2026-09-26 +--- + +# Banner — Component Spec + +## Purpose + +A split promotional band: the announcement and its actions in the wide leading column, a short supporting note on a raised surface in the narrow trailing one. It is the page's one loud interruption between sections — wider and more assertive than `call-to-action`, which is a single-column closing ask. + +## When to use + +- To announce a launch, programme or offer part-way down a marketing page. +- Whenever an announcement needs both a primary action and a short piece of supporting context beside it. +- Once per page. A second banner turns the first into furniture. + +## When NOT to use + +- For the closing ask at the end of a page or section → use `call-to-action`, a single-column panel. +- For the page's leading statement → use `hero`, whose `Hero.Title` owns the `h1`. +- For a dismissible or status-bearing notice inside the product → use `message`. +- For a feature tile in a grid → use `topic` in a `card-grid`. + +## Related + +- `call-to-action` — the single-column closing ask; this is the wider mid-page announcement. +- `hero` — the page's leading band; its `Hero.Title` owns the page's statement and `h1`. +- `message` — the in-product notice, which can be dismissed and carries severity. + +## Best practices + +- The band draws no frame of its own: the rule and the corner marks belong to the `section-module` or `frame-box` that the page assembles it into, so a band placed in a framed column never lands a second hairline on the column's own. + +- Put the announcement in `title` and keep the trailing column to one supporting sentence — it is context, not a second pitch. +- Lead with one primary action in the `actions` slot; the band's whole job is that one target. +- Use `eyebrow` to name the programme the banner belongs to, so the band reads in context when a reader lands mid-page. +- Keep the trailing note short enough not to set the band's height; it sits beside the announcement, not under it. + +## Usage + +```vue + + + +``` + +## Props + +| Prop | Type | Default | Required | JSDoc | +|---|---|---|---|---| +| `title` | `string` | `—` | true | The announcement, rendered as the band's `h2` in the leading column. | +| `description` | `string` | `''` | false | Supporting note set on the raised trailing column; overridden by the `aside` slot. | +| `eyebrow` | `string` | `''` | false | Short uppercase overline naming the programme, above the announcement. | + +## Events + +| _none_ | — | — | + +## Slots + +| Slot | Scope | Notes | +|---|---|---| +| `actions` | — | The controls the band exists to offer, under the announcement. | +| `aside` | — | Trailing column content; replaces the `description` prop when provided. | + +## States + +- Visual states: `default` +- The band holds no state of its own; controls composed into `actions` keep theirs +- Below `md` the two columns stack, announcement first, so reading order matches visual order + +## Motion & Animations + +_none_ + +## Tokens + +| Region | Token (DESIGN.md) | +|---|---| +| typography (announcement) | `.text-heading-xl` | +| typography (trailing note) | `.text-heading-sm` | +| announcement text | `var(--text-default)` | +| trailing note text | `var(--text-muted)` | +| leading surface | `var(--bg-surface)` | +| trailing surface | `var(--bg-surface-raised)` | +| band rules and marks | `var(--border-default)` | +| spacing (column padding) | `var(--spacing-xxl)` | +| spacing (copy stack) | `var(--spacing-lg)` | +| spacing (copy to actions) | `var(--spacing-xl)` | + +## Theme gaps + +| Figma variable | Temporary primitive | Follow-up | +|---|---|---| +| _none_ | — | — | + +## Accessibility (WCAG 2.1 AA) + +- Visible focus: not applicable to the band itself; controls composed into `actions` keep their own `focus-visible:ring-2 focus-visible:ring-(--ring-color) focus-visible:ring-offset-2 focus-visible:ring-offset-(--bg-canvas)` ring. +- Keyboard map: none of its own — `Tab` reaches only the controls placed in the `actions` slot, in DOM order. +- ARIA: the announcement is a real `h2` rendered by the composed `section-title`, and the band is a `
` named by its `title`, so it is reachable as a named landmark; no `role` is added. +- Contrast ≥4.5:1 (text) / ≥3:1 (large + icons): the announcement is `var(--text-default)` on `var(--bg-surface)`; the trailing note uses `var(--text-muted)` on `var(--bg-surface-raised)`, which clears AA on the dark canvas and is a known theme-level shortfall on the light one (see `big-numbers`). +- `motion-reduce:transition-none motion-reduce:transform-none` — not applicable, the band is static. +- Touch target ≥40×40 px — the `actions` row stretches its children to full width below `md`, so slotted buttons keep their own target size. + +## Stories (Storybook) + +- Default +- WithAside — the trailing column filled through the `aside` slot rather than the `description` prop (justified: the slot replaces the prop and is the only way to put richer content in that column) + +## Constraints — DO NOT + + + +- Do not add props beyond the Props table above. If you need a prop that is not listed, emit `BLOCKED: missing prop ` and stop — do not invent. +- Do not add events beyond the Events table above. Same rule for slots and sub-components. +- Do not invent imports. Every `@aziontech/webkit/*` path must exist in `packages/webkit/package.json#exports`. Every relative import must resolve to a real file. Every npm package must be installed. +- Do not use HEX/RGB/HSL colors, Tailwind palette names (e.g. `bg-blue-500`), raw typography classes (e.g. `text-sm`), `any`, `@ts-ignore`, or `class` inside `defineProps`. +- Do not install or import positioning/animation libraries (`@floating-ui/*`, `popper.js`, `tippy.js`, `gsap`, `framer-motion`, `motion`, `@vueuse/motion`, `@formkit/auto-animate`, drag-drop runtimes, scroll virtualization libs). Use CSS + Vue primitives (``, ``). See `.claude/rules/dependencies.md`. +- Do not improvise animations. Every `animate-*` / `transition-*` class must come from `packages/theme/src/tokens/semantic/animations.js`; every motion-bearing class pairs with `motion-reduce:*` on the same class string; no component-local `@keyframes`. +- Do not create class presets in JavaScript (`const kindClasses = {...}`, `const sharedClasses = [...]`, `const sizeClasses = {...}`, `const rootClasses = computed(...)`). Variants live on `data-*` attributes consumed by Tailwind `data-[attr=value]:`. All utilities live inline on the root element's `class` attribute. No `