@sudajs/cli 0.18.10 → 0.19.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.d.ts +2569 -69
- package/dist/index.js +609 -469
- package/dist/index.js.map +1 -1
- package/package.json +2 -3
- package/templates/theme/AGENTS.md +91 -11
- package/templates/theme/README.md +14 -0
- package/templates/theme/docs/agent-guides/component-authoring.md +11 -2
- package/templates/theme/docs/agent-guides/design-and-runtime.md +23 -8
- package/templates/theme/src/manifest.ts +6 -0
- package/templates/theme/src/sections.tsx +16 -11
- package/templates/theme/src/styles.css +40 -20
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sudajs/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.19.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"suda": "./bin/suda.js"
|
|
@@ -24,7 +24,6 @@
|
|
|
24
24
|
"access": "public"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
28
27
|
"@puckeditor/core": "^0.22.4",
|
|
29
28
|
"commander": "^12.1.0",
|
|
30
29
|
"esbuild": "^0.25.12",
|
|
@@ -34,7 +33,7 @@
|
|
|
34
33
|
"react": "^19.2.7",
|
|
35
34
|
"react-dom": "^19.2.7",
|
|
36
35
|
"zod": "^3.24.1",
|
|
37
|
-
"@sudajs/theme-engine": "6.
|
|
36
|
+
"@sudajs/theme-engine": "6.6.0"
|
|
38
37
|
},
|
|
39
38
|
"devDependencies": {
|
|
40
39
|
"@tailwindcss/postcss": "^4.3.0",
|
|
@@ -80,6 +80,11 @@ Extract shared code only when multiple sections genuinely reuse it.
|
|
|
80
80
|
names and filler suffixes such as `Section`, `Component`, `New`, or `Custom`.
|
|
81
81
|
- Prefer semantic variant names. Use numbered variants only when the variants
|
|
82
82
|
truly share a purpose and no concise name describes the difference.
|
|
83
|
+
- Every page section must declare `section.motion`. Use `"reveal"` for ordinary
|
|
84
|
+
sections that use the shared viewport entrance animation. Use `"custom"` when
|
|
85
|
+
the section owns its own client animation (timeline, carousel, marquee, or
|
|
86
|
+
scroll-scrub), and use `"none"` when it must remain static. TypeScript will
|
|
87
|
+
report an error when the motion declaration is missing.
|
|
83
88
|
- Keep `fields`, TypeScript props, `defaultProps`, and `render` aligned. Every
|
|
84
89
|
normal field needs a sensible serializable default.
|
|
85
90
|
- Use the most specific field: `url`, `image`, `video`, `media`, `icon`,
|
|
@@ -100,7 +105,8 @@ Default ownership is strict:
|
|
|
100
105
|
<div>{val || "defaultValue"}</div>;
|
|
101
106
|
|
|
102
107
|
// Correct: defaultProps owns the default.
|
|
103
|
-
export const Example:
|
|
108
|
+
export const Example: SudaSectionConfig<ExampleProps> = {
|
|
109
|
+
section: { category: "features", variant: "example", motion: "reveal" },
|
|
104
110
|
fields: { val: { type: "text", label: t("sections.example.fields.val") } },
|
|
105
111
|
defaultProps: { val: "defaultValue" },
|
|
106
112
|
render: ({ val }) => <div>{val}</div>,
|
|
@@ -155,9 +161,71 @@ it must not substitute placeholder copy, labels, links, icons, or menu items.
|
|
|
155
161
|
|
|
156
162
|
- Define one editable design system in `sourceManifest.designSystem`, expose it
|
|
157
163
|
at the layout root, resolve it once there, and render components through the
|
|
158
|
-
resulting
|
|
159
|
-
-
|
|
160
|
-
|
|
164
|
+
resulting unprefixed CSS variables. Do not add or consume `--suda-*` aliases.
|
|
165
|
+
- Set `tokens.layout.contentWidth` in every preset in `src/manifest.ts`. Choose only
|
|
166
|
+
`"1024px"`, `"1200px"`, `"1280px"`, or `"1440px"`:
|
|
167
|
+
|
|
168
|
+
```ts
|
|
169
|
+
tokens: {
|
|
170
|
+
colors: { /* ... */ },
|
|
171
|
+
radius: { /* ... */ },
|
|
172
|
+
layout: { contentWidth: "1280px" },
|
|
173
|
+
}
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
- In the layout root, resolve the selected preset and apply its CSS variables
|
|
177
|
+
to the element that wraps `children`. Use this exact pattern:
|
|
178
|
+
|
|
179
|
+
```tsx
|
|
180
|
+
render: ({ children, designSystem }) => {
|
|
181
|
+
const tokens = resolveThemeDesignTokens(sourceManifest.designSystem, designSystem);
|
|
182
|
+
return (
|
|
183
|
+
<div
|
|
184
|
+
className="my-theme-root"
|
|
185
|
+
style={createThemeDesignCssVariables(tokens)}
|
|
186
|
+
>
|
|
187
|
+
{children}
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
},
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
- Do not set `max-width` on the layout root or `PageOutlet`; doing so prevents
|
|
194
|
+
full-width sections. Apply `--content-max-width` only to inner content
|
|
195
|
+
containers. Wrap Header and Footer contents in that same container when they
|
|
196
|
+
must align with page sections.
|
|
197
|
+
|
|
198
|
+
- Every page section root must include `w-full`. Put the section background,
|
|
199
|
+
border, and full-bleed media on this root. Do not set `max-width` on it.
|
|
200
|
+
- Put normal section content inside one shared container class and define that
|
|
201
|
+
class once in `src/styles.css`:
|
|
202
|
+
|
|
203
|
+
```css
|
|
204
|
+
.my-theme-container {
|
|
205
|
+
width: min(var(--content-max-width), 100%);
|
|
206
|
+
margin-inline: auto;
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- Use the same container inside full-width Hero and band sections when their
|
|
211
|
+
text, buttons, cards, or navigation must align with ordinary sections. Omit
|
|
212
|
+
the inner container only when the section content itself must be edge to
|
|
213
|
+
edge. Do not repeat `1024px`, `1200px`, `1280px`, or `1440px` in section CSS.
|
|
214
|
+
- `--background` / `--foreground`: default page surface and readable default
|
|
215
|
+
content. `--primary` / `--primary-foreground`: main brand and action surface
|
|
216
|
+
plus readable content on it. `--secondary` / `--secondary-foreground`:
|
|
217
|
+
supporting surface plus its content; it is not assumed light or dark.
|
|
218
|
+
- `--accent` / `--accent-foreground`: high-attention emphasis, selection, or
|
|
219
|
+
decorative surface plus its content, not a default CTA substitute.
|
|
220
|
+
`--muted` / `--muted-foreground`: subdued surface and secondary text/icons.
|
|
221
|
+
Always pair a chosen surface with its corresponding foreground token.
|
|
222
|
+
- `--radius` is the generic fallback; use `--radius-card`,
|
|
223
|
+
`--radius-button`, and `--radius-input` for cards, command buttons, and form
|
|
224
|
+
controls. Tailwind token mappings use `@theme inline`; hand-written CSS may
|
|
225
|
+
consume these variables directly.
|
|
226
|
+
- The theme decides which semantic surface each of its components uses. Do not
|
|
227
|
+
alter built-in section styling to accommodate a single theme palette, and do
|
|
228
|
+
not hardcode replacement colors in individual sections.
|
|
161
229
|
- Keep typography, spacing, containers, buttons, cards, forms, and media frames
|
|
162
230
|
consistent across ordinary sections, CMS sections, and starter pages.
|
|
163
231
|
- Theme-local assets live under top-level `assets/`. Required preview images are
|
|
@@ -177,12 +245,14 @@ Before editing:
|
|
|
177
245
|
When adding or changing a section:
|
|
178
246
|
|
|
179
247
|
1. Define serializable props in its dedicated `.tsx` file.
|
|
180
|
-
2. Add `
|
|
248
|
+
2. Add `section` metadata with `category`, semantic `variant`, and an explicit
|
|
249
|
+
`motion` mode (`reveal`, `custom`, or `none`).
|
|
250
|
+
3. Add `label`, useful `ai.instructions`, typed `fields`, optional
|
|
181
251
|
`blockSlots`, `defaultProps`, and `render`.
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
252
|
+
4. Add all editor label keys to `src/locales/en.json`.
|
|
253
|
+
5. Register it in `pageConfig` and the appropriate translated category.
|
|
254
|
+
6. Add realistic usage to starter pages or CMS templates where appropriate.
|
|
255
|
+
7. Verify public rendering plus editor desktop, tablet, and mobile behavior.
|
|
186
256
|
|
|
187
257
|
Before handoff:
|
|
188
258
|
|
|
@@ -192,7 +262,17 @@ pnpm typecheck
|
|
|
192
262
|
pnpm build
|
|
193
263
|
pnpm validate
|
|
194
264
|
suda theme check
|
|
265
|
+
suda theme visual-check
|
|
195
266
|
```
|
|
196
267
|
|
|
197
|
-
Run `suda theme
|
|
198
|
-
|
|
268
|
+
Run `suda theme visual-check` after developing or substantially changing a
|
|
269
|
+
theme. It renders every page component declared by the theme's
|
|
270
|
+
`pageConfig.components` with each declared design preset at desktop, tablet,
|
|
271
|
+
and mobile sizes, writes screenshots and `report.json` to
|
|
272
|
+
`.suda-build/visual-check/`, and fails on token contrast, semantic surface,
|
|
273
|
+
or page-overflow errors. Fixed-header overlap is reported as a layout warning;
|
|
274
|
+
solve it in the theme layout. Platform built-in sections are covered by
|
|
275
|
+
theme-engine tests, not repeated for every theme. Run `suda theme capture` when
|
|
276
|
+
publish-preview visuals change.
|
|
277
|
+
Do not use removed skip flags or bypass build, validation, AI metadata, locale,
|
|
278
|
+
visual, or screenshot checks.
|
|
@@ -34,6 +34,20 @@ pnpm build
|
|
|
34
34
|
|
|
35
35
|
By default capture writes `assets/preview/desktop.png`, `assets/preview/tablet.png`, and `assets/preview/mobile.png`.
|
|
36
36
|
|
|
37
|
+
Run a reusable visual audit after creating or substantially changing the theme:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
suda theme visual-check
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
It renders every page component declared by the theme's `pageConfig.components`
|
|
44
|
+
with every design preset at desktop, tablet, and mobile sizes. Screenshots and
|
|
45
|
+
`report.json` are written to `.suda-build/visual-check/`; the command fails on
|
|
46
|
+
token contrast, semantic surface-color, or page-overflow errors. Platform
|
|
47
|
+
built-in sections are covered by theme-engine tests rather than being repeated
|
|
48
|
+
for every theme. Use `--preset <id>`, `--device <device>`, or `--full-page` to
|
|
49
|
+
narrow a review.
|
|
50
|
+
|
|
37
51
|
Fields can use `visibleIf: ({ props }, { fields }) => boolean` for synchronous dynamic visibility. The starter hero shows this by hiding the Logo picker when `showLogo` is false. Suda converts `visibleIf` to Puck `resolveFields` during normalization, and the generated AI/MCP section schema marks those fields as dynamically visible.
|
|
38
52
|
|
|
39
53
|
When adding fields, prefer purpose-built Suda field types over generic text inputs: use `url` for links, `image` / `video` / `media` for media, `color` for colors, `icon` for icon names, `range` for bounded numbers, `spacing` for spacing tokens, `menu` for navigation, `select` / `radio` for fixed choices, and `array` / `object` for structured values. Multi-column sections such as card grids should expose a `columns` prop.
|
|
@@ -21,10 +21,18 @@ icons, AI metadata, or block slots. The root `AGENTS.md` rules still apply.
|
|
|
21
21
|
|
|
22
22
|
## Field source of truth
|
|
23
23
|
|
|
24
|
-
Type props first. Use `
|
|
24
|
+
Type props first. Use `SudaSectionConfig<Props>`, `SudaFields<Props>`, and
|
|
25
25
|
`defineSudaPageData(pageConfig, data)` to catch shape mismatches. Do not invent a
|
|
26
26
|
parallel Zod schema for theme fields.
|
|
27
27
|
|
|
28
|
+
Every page section must include `section` metadata. Its `motion` value is
|
|
29
|
+
required: ordinary sections use `"reveal"`, sections with their own client-side
|
|
30
|
+
animation use `"custom"`, and intentionally static sections use `"none"`.
|
|
31
|
+
The rendered section root must also carry the matching
|
|
32
|
+
`data-section-motion="reveal"`, `"custom"`, or `"none"` attribute. Do not rely
|
|
33
|
+
on a class-name convention; TypeScript checks the metadata contract and the
|
|
34
|
+
runtime uses the data attribute to avoid hiding content before hydration.
|
|
35
|
+
|
|
28
36
|
Keep these four structures aligned:
|
|
29
37
|
|
|
30
38
|
1. TypeScript props.
|
|
@@ -219,7 +227,8 @@ pricing cards, feature rows, stats, timeline items, or contact methods.
|
|
|
219
227
|
```tsx
|
|
220
228
|
type HeroProps = { title?: string; actions?: Slot };
|
|
221
229
|
|
|
222
|
-
export const Hero:
|
|
230
|
+
export const Hero: SudaSectionConfig<HeroProps> = {
|
|
231
|
+
section: { category: "hero", variant: "centered", motion: "reveal" },
|
|
223
232
|
label: t("sections.hero.label"),
|
|
224
233
|
ai: { instructions: "Primary landing-page introduction. Use once near the top." },
|
|
225
234
|
fields: {
|
|
@@ -64,17 +64,30 @@ scales, radii, shadows, or spacing.
|
|
|
64
64
|
|
|
65
65
|
For Tailwind, map Suda tokens through `@theme inline` and consume semantic
|
|
66
66
|
utilities such as `bg-primary`, `text-primary-foreground`, and `rounded-card`.
|
|
67
|
-
The `inline` keyword is required because Suda injects
|
|
67
|
+
The `inline` keyword is required because Suda injects unprefixed variables on
|
|
68
68
|
the rendered theme root.
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
70
|
+
Use the token meanings below rather than guessing from a theme's current
|
|
71
|
+
palette. Foreground tokens always pair with the surface token actually used.
|
|
72
|
+
|
|
73
|
+
| CSS variables | Meaning and use |
|
|
74
|
+
| ------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------- |
|
|
75
|
+
| `--background` / `--foreground` | Default page surface and readable default content. |
|
|
76
|
+
| `--primary` / `--primary-foreground` | Main brand and action surface, plus readable content on it. |
|
|
77
|
+
| `--secondary` / `--secondary-foreground` | Supporting surface, plus readable content on it. It is not assumed light or dark. |
|
|
78
|
+
| `--accent` / `--accent-foreground` | High-attention emphasis, selection, or decorative surface, plus readable content on it. Do not use it as a default CTA substitute. |
|
|
79
|
+
| `--muted` / `--muted-foreground` | Low-emphasis surface and secondary text/icons. |
|
|
80
|
+
| `--radius` | Generic radius fallback. |
|
|
81
|
+
| `--radius-card` / `--radius-button` / `--radius-input` | Radius for cards, command buttons, and form controls. |
|
|
82
|
+
|
|
83
|
+
Hand-written CSS consumes these unprefixed variables directly. Do not use
|
|
84
|
+
Tailwind aliases such as `var(--color-primary)` or `var(--color-background)`
|
|
85
|
+
in hand-written rules. For derived values, declare a theme-prefixed variable on
|
|
86
|
+
the root:
|
|
74
87
|
|
|
75
88
|
```css
|
|
76
89
|
.my-theme-root {
|
|
77
|
-
--my-theme-primary-hover: color-mix(in srgb, var(--
|
|
90
|
+
--my-theme-primary-hover: color-mix(in srgb, var(--primary) 84%, black);
|
|
78
91
|
}
|
|
79
92
|
|
|
80
93
|
.my-theme-button:hover {
|
|
@@ -82,8 +95,10 @@ theme-prefixed variable on the root:
|
|
|
82
95
|
}
|
|
83
96
|
```
|
|
84
97
|
|
|
85
|
-
|
|
86
|
-
|
|
98
|
+
Theme authors decide which semantic surface each component uses. Do not alter
|
|
99
|
+
built-in section styling to match one theme palette, and do not hardcode
|
|
100
|
+
replacement colors in individual sections. Build/check/publish reject
|
|
101
|
+
hand-written consumption of Suda-backed Tailwind aliases.
|
|
87
102
|
|
|
88
103
|
## Visual consistency
|
|
89
104
|
|
|
@@ -35,6 +35,9 @@ export const sourceManifest: ThemeSourceManifest = {
|
|
|
35
35
|
button: "8px",
|
|
36
36
|
input: "8px",
|
|
37
37
|
},
|
|
38
|
+
layout: {
|
|
39
|
+
contentWidth: "1280px",
|
|
40
|
+
},
|
|
38
41
|
},
|
|
39
42
|
},
|
|
40
43
|
{
|
|
@@ -58,6 +61,9 @@ export const sourceManifest: ThemeSourceManifest = {
|
|
|
58
61
|
button: "9999px",
|
|
59
62
|
input: "10px",
|
|
60
63
|
},
|
|
64
|
+
layout: {
|
|
65
|
+
contentWidth: "1280px",
|
|
66
|
+
},
|
|
61
67
|
},
|
|
62
68
|
},
|
|
63
69
|
],
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { SudaSectionConfig, SudaSectionMetadata } from "@sudajs/theme-engine";
|
|
2
2
|
import {
|
|
3
3
|
SudaIcon,
|
|
4
4
|
type SudaIconName,
|
|
@@ -48,7 +48,8 @@ type FeaturedPostsProps = {
|
|
|
48
48
|
postList?: ThemePostResourceQuery;
|
|
49
49
|
} & PuckExtras;
|
|
50
50
|
|
|
51
|
-
export const Hero:
|
|
51
|
+
export const Hero: SudaSectionConfig<HeroProps> = {
|
|
52
|
+
section: { category: "hero", variant: "centered", motion: "reveal" } satisfies SudaSectionMetadata,
|
|
52
53
|
label: t("sections.hero.label"),
|
|
53
54
|
ai: {
|
|
54
55
|
instructions:
|
|
@@ -98,7 +99,7 @@ export const Hero: SudaComponentConfig<HeroProps> = {
|
|
|
98
99
|
}) => {
|
|
99
100
|
const logoSrc = resolveAsset(puck?.metadata, logo);
|
|
100
101
|
return (
|
|
101
|
-
<section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-hero">
|
|
102
|
+
<section data-section-motion="reveal" className="w-full __SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-hero">
|
|
102
103
|
{showLogo && logoSrc ? (
|
|
103
104
|
<img className="__SUDA_THEME_KEY__-hero-logo" src={logoSrc} alt="" />
|
|
104
105
|
) : null}
|
|
@@ -113,7 +114,8 @@ export const Hero: SudaComponentConfig<HeroProps> = {
|
|
|
113
114
|
},
|
|
114
115
|
};
|
|
115
116
|
|
|
116
|
-
export const FeatureGrid:
|
|
117
|
+
export const FeatureGrid: SudaSectionConfig<FeatureGridProps> = {
|
|
118
|
+
section: { category: "features", variant: "feature-grid", motion: "reveal" } satisfies SudaSectionMetadata,
|
|
117
119
|
label: t("sections.featureGrid.label"),
|
|
118
120
|
ai: {
|
|
119
121
|
instructions:
|
|
@@ -146,7 +148,7 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
|
|
|
146
148
|
],
|
|
147
149
|
},
|
|
148
150
|
render: ({ title, description, columns = 3, features = [] }) => (
|
|
149
|
-
<section className="__SUDA_THEME_KEY__-section">
|
|
151
|
+
<section data-section-motion="reveal" className="w-full __SUDA_THEME_KEY__-section">
|
|
150
152
|
<h2>{title}</h2>
|
|
151
153
|
<p>{description}</p>
|
|
152
154
|
<div
|
|
@@ -165,7 +167,8 @@ export const FeatureGrid: SudaComponentConfig<FeatureGridProps> = {
|
|
|
165
167
|
),
|
|
166
168
|
};
|
|
167
169
|
|
|
168
|
-
export const Testimonial:
|
|
170
|
+
export const Testimonial: SudaSectionConfig<TestimonialProps> = {
|
|
171
|
+
section: { category: "social-proof", variant: "testimonial", motion: "reveal" } satisfies SudaSectionMetadata,
|
|
169
172
|
label: t("sections.testimonial.label"),
|
|
170
173
|
ai: {
|
|
171
174
|
instructions:
|
|
@@ -184,7 +187,7 @@ export const Testimonial: SudaComponentConfig<TestimonialProps> = {
|
|
|
184
187
|
role: "Founder",
|
|
185
188
|
},
|
|
186
189
|
render: ({ quote, author, role }) => (
|
|
187
|
-
<section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-quote">
|
|
190
|
+
<section data-section-motion="reveal" className="w-full __SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-quote">
|
|
188
191
|
<blockquote>{quote}</blockquote>
|
|
189
192
|
<p>
|
|
190
193
|
{author} · {role}
|
|
@@ -193,7 +196,8 @@ export const Testimonial: SudaComponentConfig<TestimonialProps> = {
|
|
|
193
196
|
),
|
|
194
197
|
};
|
|
195
198
|
|
|
196
|
-
export const CallToAction:
|
|
199
|
+
export const CallToAction: SudaSectionConfig<CallToActionProps> = {
|
|
200
|
+
section: { category: "conversion", variant: "cta", motion: "reveal" } satisfies SudaSectionMetadata,
|
|
197
201
|
label: t("sections.callToAction.label"),
|
|
198
202
|
ai: {
|
|
199
203
|
instructions:
|
|
@@ -214,7 +218,7 @@ export const CallToAction: SudaComponentConfig<CallToActionProps> = {
|
|
|
214
218
|
buttonHref: "#contact",
|
|
215
219
|
},
|
|
216
220
|
render: ({ title, description, buttonLabel, buttonHref }) => (
|
|
217
|
-
<section className="__SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-cta" id="contact">
|
|
221
|
+
<section data-section-motion="reveal" className="w-full __SUDA_THEME_KEY__-section __SUDA_THEME_KEY__-cta" id="contact">
|
|
218
222
|
<h2>{title}</h2>
|
|
219
223
|
<p>{description}</p>
|
|
220
224
|
<a className="__SUDA_THEME_KEY__-button" href={buttonHref}>
|
|
@@ -224,7 +228,8 @@ export const CallToAction: SudaComponentConfig<CallToActionProps> = {
|
|
|
224
228
|
),
|
|
225
229
|
};
|
|
226
230
|
|
|
227
|
-
export const FeaturedPosts:
|
|
231
|
+
export const FeaturedPosts: SudaSectionConfig<FeaturedPostsProps> = {
|
|
232
|
+
section: { category: "content", variant: "featured-posts", motion: "reveal" } satisfies SudaSectionMetadata,
|
|
228
233
|
label: t("sections.featuredPosts.label"),
|
|
229
234
|
ai: {
|
|
230
235
|
instructions:
|
|
@@ -251,7 +256,7 @@ export const FeaturedPosts: SudaComponentConfig<FeaturedPostsProps> = {
|
|
|
251
256
|
const postHref = (post: (typeof posts)[number]) =>
|
|
252
257
|
post.url || (post.slug ? `/posts/${encodeURIComponent(post.slug)}` : "#");
|
|
253
258
|
return (
|
|
254
|
-
<section className="__SUDA_THEME_KEY__-section">
|
|
259
|
+
<section data-section-motion="reveal" className="w-full __SUDA_THEME_KEY__-section">
|
|
255
260
|
<h2>{title}</h2>
|
|
256
261
|
<p>{description}</p>
|
|
257
262
|
<div className="__SUDA_THEME_KEY__-grid" style={{ "--__SUDA_THEME_KEY__-columns": 3 }}>
|
|
@@ -1,24 +1,44 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
@source "./**/*.{ts,tsx}";
|
|
3
3
|
|
|
4
|
+
[data-suda-motion-ready] [data-section-motion="reveal"] {
|
|
5
|
+
opacity: 0;
|
|
6
|
+
transform: translateY(24px);
|
|
7
|
+
transition:
|
|
8
|
+
opacity 700ms ease,
|
|
9
|
+
transform 700ms ease;
|
|
10
|
+
}
|
|
11
|
+
[data-suda-motion-ready] [data-section-motion="reveal"][data-motion-visible="true"],
|
|
12
|
+
[data-suda-motion-reduced] [data-section-motion="reveal"] {
|
|
13
|
+
opacity: 1;
|
|
14
|
+
transform: none;
|
|
15
|
+
}
|
|
16
|
+
@media (prefers-reduced-motion: reduce) {
|
|
17
|
+
[data-section-motion="reveal"] {
|
|
18
|
+
opacity: 1;
|
|
19
|
+
transform: none;
|
|
20
|
+
transition: none;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
4
24
|
.__SUDA_THEME_KEY__-root {
|
|
5
25
|
font-family: Inter, ui-sans-serif, system-ui, sans-serif;
|
|
6
|
-
color: var(--
|
|
7
|
-
background: var(--
|
|
26
|
+
color: var(--foreground, #111827);
|
|
27
|
+
background: var(--background, #ffffff);
|
|
8
28
|
}
|
|
9
29
|
.__SUDA_THEME_KEY__-header,
|
|
10
30
|
.__SUDA_THEME_KEY__-footer {
|
|
11
31
|
padding: 20px clamp(20px, 5vw, 64px);
|
|
12
|
-
border-bottom: 1px solid var(--
|
|
32
|
+
border-bottom: 1px solid var(--muted, #e5e7eb);
|
|
13
33
|
}
|
|
14
34
|
.__SUDA_THEME_KEY__-footer {
|
|
15
35
|
display: flex;
|
|
16
36
|
flex-wrap: wrap;
|
|
17
37
|
align-items: center;
|
|
18
38
|
gap: 12px 20px;
|
|
19
|
-
border-top: 1px solid var(--
|
|
39
|
+
border-top: 1px solid var(--muted, #e5e7eb);
|
|
20
40
|
border-bottom: 0;
|
|
21
|
-
color: var(--
|
|
41
|
+
color: var(--muted-foreground, #6b7280);
|
|
22
42
|
}
|
|
23
43
|
.__SUDA_THEME_KEY__-footer a {
|
|
24
44
|
display: inline-flex;
|
|
@@ -35,19 +55,19 @@
|
|
|
35
55
|
padding: 64px clamp(20px, 5vw, 64px);
|
|
36
56
|
}
|
|
37
57
|
.__SUDA_THEME_KEY__-hero {
|
|
38
|
-
background: var(--
|
|
58
|
+
background: var(--accent, #f8fafc);
|
|
39
59
|
}
|
|
40
60
|
.__SUDA_THEME_KEY__-hero-logo {
|
|
41
61
|
width: 64px;
|
|
42
62
|
height: 64px;
|
|
43
|
-
border-radius: var(--
|
|
63
|
+
border-radius: var(--radius-card, 14px);
|
|
44
64
|
margin-bottom: 24px;
|
|
45
65
|
}
|
|
46
66
|
.__SUDA_THEME_KEY__-eyebrow {
|
|
47
67
|
text-transform: uppercase;
|
|
48
68
|
letter-spacing: 0.08em;
|
|
49
69
|
font-size: 12px;
|
|
50
|
-
color: var(--
|
|
70
|
+
color: var(--primary, #2563eb);
|
|
51
71
|
font-weight: 700;
|
|
52
72
|
}
|
|
53
73
|
.__SUDA_THEME_KEY__-section h1 {
|
|
@@ -65,16 +85,16 @@
|
|
|
65
85
|
.__SUDA_THEME_KEY__-section p {
|
|
66
86
|
max-width: 680px;
|
|
67
87
|
line-height: 1.7;
|
|
68
|
-
color: var(--
|
|
88
|
+
color: var(--muted-foreground, #4b5563);
|
|
69
89
|
}
|
|
70
90
|
.__SUDA_THEME_KEY__-button {
|
|
71
91
|
display: inline-flex;
|
|
72
92
|
align-items: center;
|
|
73
93
|
min-height: 42px;
|
|
74
94
|
padding: 0 18px;
|
|
75
|
-
border-radius: var(--
|
|
76
|
-
background: var(--
|
|
77
|
-
color: var(--
|
|
95
|
+
border-radius: var(--radius-button, 8px);
|
|
96
|
+
background: var(--primary, #111827);
|
|
97
|
+
color: var(--primary-foreground, #ffffff);
|
|
78
98
|
text-decoration: none;
|
|
79
99
|
font-weight: 700;
|
|
80
100
|
}
|
|
@@ -85,14 +105,14 @@
|
|
|
85
105
|
margin-top: 28px;
|
|
86
106
|
}
|
|
87
107
|
.__SUDA_THEME_KEY__-card {
|
|
88
|
-
border: 1px solid var(--
|
|
89
|
-
border-radius: var(--
|
|
108
|
+
border: 1px solid var(--muted, #e5e7eb);
|
|
109
|
+
border-radius: var(--radius-card, 8px);
|
|
90
110
|
padding: 20px;
|
|
91
111
|
}
|
|
92
112
|
.__SUDA_THEME_KEY__-card-icon {
|
|
93
113
|
width: 24px;
|
|
94
114
|
height: 24px;
|
|
95
|
-
color: var(--
|
|
115
|
+
color: var(--primary, #2563eb);
|
|
96
116
|
}
|
|
97
117
|
.__SUDA_THEME_KEY__-quote blockquote {
|
|
98
118
|
max-width: 780px;
|
|
@@ -101,15 +121,15 @@
|
|
|
101
121
|
margin: 0 0 16px;
|
|
102
122
|
}
|
|
103
123
|
.__SUDA_THEME_KEY__-cta {
|
|
104
|
-
background: var(--
|
|
105
|
-
color: var(--
|
|
124
|
+
background: var(--foreground, #111827);
|
|
125
|
+
color: var(--background, #ffffff);
|
|
106
126
|
}
|
|
107
127
|
.__SUDA_THEME_KEY__-cta p {
|
|
108
|
-
color: var(--
|
|
128
|
+
color: var(--muted, #d1d5db);
|
|
109
129
|
}
|
|
110
130
|
.__SUDA_THEME_KEY__-cta .__SUDA_THEME_KEY__-button {
|
|
111
|
-
background: var(--
|
|
112
|
-
color: var(--
|
|
131
|
+
background: var(--background, #ffffff);
|
|
132
|
+
color: var(--foreground, #111827);
|
|
113
133
|
}
|
|
114
134
|
@media (max-width: 760px) {
|
|
115
135
|
.__SUDA_THEME_KEY__-grid {
|