@stackonward/cli 1.5.3 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (74) hide show
  1. package/dist/index.js +1 -1
  2. package/dist/index.js.map +1 -1
  3. package/dist/resources/components/nuxt/site-source/app/assets/css/site-shell.css +124 -56
  4. package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/android.svg +10 -0
  5. package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/apple.svg +3 -0
  6. package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/whatsapp.svg +5 -0
  7. package/dist/resources/components/nuxt/site-source/app/components/app/Sidebar.vue +1 -1
  8. package/dist/resources/components/nuxt/site-source/app/components/checkout/Status.vue +3 -3
  9. package/dist/resources/components/nuxt/site-source/app/components/consent/Banner.vue +23 -8
  10. package/dist/resources/components/nuxt/site-source/app/components/content/CallToAction.vue +61 -0
  11. package/dist/resources/components/nuxt/site-source/app/components/content/Card.vue +35 -0
  12. package/dist/resources/components/nuxt/site-source/app/components/content/Compatibility.vue +10 -0
  13. package/dist/resources/components/nuxt/site-source/app/components/content/Faq.vue +56 -0
  14. package/dist/resources/components/nuxt/site-source/app/components/content/Features.vue +8 -0
  15. package/dist/resources/components/nuxt/site-source/app/components/content/List.vue +1 -8
  16. package/dist/resources/components/nuxt/site-source/app/components/content/Reviews.vue +93 -0
  17. package/dist/resources/components/nuxt/site-source/app/components/countdown/Timer.vue +112 -0
  18. package/dist/resources/components/nuxt/site-source/app/components/locale/Menu.vue +3 -3
  19. package/dist/resources/components/nuxt/site-source/app/components/site/BackToTop.vue +30 -23
  20. package/dist/resources/components/nuxt/site-source/app/components/site/Header.vue +2 -2
  21. package/dist/resources/components/nuxt/site-source/app/components/steps/Progress.vue +36 -0
  22. package/dist/resources/components/nuxt/site-source/app/components/steps/Showcase.vue +340 -0
  23. package/dist/resources/components/nuxt/site-source/app/components/theme/Menu.vue +4 -4
  24. package/dist/resources/components/nuxt/site-source/app/components/ui/Button.vue +28 -2
  25. package/dist/resources/components/nuxt/site-source/app/components/ui/GradientBorder.vue +5 -5
  26. package/dist/resources/components/nuxt/site-source/app/components/ui/Icon.vue +14 -0
  27. package/dist/resources/components/nuxt/site-source/app/components/ui/Image.vue +126 -0
  28. package/dist/resources/components/nuxt/site-source/app/components/ui/Marquee.vue +99 -0
  29. package/dist/resources/components/nuxt/site-source/app/components/ui/Modal.vue +26 -88
  30. package/dist/resources/components/nuxt/site-source/app/components/ui/Popover.vue +87 -93
  31. package/dist/resources/components/nuxt/site-source/app/components/ui/Text.vue +84 -12
  32. package/dist/resources/components/nuxt/site-source/app/components/ui/Video.vue +105 -0
  33. package/dist/resources/components/nuxt/site-source/app/components/whatsapp/Chat.vue +126 -0
  34. package/dist/resources/components/nuxt/site-source/app/components/youtube/Embed.vue +286 -73
  35. package/dist/resources/components/nuxt/site-source/app/composables/useFloatingBottomStack.ts +44 -0
  36. package/dist/resources/components/nuxt/site-source/app/composables/useNavigation.ts +6 -8
  37. package/dist/resources/components/nuxt/site-source/app/consent/types.ts +1 -4
  38. package/dist/resources/components/nuxt/site-source/app/content/types.ts +3 -0
  39. package/dist/resources/components/nuxt/site-source/app/theme/accordion.ts +5 -4
  40. package/dist/resources/components/nuxt/site-source/app/theme/breadcrumb.ts +14 -4
  41. package/dist/resources/components/nuxt/site-source/app/theme/button.ts +133 -27
  42. package/dist/resources/components/nuxt/site-source/app/theme/carousel.ts +11 -7
  43. package/dist/resources/components/nuxt/site-source/app/theme/checkbox.ts +9 -3
  44. package/dist/resources/components/nuxt/site-source/app/theme/drawer.ts +6 -0
  45. package/dist/resources/components/nuxt/site-source/app/theme/form-field.ts +8 -0
  46. package/dist/resources/components/nuxt/site-source/app/theme/icon.ts +6 -0
  47. package/dist/resources/components/nuxt/site-source/app/theme/index.ts +32 -1
  48. package/dist/resources/components/nuxt/site-source/app/theme/input.ts +49 -18
  49. package/dist/resources/components/nuxt/site-source/app/theme/modal.ts +9 -5
  50. package/dist/resources/components/nuxt/site-source/app/theme/pagination.ts +9 -2
  51. package/dist/resources/components/nuxt/site-source/app/theme/radio-group.ts +7 -0
  52. package/dist/resources/components/nuxt/site-source/app/theme/select.ts +62 -6
  53. package/dist/resources/components/nuxt/site-source/app/theme/stepper.ts +15 -6
  54. package/dist/resources/components/nuxt/site-source/app/theme/textarea.ts +78 -0
  55. package/dist/resources/components/nuxt/site-source/app/theme/toast.ts +1 -4
  56. package/dist/resources/components/nuxt/site-source/app/theme/tooltip.ts +5 -1
  57. package/dist/resources/components/registry.json +133 -45
  58. package/dist/resources/components/vue/ui/BottomSheet.vue +5 -1
  59. package/dist/resources/components/vue/ui/ScrollArea.vue +2 -1
  60. package/dist/resources/registry.json +1 -1
  61. package/dist/resources/templates/base/.claude/docs/architecture-template.md +2 -1
  62. package/dist/resources/templates/base/AGENTS.md +7 -0
  63. package/dist/resources/templates/base/CLAUDE.md +7 -0
  64. package/dist/resources/templates/nuxt/.claude/docs/stack.md +20 -1
  65. package/dist/resources/templates/nuxt/DESIGN.md +191 -0
  66. package/dist/resources/templates/nuxt/app/app.config.ts +2 -0
  67. package/dist/resources/templates/nuxt/app/pages/app/index.vue +9 -7
  68. package/dist/resources/templates/nuxt/app/pages/index.vue +12 -27
  69. package/dist/resources/templates/nuxt/app/pages/ui/index.vue +219 -3
  70. package/dist/resources/templates/nuxt/nuxt.config.ts +2 -4
  71. package/dist/resources/templates/nuxt/package.json +2 -1
  72. package/dist/resources/templates/nuxt/stylelint.config.mjs +12 -0
  73. package/package.json +3 -3
  74. package/dist/resources/components/nuxt/site-source/app/assets/icons/common/.gitkeep +0 -0
@@ -0,0 +1,191 @@
1
+ # Nuxt Design Standard
2
+
3
+ This document defines the visual contract for the application-owned Nuxt UI foundation. Read it with the [Nuxt Engineering Standard](.claude/docs/stack.md) before changing icons, controls or theme defaults. Application branding remains in its own assets and semantic color tokens.
4
+
5
+ ## Design Decisions
6
+
7
+ | Decision | Primary-source pattern | Application choice | Reason |
8
+ | ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------- |
9
+ | Functional icon family | [Lucide specification](https://lucide.dev/contribute/icons/specification) defines a consistent canvas, stroke and visual weight. | Use the installed Lucide collection with its original geometry. | One family keeps related actions visually coherent. |
10
+ | Size hierarchy | [Carbon icon usage](https://carbondesignsystem.com/elements/icons/usage/) uses a small, consistent scale and separates artwork from touch targets. | Use 16/20/24/32px for interface icons; 20px is the ordinary default. | Central sizing supports both compact and prominent contexts. |
11
+ | Accessible meaning | [Lucide accessibility](https://lucide.dev/how-to/accessibility) distinguishes decorative, standalone and interactive icons. | Hide decoration; label standalone meaning or the interactive control that owns an action. | Assistive technology announces each meaning once. |
12
+ | Rendering and delivery | [Nuxt Icon](https://nuxt.com/modules/icon) supports application defaults, local collections and SVG rendering. | Reuse Nuxt Icon through `UiIcon`, bundle Lucide locally and disable external API fallback. | SSR and client rendering use the same locally owned assets and configuration. |
13
+
14
+ ## Ownership
15
+
16
+ | Owner | Contract |
17
+ | ---------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- |
18
+ | `app/assets/css/site-shell.css` | `--site-control-height-*`, icon sizes, radius tokens, Tailwind aliases and semantic colors |
19
+ | `app/theme/icon.ts` | Nuxt Icon defaults: 20px token, SVG mode, non-shrinking alignment and decorative attributes |
20
+ | `app/app.config.ts` | Composes `icon` defaults and the Nuxt UI theme |
21
+ | `app/components/ui/Icon.vue` | Typed `name`, `size` and accessible `label` adapter |
22
+ | `app/theme/button.ts`, `input.ts`, `select.ts`, `textarea.ts`, `checkbox.ts` | Internal icon slots and the control's geometry |
23
+ | `nuxt.config.ts` | Locally bundled `lucide`, `brand` SVG collection and `fallbackToApi: false` |
24
+ | `app/assets/icons/brand/` | Reviewed third-party identity assets |
25
+
26
+ Registry-installed source belongs to this application. **MUST** change the owning token or component when a repeated pattern changes. A one-location size choice stays at its caller. **FORBIDDEN**: adding a second icon component, runtime registry, icon font or icon library for an existing Lucide capability.
27
+
28
+ ## Control Geometry and Visual Scenarios
29
+
30
+ Marketing and product UI use the same `UiButton`, Nuxt UI controls and theme tokens. **MUST** choose the size and composition for the action's context; **FORBIDDEN**: separate marketing/product button implementations or campaign dimensions in the generic button default.
31
+
32
+ | Scenario | Controls | Typography and layout ownership |
33
+ | -------------------------------- | ----------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
34
+ | Marketing conversion | Section CTAs use `2xl` for prominence, with a section-owned minimum width of 200px and `max-w-full`. | Content sections own their larger headings, supporting copy, generous spacing and responsive content width. |
35
+ | Product navigation and workbench | Use `sm` for compact labeled actions and `md` for ordinary controls. Buttons fit their content. | App header, sidebar and editor owners control density, alignment and available width; controls retain the shared geometry. |
36
+ | Product authentication and forms | Use `lg` when the form needs a larger field and submission target. Select `block` for a full-width submit action. | The form owns field width, label/help spacing and validation space; it does not inherit a marketing CTA's minimum width. |
37
+
38
+ The control scale uses `--site-control-height-*` tokens and `h-control-*` utilities. Values below assume a 16px root; height, text and padding use rem. Text shows font size / line height. Button labels use medium weight. Input/select padding is the base padding on each side; icon slots add their own reserved space.
39
+
40
+ | Size | Button height | Input/select height | Text | Button horizontal padding | Input/select horizontal padding | Icon |
41
+ | -------------- | ------------- | ------------------- | --------- | ------------------------- | ------------------------------- | ---- |
42
+ | `xs` | 32px | 32px | 12 / 16px | 12px | 12px | 16px |
43
+ | `sm` | 36px | 36px | 14 / 20px | 16px | 12px | 16px |
44
+ | `md` (default) | 42px | 42px | 14 / 20px | 20px | 14px | 20px |
45
+ | `lg` | 48px | 48px | 16 / 24px | 24px | 16px | 20px |
46
+ | `xl` | 52px | 52px | 18 / 28px | 28px | 18px | 24px |
47
+ | `2xl` | 56px | — | 18 / 28px | 32px | — | 24px |
48
+
49
+ Editable inputs and textareas use 16px text below the `md` breakpoint unless the caller explicitly opts into the framework's `fixed` typography behavior. Their desktop typography follows the table. Select triggers retain their selected size. Textareas use rows/content height, not the single-line height scale; their vertical padding belongs to `app/theme/textarea.ts`.
50
+
51
+ Ordinary buttons have intrinsic width with no shared minimum or fixed width. `block` or `fullWidth` explicitly fills the available width. Marketing CTA widths belong to `content/CallToAction.vue`, `content/Faq.vue`, `content/Reviews.vue` and `steps/Showcase.vue`; page widths and heading scales remain at their layout/section owner. Link buttons retain text-flow height. Square icon buttons keep at least 44 × 44px targets even when their selected size is smaller.
52
+
53
+ Buttons, text inputs, selects and textareas use `--site-radius-md` through `rounded-ml` (10px); non-fullscreen dialogs use `--site-radius-lg` through `rounded-2ml` (20px). **MUST** reuse these radius tokens instead of adding a different radius to each field or button. Primary and neutral outline buttons and ordinary outlined fields use one 1px ring, without a second border. Primary solid buttons and primary outlined fields expose a 2px keyboard-focus outline; primary outline buttons use a 2px focus ring. Other semantic variants retain their theme-owned focus behavior. Focus treatment must remain visible and distinct from the resting edge.
54
+
55
+ Custom token utilities **MUST** be registered in `app/theme/index.ts` through `tv.twMergeConfig.extend.theme`: `radius` owns `ml`/`2ml`, and `spacing` owns the `control-*`/`icon-*` scales. This lets Nuxt UI merge framework defaults, theme values and caller overrides consistently. **FORBIDDEN**: relying on CSS source order to resolve conflicting size or radius utilities.
56
+
57
+ ✅ **Select shared sizes; the caller owns emphasis and width.**
58
+
59
+ ```vue
60
+ <UiButton size="sm" color="primary" variant="solid">Sign in</UiButton>
61
+ <UiButton size="lg" block type="submit">Continue</UiButton>
62
+ <UiButton size="2xl" class="min-w-[200px] max-w-full">Start creating</UiButton>
63
+ ```
64
+
65
+ ❌ **A generic width mandate and per-page sizing bypass both scene owners.**
66
+
67
+ ```vue
68
+ <UiButton class="h-[39px] min-w-[200px] rounded-[7px] text-[15px]">Save</UiButton>
69
+ ```
70
+
71
+ ## Functional Icon Geometry
72
+
73
+ Functional icons **MUST** use `i-lucide-*` names from the installed collection. Their design uses a square 24 × 24 viewBox, 2-unit centered strokes, rounded open-path caps and rounded joins. Preserve the family's edge clearance, internal spacing and visual balance; identical CSS dimensions alone do not establish equal optical weight. These constraints follow the [Lucide specification](https://lucide.dev/contribute/icons/specification).
74
+
75
+ Keep the original aspect ratio and geometry when rendering at another size. Strokes scale proportionally with the viewBox; the 2-unit source stroke is not a fixed 2 CSS-pixel stroke at every display size. **FORBIDDEN**: per-location stroke-width overrides, non-uniform scaling, padding embedded in duplicate SVGs or arbitrary `translate` corrections. Align icons and text with the control's flex/grid layout. A family-level optical issue is resolved in the asset or by selecting a clearer existing icon.
76
+
77
+ Use the same icon for the same action across routes. Distinct actions must not share an ambiguous symbol in the same context. Prefer familiar action/object meanings and retain text for unfamiliar actions. Selection, loading and error states use the established control and status contracts; a spinner alone does not explain what is pending.
78
+
79
+ ## Size Scale
80
+
81
+ Values use rem and therefore follow the user's root text size. Pixel equivalents below assume a 16px root.
82
+
83
+ | Token / utility | Value | Intended use |
84
+ | ----------------------------------- | -------------- | -------------------------------------------------------- |
85
+ | `--site-icon-xs` / `size-icon-xs` | 0.75rem / 12px | Internal checkbox mark only |
86
+ | `--site-icon-sm` / `size-icon-sm` | 1rem / 16px | Compact inline or control icon |
87
+ | `--site-icon-md` / `size-icon-md` | 1.25rem / 20px | Default functional icon, navigation and ordinary actions |
88
+ | `--site-icon-lg` / `size-icon-lg` | 1.5rem / 24px | Prominent control or section icon |
89
+ | `--site-icon-xl` / `size-icon-xl` | 2rem / 32px | Status or feature illustration |
90
+ | `--site-icon-2xl` / `size-icon-2xl` | 2.5rem / 40px | Large status or feature illustration |
91
+ | `--site-icon-3xl` / `size-icon-3xl` | 3rem / 48px | Large empty-state or explanatory illustration |
92
+
93
+ Use the default unless the surrounding component calls for another level. Buttons and inputs map `xs`/`sm` to 16px, `md`/`lg` to 20px and `xl` to 24px; the button's `2xl` also uses 24px. Internal Nuxt UI icons keep their component slot ownership and share the same Nuxt Icon provider. Do not manually duplicate an icon already provided by a control's `icon`, `leading-icon` or `trailing-icon` prop.
94
+
95
+ An icon-only button or link **MUST** expose a target of at least 44 × 44 CSS pixels, including on narrow viewports. The artwork can remain 16, 20 or 24px. This separates visual density from interaction size, as described by [Carbon](https://carbondesignsystem.com/elements/icons/usage/) and [Lucide](https://lucide.dev/how-to/accessibility). Control padding, minimum dimensions and focus behavior belong to the interactive wrapper.
96
+
97
+ ## Component API and Overrides
98
+
99
+ `UiIcon` accepts `name: string`, `size?: string | number` and `label?: string`. Numeric `size` values are CSS pixels; strings accept CSS lengths, including rem and token references. Without `size`, the application default is `var(--site-icon-md)`.
100
+
101
+ ✅ **Use the shared default or one explicit size mechanism.**
102
+
103
+ ```vue
104
+ <UiIcon name="i-lucide-image" />
105
+ <UiIcon name="i-lucide-image" class="size-icon-lg" />
106
+ <UiIcon name="i-lucide-image" :size="24" />
107
+ <UiIcon name="i-lucide-image" size="var(--site-icon-xl)" />
108
+ ```
109
+
110
+ `size-icon-*` is the preferred local scale selection. Use `size` when a measured integration requires a specific value. **MUST** choose one sizing mechanism for an instance; `size` and a sizing class must not compete. Color is supplied with semantic classes or inherited from the parent, not a separate icon-specific color prop.
111
+
112
+ ❌ **Competing sizes and page-owned artwork bypass the shared contract.**
113
+
114
+ ```vue
115
+ <UiIcon name="i-lucide-image" :size="18" class="size-icon-lg" />
116
+ <svg viewBox="0 0 32 20" class="h-4 w-7" stroke-width="3" />
117
+ ```
118
+
119
+ **FORBIDDEN**: global `svg` width, height, stroke or fill rules. They also affect logos, charts, cropping overlays and other non-icon graphics. Nuxt's path-aware component naming remains enabled; standalone application icons use `UiIcon`, while Nuxt UI controls render their own internal icons.
120
+
121
+ ## Color and Brand Assets
122
+
123
+ Functional icons inherit `currentColor`. Use the same semantic foreground as adjacent text, or an explicit semantic role such as `text-primary`, `text-success` or `text-warning`. Hover, active, disabled and focus states come from the control/theme owner. A status must also have recognizable shape or text; color alone is insufficient.
124
+
125
+ Verify foreground/background contrast in each supported theme. Meaningful interface icons must reach at least 3:1 against adjacent colors under [WCAG non-text contrast](https://www.w3.org/WAI/WCAG22/Understanding/non-text-contrast.html); ordinary paired text requires at least 4.5:1. Aim for 4.5:1 for icons where practical, following the stronger [Carbon guidance](https://carbondesignsystem.com/elements/icons/usage/). Use the product's accessible interaction token instead of assuming its logo color is suitable for a small control. Inactive controls are exempt from the non-text contrast requirement, but their disabled state must remain understandable within the labeled control.
126
+
127
+ Third-party identities are local `brand:*` assets, including `brand:apple`, `brand:android` and `brand:whatsapp`. Preserve their proportions and any required identity colors; monochrome variants inherit the foreground only when the supplied asset supports that treatment. Product logos use the product's approved assets and brand specification. **FORBIDDEN**: recoloring a provider logo as a generic status icon, forcing logos into Lucide geometry or embedding a product's palette in shared icon defaults.
128
+
129
+ Local brand files use concise kebab-case names under `app/assets/icons/brand/`. Nuxt config registers that collection. Do not restore obsolete `common:*` names, slash-name conversion, compatibility aliases or global SVG symbol injection.
130
+
131
+ ## Accessible Names and Interaction
132
+
133
+ `UiIcon` is decorative by default: `aria-hidden="true"` and `focusable="false"`. A non-empty `label` exposes `role="img"` and an accessible name. The label describes meaning, not the icon's filename. These defaults implement the distinctions in the [Lucide accessibility guide](https://lucide.dev/how-to/accessibility).
134
+
135
+ ✅ **Nearby text owns the meaning; the icon stays decorative.**
136
+
137
+ ```vue
138
+ <span class="inline-flex items-center gap-2 text-success">
139
+ <UiIcon name="i-lucide-circle-check" />
140
+ Ready
141
+ </span>
142
+ ```
143
+
144
+ ✅ **A standalone semantic icon receives a localized label.**
145
+
146
+ ```vue
147
+ <UiIcon name="i-lucide-circle-check" :label="t('status.ready')" class="text-success" />
148
+ ```
149
+
150
+ ✅ **An icon action is labeled on its semantic button.**
151
+
152
+ ```vue
153
+ <UiButton
154
+ square
155
+ color="neutral"
156
+ variant="ghost"
157
+ icon="i-lucide-x"
158
+ :aria-label="t('actions.close')"
159
+ @click="close"
160
+ />
161
+ ```
162
+
163
+ ❌ **A labeled picture is not a keyboard-operable, named action.**
164
+
165
+ ```vue
166
+ <UiIcon name="i-lucide-x" label="Close" @click="close" />
167
+ ```
168
+
169
+ Use the existing locale owner for labels, visible focus on controls and the component's keyboard behavior. A tooltip supplements a control's name; it does not replace that name. Do not add `label` to an icon that repeats its button's accessible name or adjacent text.
170
+
171
+ ## Additions and Verification
172
+
173
+ 1. Search existing component, navigation and state owners for the same meaning; reuse the established Lucide name and component before adding an asset.
174
+ 2. Confirm the chosen name exists in the installed local collection. Keep `fallbackToApi: false`; an unbundled icon is a missing dependency or registration, not a reason to fetch a public icon API.
175
+ 3. Review any new brand asset for ownership, intrinsic dimensions, aspect ratio and identity color. Keep product-only assets in the product. A genuinely missing functional symbol requires an explicit design decision before another collection is introduced.
176
+ 4. Keep glyph size, control hit target and text alignment separate. Inspect optical weight at 16/20/24/32px, the ordinary 20px default, an explicit override and the component's actual size variants.
177
+ 5. Verify decorative versus meaningful accessibility, keyboard action/focus, status text, loading/disabled states, light/dark contrast and narrow layout. Inspect actual rendered SVG dimensions and viewBox; a successful typecheck alone is not visual evidence.
178
+ 6. For shared-source changes, verify source → registry → packed CLI → externally generated application. The generated `/ui` catalog provides default, size, semantic-label, brand and control examples. Application-specific routes verify their own composition.
179
+
180
+ ### Acceptance Checklist
181
+
182
+ - [ ] Marketing CTAs and product actions use the same controls with the appropriate size and caller-owned width.
183
+ - [ ] Actual control heights, typography, padding, radii, border/focus states and narrow-view input text match the shared contract.
184
+ - [ ] Functional icons come from the installed Lucide family; brand assets remain separate.
185
+ - [ ] The 20px default, selected size tokens and explicit `size` override render correctly without competing size declarations.
186
+ - [ ] Source geometry and proportional strokes are preserved; no arbitrary per-icon offsets or global SVG overrides exist.
187
+ - [ ] Icon-only controls retain at least 44 × 44px targets and visible keyboard focus.
188
+ - [ ] Decorative icons are silent; standalone meaning and actions have names at the correct owner.
189
+ - [ ] Status remains understandable without color, and supported themes have adequate contrast.
190
+ - [ ] Icons resolve from local assets/bundles with no external icon API fallback.
191
+ - [ ] Changed shared assets, rules and defaults survive packed CLI generation and the generated app's checks.
@@ -1,7 +1,9 @@
1
1
  import siteUiTheme from "./theme";
2
+ import icon from "./theme/icon";
2
3
 
3
4
  export default defineAppConfig({
4
5
  ui: siteUiTheme,
6
+ icon,
5
7
  siteShell: {
6
8
  name: "{{projectName}}",
7
9
  descriptionKey: "site.description",
@@ -50,7 +50,7 @@ useSeo({
50
50
  <span>Output mix</span>
51
51
  <ul class="workspace-page__formats">
52
52
  <li v-for="format in outputFormats" :key="format">
53
- <UIcon name="i-lucide-check" />
53
+ <UiIcon name="i-lucide-check" class="text-primary" />
54
54
  {{ format }}
55
55
  </li>
56
56
  </ul>
@@ -119,7 +119,13 @@ useSeo({
119
119
  gap: 18px;
120
120
  border: 1px solid var(--site-color-border);
121
121
  border-radius: var(--site-radius-lg);
122
- background: linear-gradient(180deg, rgb(168 101 80 / 4%), transparent), var(--site-color-surface);
122
+ background:
123
+ linear-gradient(
124
+ 180deg,
125
+ color-mix(in srgb, var(--site-color-primary) 4%, transparent),
126
+ transparent
127
+ ),
128
+ var(--site-color-surface);
123
129
  padding: 20px;
124
130
  }
125
131
 
@@ -148,7 +154,7 @@ useSeo({
148
154
  gap: 12px;
149
155
  border: 1px solid var(--site-color-border);
150
156
  border-radius: var(--site-radius-md);
151
- background: rgb(168 101 80 / 4%);
157
+ background: color-mix(in srgb, var(--site-color-primary) 4%, transparent);
152
158
  padding: 14px;
153
159
  }
154
160
 
@@ -193,10 +199,6 @@ useSeo({
193
199
  color: var(--site-color-muted);
194
200
  }
195
201
 
196
- .workspace-page__formats svg {
197
- color: var(--site-color-primary);
198
- }
199
-
200
202
  .workspace-page__metric strong {
201
203
  color: var(--site-color-primary);
202
204
  font-size: 58px;
@@ -64,12 +64,12 @@ useSeo({
64
64
  to="/ui"
65
65
  >
66
66
  View workflow
67
- <UIcon name="i-lucide-play" />
67
+ <UiIcon name="i-lucide-play" class="size-icon-sm" />
68
68
  </NuxtLink>
69
69
  </div>
70
70
  <div class="proof-list" aria-label="Product proof">
71
71
  <div v-for="item in proofItems" :key="item.label" class="proof-list__item">
72
- <UIcon :name="item.icon" />
72
+ <UiIcon :name="item.icon" class="text-secondary" />
73
73
  <span>{{ item.label }}</span>
74
74
  </div>
75
75
  </div>
@@ -91,11 +91,11 @@ useSeo({
91
91
  {{ panel.label }}
92
92
  </span>
93
93
  <span class="hero-gallery__video hero-gallery__video--vertical">
94
- <UIcon name="i-lucide-play" />
94
+ <UiIcon name="i-lucide-play" class="size-icon-sm" />
95
95
  0:15
96
96
  </span>
97
97
  <span class="hero-gallery__video hero-gallery__video--wide">
98
- <UIcon name="i-lucide-play" />
98
+ <UiIcon name="i-lucide-play" class="size-icon-sm" />
99
99
  0:08
100
100
  </span>
101
101
  </div>
@@ -105,7 +105,7 @@ useSeo({
105
105
  {{ ratio }}
106
106
  </button>
107
107
  <button type="button" aria-label="Add ratio">
108
- <UIcon name="i-lucide-plus" />
108
+ <UiIcon name="i-lucide-plus" />
109
109
  </button>
110
110
  </div>
111
111
  </div>
@@ -219,15 +219,10 @@ useSeo({
219
219
  }
220
220
 
221
221
  .hero-section__secondary-action {
222
- border-color: rgb(168 101 80 / 42%);
222
+ border-color: color-mix(in srgb, var(--site-color-primary) 42%, transparent);
223
223
  color: var(--site-color-accent-warm);
224
224
  }
225
225
 
226
- .hero-section__secondary-action svg {
227
- width: 16px;
228
- height: 16px;
229
- }
230
-
231
226
  .proof-list {
232
227
  display: grid;
233
228
  max-width: 560px;
@@ -253,12 +248,6 @@ useSeo({
253
248
  padding-left: 0;
254
249
  }
255
250
 
256
- .proof-list__item svg {
257
- width: 22px;
258
- height: 22px;
259
- color: var(--site-color-accent);
260
- }
261
-
262
251
  .hero-section__media {
263
252
  display: grid;
264
253
  min-width: 0;
@@ -308,11 +297,6 @@ useSeo({
308
297
  backdrop-filter: blur(10px);
309
298
  }
310
299
 
311
- .hero-gallery__video svg {
312
- width: 16px;
313
- height: 16px;
314
- }
315
-
316
300
  .hero-gallery__label--before {
317
301
  top: 4%;
318
302
  left: 2%;
@@ -375,7 +359,7 @@ useSeo({
375
359
  min-height: 34px;
376
360
  align-items: center;
377
361
  justify-content: center;
378
- border: 1px solid rgb(168 101 80 / 28%);
362
+ border: 1px solid color-mix(in srgb, var(--site-color-primary) 28%, transparent);
379
363
  border-radius: var(--site-radius-sm);
380
364
  background: rgb(255 255 255 / 82%);
381
365
  color: var(--site-color-primary);
@@ -384,13 +368,14 @@ useSeo({
384
368
  }
385
369
 
386
370
  .ratio-toolbar button:hover {
387
- border-color: rgb(168 101 80 / 44%);
388
- background: rgb(168 101 80 / 6%);
371
+ border-color: color-mix(in srgb, var(--site-color-primary) 44%, transparent);
372
+ background: color-mix(in srgb, var(--site-color-primary) 6%, transparent);
389
373
  color: var(--site-color-primary-hover);
390
374
  }
391
375
 
392
376
  .ratio-toolbar button:last-child {
393
- min-width: 34px;
377
+ min-width: 44px;
378
+ min-height: 44px;
394
379
  border-radius: 50%;
395
380
  }
396
381
 
@@ -503,7 +488,7 @@ useSeo({
503
488
  background: var(--site-color-primary);
504
489
  color: var(--site-color-primary-contrast);
505
490
  font-weight: 700;
506
- box-shadow: 0 18px 44px rgb(168 101 80 / 22%);
491
+ box-shadow: 0 18px 44px color-mix(in srgb, var(--site-color-primary) 22%, transparent);
507
492
  }
508
493
 
509
494
  @media (width <= 1100px) {
@@ -6,6 +6,55 @@ const analytics = useAnalytics();
6
6
  const visibleItems = ref(4);
7
7
  const infiniteLoading = ref(false);
8
8
  const checkoutUrlPreview = ref("");
9
+ const previewPage = ref(1);
10
+ const previewRatio = ref("4:5");
11
+ const drawerOpen = ref(false);
12
+ const previewIcons = [
13
+ "i-lucide-house",
14
+ "i-lucide-menu",
15
+ "i-lucide-search",
16
+ "i-lucide-plus",
17
+ "i-lucide-minus",
18
+ "i-lucide-x",
19
+ "i-lucide-check",
20
+ "i-lucide-chevron-down",
21
+ "i-lucide-circle-user",
22
+ "i-lucide-loader-circle",
23
+ "i-lucide-play",
24
+ "i-lucide-pause",
25
+ "i-lucide-headset",
26
+ "i-lucide-message-circle",
27
+ "i-lucide-star",
28
+ "i-lucide-arrow-up",
29
+ "i-lucide-triangle-alert",
30
+ "i-lucide-settings-2",
31
+ "i-lucide-image",
32
+ "i-lucide-shield-check",
33
+ ];
34
+ const previewIconSizes = [
35
+ { label: "16 · Inline", class: "size-icon-sm" },
36
+ { label: "20 · Default", class: "size-icon-md" },
37
+ { label: "24 · Prominent", class: "size-icon-lg" },
38
+ { label: "32 · Status", class: "size-icon-xl" },
39
+ ];
40
+ const previewSteps = [
41
+ {
42
+ title: "Add a source",
43
+ description: "Choose the image you want to work with.",
44
+ icon: "i-lucide-image",
45
+ },
46
+ {
47
+ title: "Describe the result",
48
+ description: "Set the scene, light, and output format.",
49
+ icon: "i-lucide-pencil",
50
+ },
51
+ {
52
+ title: "Review your work",
53
+ description: "Compare the results and choose a direction.",
54
+ icon: "i-lucide-check",
55
+ },
56
+ ];
57
+ const previewImages = previewSteps.map((step) => ({ src: "/favicon.svg", alt: step.title }));
9
58
 
10
59
  const infiniteItems = Array.from({ length: 10 }, (_, index) => ({
11
60
  id: `asset-${index + 1}`,
@@ -121,7 +170,7 @@ useSeo({
121
170
  title="Approve campaign set"
122
171
  description="Use slots for business-specific body and footer content."
123
172
  >
124
- <template #content>
173
+ <template #body>
125
174
  <UiText as="p" tone="muted">
126
175
  Campaign output is ready for review. The consuming product owns approval rules and
127
176
  provider fulfillment.
@@ -230,7 +279,166 @@ useSeo({
230
279
  Multi-line campaign summaries can be clamped by the reusable text component so compact
231
280
  cards, popovers, and tables remain stable.
232
281
  </UiText>
282
+ <UiText auto-shrink class="block max-w-48" :min-font-size="10"
283
+ >Automatically fitted text keeps its full label visible.</UiText
284
+ >
285
+ </div>
286
+ </section>
287
+
288
+ <section class="system-lab__panel system-lab__panel--wide">
289
+ <UiText as="h2" size="xl" weight="bold">Complete theme controls</UiText>
290
+ <div class="flex flex-wrap items-center gap-4">
291
+ <UiButton
292
+ v-for="size in ['xs', 'sm', 'md', 'lg', 'xl', '2xl'] as const"
293
+ :key="size"
294
+ :size="size"
295
+ >{{ size }}</UiButton
296
+ >
297
+ </div>
298
+ <UBreadcrumb :items="[{ label: 'Home', to: '/' }, { label: 'Components' }]" />
299
+ <UFormField label="Output notes"
300
+ ><UTextarea placeholder="Describe the output"
301
+ /></UFormField>
302
+ <URadioGroup
303
+ v-model="previewRatio"
304
+ :items="['4:5', '1:1', '16:9']"
305
+ orientation="horizontal"
306
+ variant="card"
307
+ />
308
+ <UPagination v-model:page="previewPage" :total="100" />
309
+ <UTooltip text="Shared tooltip theme"
310
+ ><UiButton color="neutral" variant="outline">Tooltip example</UiButton></UTooltip
311
+ >
312
+ <UiButton color="neutral" variant="outline" @click="drawerOpen = true"
313
+ >Open drawer</UiButton
314
+ >
315
+ <UDrawer
316
+ v-model:open="drawerOpen"
317
+ title="Output settings"
318
+ description="Responsive drawer controls"
319
+ >
320
+ <template #body
321
+ ><UFormField label="File name"><UInput placeholder="Product image" /></UFormField
322
+ ></template>
323
+ <template #footer
324
+ ><UiButton block @click="drawerOpen = false">Apply settings</UiButton></template
325
+ >
326
+ </UDrawer>
327
+ </section>
328
+
329
+ <section class="system-lab__panel">
330
+ <UiText as="h2" size="xl" weight="bold">Media and icons</UiText>
331
+ <p class="text-sm text-muted">
332
+ Lucide · 24 × 24 canvas · 2-unit round stroke · 20px default
333
+ </p>
334
+ <div class="grid grid-cols-4 items-end gap-3 text-center text-xs" data-icon-sizes>
335
+ <div
336
+ v-for="item in previewIconSizes"
337
+ :key="item.label"
338
+ class="flex flex-col items-center gap-2"
339
+ >
340
+ <UiIcon name="i-lucide-image" :class="item.class" />
341
+ <span>{{ item.label }}</span>
342
+ </div>
343
+ </div>
344
+ <div class="flex flex-wrap items-center gap-4 text-sm" data-icon-overrides>
345
+ <span class="inline-flex items-center gap-2"
346
+ ><UiIcon name="i-lucide-image" />Default</span
347
+ >
348
+ <span class="inline-flex items-center gap-2"
349
+ ><UiIcon name="i-lucide-image" :size="24" />size=24</span
350
+ >
351
+ <span class="inline-flex items-center gap-2"
352
+ ><UiIcon name="i-lucide-image" size="2rem" />size=2rem</span
353
+ >
354
+ <UiIcon name="i-lucide-circle-check" label="Ready" class="text-success" />
355
+ </div>
356
+ <div class="grid grid-cols-4 gap-4 text-center text-xs" data-icon-catalog>
357
+ <div
358
+ v-for="icon in previewIcons"
359
+ :key="icon"
360
+ class="flex min-w-0 flex-col items-center gap-2"
361
+ >
362
+ <UiIcon :name="icon" class="text-primary" />
363
+ <span class="break-words">{{ icon.replace("i-lucide-", "") }}</span>
364
+ </div>
365
+ </div>
366
+ <div class="flex items-center gap-4" data-brand-icons>
367
+ <UiIcon name="brand:apple" label="Apple" class="size-icon-lg" />
368
+ <UiIcon name="brand:android" label="Android" class="size-icon-lg" />
369
+ <UiIcon name="brand:whatsapp" label="WhatsApp" class="size-icon-lg" />
370
+ <span class="text-sm text-muted">Brand proportions and colors</span>
233
371
  </div>
372
+ <UiVideo
373
+ src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4"
374
+ :autoplay="false"
375
+ :loop="false"
376
+ :muted="true"
377
+ class="aspect-video"
378
+ />
379
+ </section>
380
+
381
+ <section class="system-lab__panel">
382
+ <UiText as="h2" size="xl" weight="bold">Countdown and progress</UiText>
383
+ <CountdownTimer
384
+ :duration="600"
385
+ storage-key="component-countdown"
386
+ :show-milliseconds="true"
387
+ :update-interval="100"
388
+ />
389
+ <StepsProgress :items="['Source', 'Settings', 'Review']" :current-step="2" />
390
+ <ContentCompatibility :icons="['brand:apple', 'brand:android']" label="Platform icons" />
391
+ </section>
392
+
393
+ <section class="system-lab__panel system-lab__panel--wide">
394
+ <ContentFaq
395
+ title="Questions and answers"
396
+ :items="[
397
+ {
398
+ question: 'Can several answers stay open?',
399
+ answer: 'Yes. Each column supports multiple expanded answers.',
400
+ },
401
+ {
402
+ question: 'Does it work with a keyboard?',
403
+ answer: 'Use Tab to reach a question and Enter or Space to expand it.',
404
+ },
405
+ {
406
+ question: 'Can answers include custom content?',
407
+ answer: 'The answer slot accepts links, images, and other components.',
408
+ },
409
+ ]"
410
+ />
411
+ <StepsShowcase
412
+ title="Three steps"
413
+ :steps="previewSteps"
414
+ :images="previewImages"
415
+ :autoplay="false"
416
+ />
417
+ <ContentReviews
418
+ title="Review cards"
419
+ :reviews="[
420
+ [
421
+ {
422
+ author: 'Sample author, Design team',
423
+ avatar: '/favicon.svg',
424
+ description:
425
+ 'Example review text for verifying card dimensions, typography, and motion.',
426
+ },
427
+ {
428
+ author: 'Sample reviewer',
429
+ avatar: '/favicon.svg',
430
+ description: 'Hover or focus the row to pause its movement.',
431
+ },
432
+ ],
433
+ ]"
434
+ />
435
+ <ContentCallToAction
436
+ title="Bring your next idea to life"
437
+ description="A reusable section with project-owned copy, logo, and destination."
438
+ button-label="Back to Home"
439
+ to="/"
440
+ :logo="{ src: '/favicon.svg', alt: 'Application icon' }"
441
+ />
234
442
  </section>
235
443
  </div>
236
444
  </section>
@@ -285,7 +493,13 @@ useSeo({
285
493
  gap: 18px;
286
494
  border: 1px solid var(--site-color-border);
287
495
  border-radius: var(--site-radius-lg);
288
- background: linear-gradient(180deg, rgb(168 101 80 / 4%), transparent), var(--site-color-surface);
496
+ background:
497
+ linear-gradient(
498
+ 180deg,
499
+ color-mix(in srgb, var(--site-color-primary) 4%, transparent),
500
+ transparent
501
+ ),
502
+ var(--site-color-surface);
289
503
  padding: 22px;
290
504
  }
291
505
 
@@ -321,11 +535,13 @@ useSeo({
321
535
  }
322
536
 
323
537
  .system-lab__actions {
324
- grid-template-columns: repeat(3, minmax(0, max-content));
538
+ display: flex;
539
+ flex-wrap: wrap;
325
540
  align-items: center;
326
541
  }
327
542
 
328
543
  .system-lab__actions--stacked {
544
+ display: grid;
329
545
  grid-template-columns: 1fr;
330
546
  justify-items: start;
331
547
  }
@@ -45,11 +45,9 @@ export default defineNuxtConfig({
45
45
  },
46
46
  },
47
47
  icon: {
48
+ serverBundle: { collections: ["lucide"] },
49
+ fallbackToApi: false,
48
50
  customCollections: [
49
- {
50
- prefix: "common",
51
- dir: "./app/assets/icons/common",
52
- },
53
51
  {
54
52
  prefix: "brand",
55
53
  dir: "./app/assets/icons/brand",
@@ -31,7 +31,8 @@
31
31
  "@stackonward/api-client": "^0.0.3",
32
32
  "@stackonward/google-tag-manager-nuxt": "^0.3.3",
33
33
  "@stackonward/nuxt-layer-base": "^0.0.4",
34
- "@stackonward/nuxt-site-shell": "^0.3.2"
34
+ "@stackonward/nuxt-site-shell": "^0.3.2",
35
+ "@stackonward/composables": "^0.0.2"
35
36
  },
36
37
  "devDependencies": {
37
38
  "@commitlint/cli": "^21.2.2",