@stackonward/cli 1.5.2 → 1.6.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.
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/resources/components/nuxt/site-source/app/assets/css/site-shell.css +112 -56
- package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/android.svg +10 -0
- package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/apple.svg +3 -0
- package/dist/resources/components/nuxt/site-source/app/assets/icons/brand/whatsapp.svg +5 -0
- package/dist/resources/components/nuxt/site-source/app/components/app/Sidebar.vue +1 -1
- package/dist/resources/components/nuxt/site-source/app/components/checkout/Status.vue +3 -3
- package/dist/resources/components/nuxt/site-source/app/components/consent/Banner.vue +23 -8
- package/dist/resources/components/nuxt/site-source/app/components/content/CallToAction.vue +59 -0
- package/dist/resources/components/nuxt/site-source/app/components/content/Card.vue +35 -0
- package/dist/resources/components/nuxt/site-source/app/components/content/Compatibility.vue +10 -0
- package/dist/resources/components/nuxt/site-source/app/components/content/Faq.vue +56 -0
- package/dist/resources/components/nuxt/site-source/app/components/content/Features.vue +8 -0
- package/dist/resources/components/nuxt/site-source/app/components/content/List.vue +1 -8
- package/dist/resources/components/nuxt/site-source/app/components/content/Reviews.vue +93 -0
- package/dist/resources/components/nuxt/site-source/app/components/countdown/Timer.vue +112 -0
- package/dist/resources/components/nuxt/site-source/app/components/locale/Menu.vue +3 -3
- package/dist/resources/components/nuxt/site-source/app/components/site/BackToTop.vue +30 -23
- package/dist/resources/components/nuxt/site-source/app/components/site/Header.vue +2 -2
- package/dist/resources/components/nuxt/site-source/app/components/steps/Progress.vue +36 -0
- package/dist/resources/components/nuxt/site-source/app/components/steps/Showcase.vue +340 -0
- package/dist/resources/components/nuxt/site-source/app/components/theme/Menu.vue +4 -4
- package/dist/resources/components/nuxt/site-source/app/components/ui/Button.vue +28 -2
- package/dist/resources/components/nuxt/site-source/app/components/ui/GradientBorder.vue +5 -5
- package/dist/resources/components/nuxt/site-source/app/components/ui/Icon.vue +14 -0
- package/dist/resources/components/nuxt/site-source/app/components/ui/Image.vue +126 -0
- package/dist/resources/components/nuxt/site-source/app/components/ui/Marquee.vue +99 -0
- package/dist/resources/components/nuxt/site-source/app/components/ui/Modal.vue +26 -88
- package/dist/resources/components/nuxt/site-source/app/components/ui/Popover.vue +87 -93
- package/dist/resources/components/nuxt/site-source/app/components/ui/Text.vue +84 -12
- package/dist/resources/components/nuxt/site-source/app/components/ui/Video.vue +105 -0
- package/dist/resources/components/nuxt/site-source/app/components/whatsapp/Chat.vue +126 -0
- package/dist/resources/components/nuxt/site-source/app/components/youtube/Embed.vue +286 -73
- package/dist/resources/components/nuxt/site-source/app/composables/useFloatingBottomStack.ts +44 -0
- package/dist/resources/components/nuxt/site-source/app/composables/useNavigation.ts +6 -8
- package/dist/resources/components/nuxt/site-source/app/consent/types.ts +1 -4
- package/dist/resources/components/nuxt/site-source/app/content/types.ts +3 -0
- package/dist/resources/components/nuxt/site-source/app/theme/accordion.ts +5 -4
- package/dist/resources/components/nuxt/site-source/app/theme/breadcrumb.ts +14 -4
- package/dist/resources/components/nuxt/site-source/app/theme/button.ts +138 -28
- package/dist/resources/components/nuxt/site-source/app/theme/carousel.ts +11 -7
- package/dist/resources/components/nuxt/site-source/app/theme/checkbox.ts +9 -3
- package/dist/resources/components/nuxt/site-source/app/theme/drawer.ts +6 -0
- package/dist/resources/components/nuxt/site-source/app/theme/form-field.ts +8 -0
- package/dist/resources/components/nuxt/site-source/app/theme/icon.ts +6 -0
- package/dist/resources/components/nuxt/site-source/app/theme/index.ts +8 -1
- package/dist/resources/components/nuxt/site-source/app/theme/input.ts +34 -18
- package/dist/resources/components/nuxt/site-source/app/theme/modal.ts +9 -5
- package/dist/resources/components/nuxt/site-source/app/theme/pagination.ts +9 -2
- package/dist/resources/components/nuxt/site-source/app/theme/radio-group.ts +7 -0
- package/dist/resources/components/nuxt/site-source/app/theme/select.ts +19 -7
- package/dist/resources/components/nuxt/site-source/app/theme/stepper.ts +15 -6
- package/dist/resources/components/nuxt/site-source/app/theme/textarea.ts +6 -0
- package/dist/resources/components/nuxt/site-source/app/theme/toast.ts +1 -4
- package/dist/resources/components/nuxt/site-source/app/theme/tooltip.ts +5 -1
- package/dist/resources/components/registry.json +133 -45
- package/dist/resources/components/vue/ui/BottomSheet.vue +5 -1
- package/dist/resources/components/vue/ui/ScrollArea.vue +2 -1
- package/dist/resources/registry.json +4 -4
- package/dist/resources/templates/base/.claude/docs/architecture-template.md +2 -1
- package/dist/resources/templates/base/AGENTS.md +7 -0
- package/dist/resources/templates/base/CLAUDE.md +7 -0
- package/dist/resources/templates/nuxt/.claude/docs/stack.md +14 -1
- package/dist/resources/templates/nuxt/.vscode/extensions.json +9 -0
- package/dist/resources/templates/nuxt/.vscode/settings.json +21 -0
- package/dist/resources/templates/nuxt/.zed/settings.json +42 -0
- package/dist/resources/templates/nuxt/DESIGN.md +146 -0
- package/dist/resources/templates/nuxt/README.md +10 -0
- package/dist/resources/templates/nuxt/app/app.config.ts +2 -0
- package/dist/resources/templates/nuxt/app/pages/app/index.vue +9 -7
- package/dist/resources/templates/nuxt/app/pages/index.vue +12 -27
- package/dist/resources/templates/nuxt/app/pages/ui/index.vue +219 -3
- package/dist/resources/templates/nuxt/nuxt.config.ts +2 -4
- package/dist/resources/templates/nuxt/package.json +4 -3
- package/dist/resources/templates/nuxt/stylelint.config.mjs +12 -0
- package/package.json +2 -2
- package/dist/resources/components/nuxt/site-source/app/assets/icons/common/.gitkeep +0 -0
|
@@ -0,0 +1,146 @@
|
|
|
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-icon-*` size tokens, `size-icon-*` 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`, `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
|
+
## Functional Icon Geometry
|
|
29
|
+
|
|
30
|
+
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).
|
|
31
|
+
|
|
32
|
+
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.
|
|
33
|
+
|
|
34
|
+
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.
|
|
35
|
+
|
|
36
|
+
## Size Scale
|
|
37
|
+
|
|
38
|
+
Values use rem and therefore follow the user's root text size. Pixel equivalents below assume a 16px root.
|
|
39
|
+
|
|
40
|
+
| Token / utility | Value | Intended use |
|
|
41
|
+
| ----------------------------------- | -------------- | -------------------------------------------------------- |
|
|
42
|
+
| `--site-icon-xs` / `size-icon-xs` | 0.75rem / 12px | Internal checkbox mark only |
|
|
43
|
+
| `--site-icon-sm` / `size-icon-sm` | 1rem / 16px | Compact inline or control icon |
|
|
44
|
+
| `--site-icon-md` / `size-icon-md` | 1.25rem / 20px | Default functional icon, navigation and ordinary actions |
|
|
45
|
+
| `--site-icon-lg` / `size-icon-lg` | 1.5rem / 24px | Prominent control or section icon |
|
|
46
|
+
| `--site-icon-xl` / `size-icon-xl` | 2rem / 32px | Status or feature illustration |
|
|
47
|
+
| `--site-icon-2xl` / `size-icon-2xl` | 2.5rem / 40px | Large status or feature illustration |
|
|
48
|
+
| `--site-icon-3xl` / `size-icon-3xl` | 3rem / 48px | Large empty-state or explanatory illustration |
|
|
49
|
+
|
|
50
|
+
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.
|
|
51
|
+
|
|
52
|
+
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.
|
|
53
|
+
|
|
54
|
+
## Component API and Overrides
|
|
55
|
+
|
|
56
|
+
`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)`.
|
|
57
|
+
|
|
58
|
+
✅ **Use the shared default or one explicit size mechanism.**
|
|
59
|
+
|
|
60
|
+
```vue
|
|
61
|
+
<UiIcon name="i-lucide-image" />
|
|
62
|
+
<UiIcon name="i-lucide-image" class="size-icon-lg" />
|
|
63
|
+
<UiIcon name="i-lucide-image" :size="24" />
|
|
64
|
+
<UiIcon name="i-lucide-image" size="var(--site-icon-xl)" />
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`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.
|
|
68
|
+
|
|
69
|
+
❌ **Competing sizes and page-owned artwork bypass the shared contract.**
|
|
70
|
+
|
|
71
|
+
```vue
|
|
72
|
+
<UiIcon name="i-lucide-image" :size="18" class="size-icon-lg" />
|
|
73
|
+
<svg viewBox="0 0 32 20" class="h-4 w-7" stroke-width="3" />
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**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.
|
|
77
|
+
|
|
78
|
+
## Color and Brand Assets
|
|
79
|
+
|
|
80
|
+
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.
|
|
81
|
+
|
|
82
|
+
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.
|
|
83
|
+
|
|
84
|
+
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.
|
|
85
|
+
|
|
86
|
+
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.
|
|
87
|
+
|
|
88
|
+
## Accessible Names and Interaction
|
|
89
|
+
|
|
90
|
+
`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).
|
|
91
|
+
|
|
92
|
+
✅ **Nearby text owns the meaning; the icon stays decorative.**
|
|
93
|
+
|
|
94
|
+
```vue
|
|
95
|
+
<span class="inline-flex items-center gap-2 text-success">
|
|
96
|
+
<UiIcon name="i-lucide-circle-check" />
|
|
97
|
+
Ready
|
|
98
|
+
</span>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
✅ **A standalone semantic icon receives a localized label.**
|
|
102
|
+
|
|
103
|
+
```vue
|
|
104
|
+
<UiIcon name="i-lucide-circle-check" :label="t('status.ready')" class="text-success" />
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
✅ **An icon action is labeled on its semantic button.**
|
|
108
|
+
|
|
109
|
+
```vue
|
|
110
|
+
<UiButton
|
|
111
|
+
square
|
|
112
|
+
color="neutral"
|
|
113
|
+
variant="ghost"
|
|
114
|
+
icon="i-lucide-x"
|
|
115
|
+
:aria-label="t('actions.close')"
|
|
116
|
+
@click="close"
|
|
117
|
+
/>
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
❌ **A labeled picture is not a keyboard-operable, named action.**
|
|
121
|
+
|
|
122
|
+
```vue
|
|
123
|
+
<UiIcon name="i-lucide-x" label="Close" @click="close" />
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
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.
|
|
127
|
+
|
|
128
|
+
## Additions and Verification
|
|
129
|
+
|
|
130
|
+
1. Search existing component, navigation and state owners for the same meaning; reuse the established Lucide name and component before adding an asset.
|
|
131
|
+
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.
|
|
132
|
+
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.
|
|
133
|
+
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.
|
|
134
|
+
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.
|
|
135
|
+
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.
|
|
136
|
+
|
|
137
|
+
### Acceptance Checklist
|
|
138
|
+
|
|
139
|
+
- [ ] Functional icons come from the installed Lucide family; brand assets remain separate.
|
|
140
|
+
- [ ] The 20px default, selected size tokens and explicit `size` override render correctly without competing size declarations.
|
|
141
|
+
- [ ] Source geometry and proportional strokes are preserved; no arbitrary per-icon offsets or global SVG overrides exist.
|
|
142
|
+
- [ ] Icon-only controls retain at least 44 × 44px targets and visible keyboard focus.
|
|
143
|
+
- [ ] Decorative icons are silent; standalone meaning and actions have names at the correct owner.
|
|
144
|
+
- [ ] Status remains understandable without color, and supported themes have adequate contrast.
|
|
145
|
+
- [ ] Icons resolve from local assets/bundles with no external icon API fallback.
|
|
146
|
+
- [ ] Changed shared assets, rules and defaults survive packed CLI generation and the generated app's checks.
|
|
@@ -40,3 +40,13 @@ pnpm check
|
|
|
40
40
|
`pnpm check` verifies formatting, TypeScript/Vue linting, CSS and Vue-style linting, Nuxt type safety, route rendering and the production build. Husky installs repository-owned `pre-commit` and `commit-msg` hooks during dependency installation. GitHub Actions runs the same gate from the committed lockfile.
|
|
41
41
|
|
|
42
42
|
Use `pnpm lint:code:fix`, `pnpm lint:styles:fix` and `pnpm format` for explicit local fixes. Shared policy comes from versioned `@stackonward/*-config` packages; the root composition files remain application-owned.
|
|
43
|
+
|
|
44
|
+
VS Code and Cursor use the included `.vscode/settings.json`. Install the recommended
|
|
45
|
+
workspace extensions to run ESLint and Stylelint fixes followed by Prettier on save.
|
|
46
|
+
Zed uses `.zed/settings.json`; install its Vue extension for Vue files. Vue,
|
|
47
|
+
TypeScript and JavaScript saves run ESLint fixes and the project's local Prettier.
|
|
48
|
+
|
|
49
|
+
`pnpm format` runs ESLint fixes before Prettier across the project. Vue top-level
|
|
50
|
+
blocks span multiple lines, with a blank line between blocks. The pre-commit hook
|
|
51
|
+
formats staged files, and CI rejects format or lint violations using the same
|
|
52
|
+
project configuration.
|
|
@@ -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
|
-
<
|
|
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:
|
|
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:
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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
|
-
<
|
|
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:
|
|
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
|
|
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:
|
|
388
|
-
background:
|
|
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:
|
|
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
|
|
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 #
|
|
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:
|
|
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
|
-
|
|
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",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"lint:code:fix": "eslint . --fix --max-warnings=0",
|
|
21
21
|
"lint:styles": "stylelint \"app/**/*.{css,vue}\"",
|
|
22
22
|
"lint:styles:fix": "stylelint \"app/**/*.{css,vue}\" --fix",
|
|
23
|
-
"format": "prettier --write .",
|
|
23
|
+
"format": "pnpm lint:code:fix && prettier --write .",
|
|
24
24
|
"format:check": "prettier --check .",
|
|
25
25
|
"test": "vitest run",
|
|
26
26
|
"test:watch": "vitest",
|
|
@@ -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.
|
|
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",
|
|
@@ -46,7 +47,7 @@
|
|
|
46
47
|
"@pinia/colada-nuxt": "^1.0.1",
|
|
47
48
|
"@pinia/nuxt": "^0.11.3",
|
|
48
49
|
"@stackonward/commitlint-config": "^0.1.1",
|
|
49
|
-
"@stackonward/eslint-config": "^1.0.
|
|
50
|
+
"@stackonward/eslint-config": "^1.0.2",
|
|
50
51
|
"@stackonward/prettier-config": "^0.0.3",
|
|
51
52
|
"@stackonward/stylelint-config": "^0.1.1",
|
|
52
53
|
"@types/node": "^22.0.0",
|
|
@@ -1,3 +1,15 @@
|
|
|
1
1
|
export default {
|
|
2
2
|
extends: ["@stackonward/stylelint-config"],
|
|
3
|
+
overrides: [
|
|
4
|
+
{
|
|
5
|
+
files: ["app/**/*.vue"],
|
|
6
|
+
customSyntax: "postcss-html",
|
|
7
|
+
rules: {
|
|
8
|
+
"selector-pseudo-class-no-unknown": [
|
|
9
|
+
true,
|
|
10
|
+
{ ignorePseudoClasses: ["deep", "global", "slotted"] },
|
|
11
|
+
],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
],
|
|
3
15
|
};
|