@soloworks/smking-next 0.16.0 → 0.17.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/CHANGELOG.md +53 -0
- package/README.md +24 -0
- package/package.json +3 -1
- package/src/components/smking-runtime.tsx +30 -0
- package/src/index.ts +1 -0
- package/src/lib/client.ts +37 -1
- package/src/lib/smking-cms-styles.ts +0 -704
- package/src/lib/smking-elements-iife.ts +0 -46
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,58 @@
|
|
|
1
1
|
# @soloworks/smking-next
|
|
2
2
|
|
|
3
|
+
## 0.17.0 — 2026-05-26
|
|
4
|
+
|
|
5
|
+
**SDK self-report (heartbeat) — dashboard SDK Health chip stops getting WAF false-negatives.**
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Every `getAeoContent()` POST now piggybacks an `sdk_meta` field on the request body so saas can upsert `sdk_health_snapshots` with `source='sdk_heartbeat'`. The dashboard "SDK Health" chip reads this signal as primary, falling back to active probe only when fresh (<24h) heartbeat is absent.
|
|
10
|
+
- Payload shape: `{ sdk: "next", sdk_version, app_env, host }`. All fields optional saas-side; host is parsed from the request URL via `new URL(url).hostname`.
|
|
11
|
+
|
|
12
|
+
### Why
|
|
13
|
+
|
|
14
|
+
Customer Cloudflare WAFs commonly block saas's Vercel ASN egress (Vercel + Googlebot UA = reverse-DNS mismatch → 403 reject). Active probe from saas → customer site reliably fails on protected sites like sleepytofu.com even when SDK is fully operational. SDK-initiated heartbeat bypasses the WAF because the customer site is the originator — saas never needs to fetch back.
|
|
15
|
+
|
|
16
|
+
### Migration
|
|
17
|
+
|
|
18
|
+
No customer action required. Patch your SDK and the dashboard chip updates automatically on the next page render that triggers `getAeoContent`. Saas accepts the field since 2026-05; older saas versions ignore unknown body fields silently.
|
|
19
|
+
|
|
20
|
+
Minor bump — backward-compatible feature. `^0.16` consumers must update their constraint to `^0.17`.
|
|
21
|
+
|
|
22
|
+
## 0.16.1 — 2026-05-25
|
|
23
|
+
|
|
24
|
+
**New `<SmkingRuntime />` component — one-time mount in customer root layout to load saas-served CSS + Web Component runtime JS.**
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- `<SmkingRuntime>` exports from package root. Customers mount it once in `app/layout.tsx` (or equivalent); it emits `<link rel="stylesheet">` + `<script async>` pointing at `/api/v1/public/runtime.{css,js}` on the saas.
|
|
29
|
+
- Optional `baseUrl` prop overrides `process.env.SMKING_BASE_URL`. Default falls back to `https://smking.app`.
|
|
30
|
+
|
|
31
|
+
### Why
|
|
32
|
+
|
|
33
|
+
bodyHtml emitted by SaaS publish (v0.16+) carries Tailwind utility classes from the dashboard's cva variants. Customer Tailwind JIT can't see those runtime strings at build time, so utility classes render as dead strings without `<SmkingRuntime>` providing the compiled CSS. `<SmkingRuntime>` solves the propagation gap and additionally bundles Web Component IIFEs (`<smking-carousel>` etc.) so future dashboard custom elements reach customer sites without an SDK bump cycle.
|
|
34
|
+
|
|
35
|
+
### Migration
|
|
36
|
+
|
|
37
|
+
Add one line to your root layout:
|
|
38
|
+
|
|
39
|
+
```tsx
|
|
40
|
+
import { SmkingRuntime } from "@soloworks/smking-next";
|
|
41
|
+
|
|
42
|
+
export default function RootLayout({ children }) {
|
|
43
|
+
return (
|
|
44
|
+
<html>
|
|
45
|
+
<body>
|
|
46
|
+
<SmkingRuntime />
|
|
47
|
+
{children}
|
|
48
|
+
</body>
|
|
49
|
+
</html>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Patch bump — no breaking changes. Existing v0.16.0 installs keep working; mounting `<SmkingRuntime>` is opt-in but recommended.
|
|
55
|
+
|
|
3
56
|
## 0.16.0 — 2026-05-24
|
|
4
57
|
|
|
5
58
|
**Raw passthrough render — `<SmkingCms>` now echoes the full Plate-serialized bodyHtml exactly, no `<article class="smk-cms">` wrapper and no inline CSS injection.**
|
package/README.md
CHANGED
|
@@ -84,6 +84,30 @@ Content-Type: application/json
|
|
|
84
84
|
|
|
85
85
|
Response: `{ revalidated: number, errors: number }`. `errors > 0` means some tags couldn't be revalidated (others still succeeded — partial-success delivery).
|
|
86
86
|
|
|
87
|
+
## Mount the runtime once (v0.16.1+)
|
|
88
|
+
|
|
89
|
+
Add `<SmkingRuntime />` once in your root layout — it emits a `<link>` to the saas-served CSS and a `<script async>` to the bundled Web Component runtime IIFE. Browser caches both per saas-controlled stale-while-revalidate headers, so the cost amortises across every `<SmkingCms>` instance on the page.
|
|
90
|
+
|
|
91
|
+
```tsx
|
|
92
|
+
// app/layout.tsx
|
|
93
|
+
import { SmkingRuntime } from "@soloworks/smking-next";
|
|
94
|
+
|
|
95
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
96
|
+
return (
|
|
97
|
+
<html>
|
|
98
|
+
<body>
|
|
99
|
+
<SmkingRuntime />
|
|
100
|
+
{children}
|
|
101
|
+
</body>
|
|
102
|
+
</html>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
Optional `baseUrl` prop overrides `process.env.SMKING_BASE_URL`. Default: `https://smking.app`.
|
|
108
|
+
|
|
109
|
+
Without `<SmkingRuntime />`, `<SmkingCms>` content still renders but the Tailwind utility classes from the dashboard's cva variants resolve to dead strings — the page reaches the browser unstyled. The wizard installer auto-adds this mount; if you're upgrading manually from < v0.16.1, add the one line above.
|
|
110
|
+
|
|
87
111
|
## CMS rendering (optional, v0.11.0+)
|
|
88
112
|
|
|
89
113
|
The base install only wires AEO. If you author content in the smking dashboard's CMS and want to render it on your Next.js site, use the `<SmkingCms slug="…" />` Server Component.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@soloworks/smking-next",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "AI-native SEO (AEO) for Next.js — auto-inject JSON-LD, FAQ, AI summary, and SEO metadata so AI crawlers (ChatGPT, Perplexity, Google AI) can cite your pages.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"homepage": "https://github.com/sillyleo/smking/tree/main/packages/smking-next",
|
|
@@ -74,10 +74,12 @@
|
|
|
74
74
|
"@tiptap/starter-kit": "^3.23.4",
|
|
75
75
|
"@tiptap/static-renderer": "^3.23.4",
|
|
76
76
|
"@types/react": "^19.0.0",
|
|
77
|
+
"@types/react-dom": "^19",
|
|
77
78
|
"@vitejs/plugin-react": "^6.0.1",
|
|
78
79
|
"jsdom": "^29.1.0",
|
|
79
80
|
"next": "^16.2.2",
|
|
80
81
|
"react": "^19.0.0",
|
|
82
|
+
"react-dom": "19.2.4",
|
|
81
83
|
"typescript": "^5",
|
|
82
84
|
"vitest": "^4.1.5"
|
|
83
85
|
},
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One-time-mount runtime asset loader. Customers place a single
|
|
3
|
+
* `<SmkingRuntime />` in their root layout. Emits a `<link>` to the
|
|
4
|
+
* saas-side compiled CSS and a `<script async>` to the bundled Web
|
|
5
|
+
* Component IIFE. Both are aggressively browser-cached
|
|
6
|
+
* (stale-while-revalidate in production), so the cost amortises to
|
|
7
|
+
* effectively zero across all the page's `<SmkingCms>` instances.
|
|
8
|
+
*
|
|
9
|
+
* Why customer must mount it explicitly:
|
|
10
|
+
* `<SmkingCms>` stays in raw-passthrough mode (v0.16 commit 4bde390)
|
|
11
|
+
* — it doesn't decide for the customer whether to load runtime assets
|
|
12
|
+
* or where. Separating concerns means a customer can use the SDK to
|
|
13
|
+
* fetch a page's bodyHtml + render it inside their own design system,
|
|
14
|
+
* without our CSS bleeding in.
|
|
15
|
+
*/
|
|
16
|
+
export function SmkingRuntime({
|
|
17
|
+
baseUrl,
|
|
18
|
+
}: {
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
}) {
|
|
21
|
+
const url = (
|
|
22
|
+
baseUrl ?? process.env.SMKING_BASE_URL ?? "https://smking.app"
|
|
23
|
+
).replace(/\/$/, "");
|
|
24
|
+
return (
|
|
25
|
+
<>
|
|
26
|
+
<link rel="stylesheet" href={`${url}/api/v1/public/runtime.css`} />
|
|
27
|
+
<script src={`${url}/api/v1/public/runtime.js`} async />
|
|
28
|
+
</>
|
|
29
|
+
);
|
|
30
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export { SmkingAEO } from "./components/smking-aeo";
|
|
2
2
|
export { SmkingCms } from "./components/smking-cms";
|
|
3
|
+
export { SmkingRuntime } from "./components/smking-runtime";
|
|
3
4
|
export { getAeoContent } from "./lib/client";
|
|
4
5
|
export { getCmsPage } from "./lib/cms-client";
|
|
5
6
|
export type {
|
package/src/lib/client.ts
CHANGED
|
@@ -8,6 +8,27 @@ import { normalizePath, resolveRequestPath } from "./path";
|
|
|
8
8
|
const DEFAULT_REVALIDATE_SECONDS = 3600;
|
|
9
9
|
const FETCH_TIMEOUT_MS = 2000;
|
|
10
10
|
|
|
11
|
+
/**
|
|
12
|
+
* SDK version self-reported in heartbeat `sdk_meta.sdk_version`. Must stay
|
|
13
|
+
* in sync with package.json `version` — `publish-smking-package` skill
|
|
14
|
+
* bumps both together.
|
|
15
|
+
*/
|
|
16
|
+
const SDK_VERSION = "0.17.0";
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Extract hostname from a full URL string. Returns null on parse failure
|
|
20
|
+
* (malformed URL would otherwise throw). Used to populate heartbeat
|
|
21
|
+
* `sdk_meta.host` without pulling next/headers into this client.
|
|
22
|
+
*/
|
|
23
|
+
function safeHostname(input: string | undefined): string | null {
|
|
24
|
+
if (!input) return null;
|
|
25
|
+
try {
|
|
26
|
+
return new URL(input).hostname;
|
|
27
|
+
} catch {
|
|
28
|
+
return null;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
11
32
|
const _warnedKeys = new Set<string>();
|
|
12
33
|
function warnOnce(key: string, message: string): void {
|
|
13
34
|
if (process.env.NODE_ENV === "production") return;
|
|
@@ -81,7 +102,22 @@ export async function getAeoContent(
|
|
|
81
102
|
const res = await fetch(`${baseUrl}/api/v1/public/aeo`, {
|
|
82
103
|
method: "POST",
|
|
83
104
|
headers: { "content-type": "application/json" },
|
|
84
|
-
body: JSON.stringify({
|
|
105
|
+
body: JSON.stringify({
|
|
106
|
+
key: params.apiKey,
|
|
107
|
+
path,
|
|
108
|
+
url,
|
|
109
|
+
// Heartbeat piggyback — saas upserts into sdk_health_snapshots so
|
|
110
|
+
// the dashboard can show 'SDK active (last seen Nm ago)' without
|
|
111
|
+
// needing an outbound probe (which Cloudflare WAFs commonly block).
|
|
112
|
+
// Saas accepts this optional field via /api/v1/public/aeo POST since
|
|
113
|
+
// 2026-05; older saas versions silently ignore it.
|
|
114
|
+
sdk_meta: {
|
|
115
|
+
sdk: "next",
|
|
116
|
+
sdk_version: SDK_VERSION,
|
|
117
|
+
app_env: process.env.NODE_ENV ?? null,
|
|
118
|
+
host: safeHostname(url),
|
|
119
|
+
},
|
|
120
|
+
}),
|
|
85
121
|
signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
|
|
86
122
|
next: {
|
|
87
123
|
revalidate: params.revalidate ?? DEFAULT_REVALIDATE_SECONDS,
|
|
@@ -1,704 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AUTO-GENERATED - do not edit by hand.
|
|
3
|
-
* Source: packages/shared/src/styles/cms-styles.css
|
|
4
|
-
* Sync: pnpm sync:cms-styles
|
|
5
|
-
* Hash: f555c5fed446
|
|
6
|
-
*
|
|
7
|
-
* Inlined by <SmkingCms> (smking-cms.tsx) inside a
|
|
8
|
-
* <style data-smking="cms-styles"> ... </style>
|
|
9
|
-
* tag exactly once per page render. Mode A only; Mode B
|
|
10
|
-
* ("tailwind-prose") skips this entirely.
|
|
11
|
-
*/
|
|
12
|
-
export const SMKING_CMS_STYLES = `/*
|
|
13
|
-
* smking CMS — canonical block styles (Mode A "css")
|
|
14
|
-
*
|
|
15
|
-
* Single source of truth for all customer-facing CMS render styling.
|
|
16
|
-
* Consumed at build time by \`scripts/sync-cms-styles.mjs\` which emits:
|
|
17
|
-
* - packages/smking-next/src/lib/smking-cms-styles.ts (TS const)
|
|
18
|
-
* - packages/smking-laravel/resources/views/cms-styles.blade.php (Blade)
|
|
19
|
-
*
|
|
20
|
-
* The dashboard imports the same file directly via Next.js workspace
|
|
21
|
-
* alias so what the editor preview shows = what the customer site renders
|
|
22
|
-
* (WYSIWYG strict per boss original spec).
|
|
23
|
-
*
|
|
24
|
-
* Mode B (\`tailwind-prose\`) customers do NOT load this file — their
|
|
25
|
-
* Tailwind + @tailwindcss/typography plugin replaces all styles below.
|
|
26
|
-
*
|
|
27
|
-
* Namespacing rules (cross-platform parity):
|
|
28
|
-
* - All selectors are scoped to \`.smk-cms\` or \`.smk-block--*\` — no
|
|
29
|
-
* global element rules (\`h1 {}\` \`p {}\`) so SDK styles never leak
|
|
30
|
-
* into customer's other page sections.
|
|
31
|
-
* - Wrapped in \`@layer smking-cms\` so customer CSS authoring with
|
|
32
|
-
* Tailwind \`@layer base|components|utilities\` has predictable order.
|
|
33
|
-
* - Tokens on \`.smk-cms\` (CSS variables) — customer overrides via
|
|
34
|
-
* \`:root { --smk-accent: …; }\` or per-instance.
|
|
35
|
-
*
|
|
36
|
-
* Edit invariants:
|
|
37
|
-
* 1. Never write a global element selector.
|
|
38
|
-
* 2. Always declare variants via \`[data-*]\` attribute selectors — the
|
|
39
|
-
* SDK markup emits the attributes; CSS reads them. No hard-coded
|
|
40
|
-
* ratios / layouts / colors.
|
|
41
|
-
* 3. Test against Mode A's class hooks: smk-cms / smk-block--{type} /
|
|
42
|
-
* smk-hero / smk-nav / smk-nav-search / smk-carousel.
|
|
43
|
-
*/
|
|
44
|
-
|
|
45
|
-
@layer smking-cms {
|
|
46
|
-
/* ============================================================
|
|
47
|
-
Design tokens — customer overrides on :root or .smk-cms
|
|
48
|
-
============================================================ */
|
|
49
|
-
.smk-cms {
|
|
50
|
-
--smk-fg: #0a0a0a;
|
|
51
|
-
--smk-fg-muted: #525252;
|
|
52
|
-
--smk-fg-subtle: #a3a3a3;
|
|
53
|
-
--smk-bg: #fff;
|
|
54
|
-
--smk-bg-soft: #fafafa;
|
|
55
|
-
--smk-border: #e5e5e5;
|
|
56
|
-
--smk-border-strong: #d4d4d4;
|
|
57
|
-
--smk-accent: #0a0a0a;
|
|
58
|
-
--smk-accent-fg: #fff;
|
|
59
|
-
--smk-radius-sm: 6px;
|
|
60
|
-
--smk-radius-md: 10px;
|
|
61
|
-
--smk-radius-lg: 16px;
|
|
62
|
-
--smk-section-gap: 4rem;
|
|
63
|
-
--smk-font-serif: ui-serif, Georgia, "Iowan Old Style", "Apple Garamond",
|
|
64
|
-
"Times New Roman", serif;
|
|
65
|
-
--smk-font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI",
|
|
66
|
-
"PingFang TC", "Noto Sans CJK TC", "Microsoft JhengHei", sans-serif;
|
|
67
|
-
}
|
|
68
|
-
@media (prefers-color-scheme: dark) {
|
|
69
|
-
.smk-cms {
|
|
70
|
-
--smk-fg: #f5f5f5;
|
|
71
|
-
--smk-fg-muted: #a3a3a3;
|
|
72
|
-
--smk-fg-subtle: #525252;
|
|
73
|
-
--smk-bg: #0a0a0a;
|
|
74
|
-
--smk-bg-soft: #171717;
|
|
75
|
-
--smk-border: #262626;
|
|
76
|
-
--smk-border-strong: #404040;
|
|
77
|
-
--smk-accent: #f5f5f5;
|
|
78
|
-
--smk-accent-fg: #0a0a0a;
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/* ============================================================
|
|
83
|
-
Article wrapper
|
|
84
|
-
============================================================ */
|
|
85
|
-
.smk-cms {
|
|
86
|
-
color: var(--smk-fg);
|
|
87
|
-
font-family: var(--smk-font-sans);
|
|
88
|
-
line-height: 1.7;
|
|
89
|
-
font-size: 17px;
|
|
90
|
-
max-width: 42rem;
|
|
91
|
-
margin: 0 auto;
|
|
92
|
-
padding: 3rem 1.5rem;
|
|
93
|
-
}
|
|
94
|
-
.smk-cms__title {
|
|
95
|
-
font-family: var(--smk-font-serif);
|
|
96
|
-
font-size: clamp(2rem, 5vw, 3rem);
|
|
97
|
-
font-weight: 700;
|
|
98
|
-
line-height: 1.1;
|
|
99
|
-
letter-spacing: -0.02em;
|
|
100
|
-
margin: 0 0 3rem;
|
|
101
|
-
}
|
|
102
|
-
.smk-cms > .smk-block + .smk-block {
|
|
103
|
-
margin-top: 2.5rem;
|
|
104
|
-
}
|
|
105
|
-
/* Hero edges out to wrapper bounds; other blocks stay readable column. */
|
|
106
|
-
.smk-cms > .smk-block--hero {
|
|
107
|
-
margin-left: -1.5rem;
|
|
108
|
-
margin-right: -1.5rem;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
/* ============================================================
|
|
112
|
-
Article — Plate-rendered HTML
|
|
113
|
-
SDK emits: \`<div class="smk-block smk-block--article">{html}</div>\`
|
|
114
|
-
The inner HTML comes from Plate's serializeHtml on the SaaS side,
|
|
115
|
-
so customer SDK ships zero editor deps. Below selectors style the
|
|
116
|
-
emitted h1/h2/p/ul/blockquote etc inside the article wrapper.
|
|
117
|
-
============================================================ */
|
|
118
|
-
.smk-block--article p {
|
|
119
|
-
margin: 0 0 1.25em;
|
|
120
|
-
}
|
|
121
|
-
.smk-block--article p:last-child {
|
|
122
|
-
margin-bottom: 0;
|
|
123
|
-
}
|
|
124
|
-
.smk-block--article h1,
|
|
125
|
-
.smk-block--article h2,
|
|
126
|
-
.smk-block--article h3 {
|
|
127
|
-
font-family: var(--smk-font-serif);
|
|
128
|
-
font-weight: 700;
|
|
129
|
-
letter-spacing: -0.015em;
|
|
130
|
-
line-height: 1.2;
|
|
131
|
-
}
|
|
132
|
-
.smk-block--article h1 {
|
|
133
|
-
font-size: 2rem;
|
|
134
|
-
margin: 2em 0 0.5em;
|
|
135
|
-
}
|
|
136
|
-
.smk-block--article h2 {
|
|
137
|
-
font-size: 1.6rem;
|
|
138
|
-
margin: 1.8em 0 0.5em;
|
|
139
|
-
}
|
|
140
|
-
.smk-block--article h3 {
|
|
141
|
-
font-size: 1.25rem;
|
|
142
|
-
margin: 1.5em 0 0.5em;
|
|
143
|
-
}
|
|
144
|
-
.smk-block--article h1:first-child,
|
|
145
|
-
.smk-block--article h2:first-child,
|
|
146
|
-
.smk-block--article h3:first-child {
|
|
147
|
-
margin-top: 0;
|
|
148
|
-
}
|
|
149
|
-
.smk-block--article ul,
|
|
150
|
-
.smk-block--article ol {
|
|
151
|
-
margin: 0 0 1.25em;
|
|
152
|
-
padding-left: 1.75em;
|
|
153
|
-
}
|
|
154
|
-
.smk-block--article li {
|
|
155
|
-
margin: 0.4em 0;
|
|
156
|
-
}
|
|
157
|
-
.smk-block--article li::marker {
|
|
158
|
-
color: var(--smk-fg-subtle);
|
|
159
|
-
}
|
|
160
|
-
.smk-block--article a {
|
|
161
|
-
color: inherit;
|
|
162
|
-
text-decoration: underline;
|
|
163
|
-
text-decoration-color: var(--smk-border-strong);
|
|
164
|
-
text-underline-offset: 3px;
|
|
165
|
-
transition: text-decoration-color 0.15s;
|
|
166
|
-
}
|
|
167
|
-
.smk-block--article a:hover {
|
|
168
|
-
text-decoration-color: currentColor;
|
|
169
|
-
}
|
|
170
|
-
.smk-block--article blockquote {
|
|
171
|
-
margin: 1.5em 0;
|
|
172
|
-
padding: 0.25em 0 0.25em 1.25rem;
|
|
173
|
-
border-left: 3px solid var(--smk-border-strong);
|
|
174
|
-
color: var(--smk-fg-muted);
|
|
175
|
-
font-style: italic;
|
|
176
|
-
}
|
|
177
|
-
.smk-block--article code {
|
|
178
|
-
background: var(--smk-bg-soft);
|
|
179
|
-
padding: 0.15em 0.4em;
|
|
180
|
-
border-radius: 4px;
|
|
181
|
-
font-size: 0.9em;
|
|
182
|
-
font-family: ui-monospace, "SF Mono", Menlo, monospace;
|
|
183
|
-
}
|
|
184
|
-
.smk-block--article pre {
|
|
185
|
-
background: var(--smk-bg-soft);
|
|
186
|
-
padding: 1rem;
|
|
187
|
-
border-radius: var(--smk-radius-sm);
|
|
188
|
-
overflow-x: auto;
|
|
189
|
-
margin: 1.5em 0;
|
|
190
|
-
}
|
|
191
|
-
.smk-block--article pre code {
|
|
192
|
-
background: transparent;
|
|
193
|
-
padding: 0;
|
|
194
|
-
}
|
|
195
|
-
.smk-block--article img {
|
|
196
|
-
max-width: 100%;
|
|
197
|
-
height: auto;
|
|
198
|
-
border-radius: var(--smk-radius-sm);
|
|
199
|
-
margin: 1.5em 0;
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/* ============================================================
|
|
203
|
-
Hero — magazine cover banner
|
|
204
|
-
SDK emits: \`<section class="smk-block smk-block--hero smk-hero">\`
|
|
205
|
-
============================================================ */
|
|
206
|
-
.smk-hero {
|
|
207
|
-
text-align: center;
|
|
208
|
-
padding: 5rem 1.5rem 4rem;
|
|
209
|
-
background: var(--smk-bg-soft);
|
|
210
|
-
border-radius: 0;
|
|
211
|
-
margin-bottom: 3rem;
|
|
212
|
-
position: relative;
|
|
213
|
-
}
|
|
214
|
-
.smk-hero__image {
|
|
215
|
-
max-width: 100%;
|
|
216
|
-
height: auto;
|
|
217
|
-
margin: 0 auto 2rem;
|
|
218
|
-
border-radius: var(--smk-radius-md);
|
|
219
|
-
display: block;
|
|
220
|
-
}
|
|
221
|
-
.smk-hero__title {
|
|
222
|
-
font-family: var(--smk-font-serif);
|
|
223
|
-
font-size: clamp(2.25rem, 6vw, 4rem);
|
|
224
|
-
font-weight: 800;
|
|
225
|
-
line-height: 1.05;
|
|
226
|
-
letter-spacing: -0.025em;
|
|
227
|
-
margin: 0 0 1rem;
|
|
228
|
-
color: var(--smk-fg);
|
|
229
|
-
max-width: 32rem;
|
|
230
|
-
margin-inline: auto;
|
|
231
|
-
}
|
|
232
|
-
.smk-hero__subtitle {
|
|
233
|
-
color: var(--smk-fg-muted);
|
|
234
|
-
font-size: 0.95rem;
|
|
235
|
-
margin: 1.5rem auto 0;
|
|
236
|
-
max-width: 28rem;
|
|
237
|
-
letter-spacing: 0.02em;
|
|
238
|
-
}
|
|
239
|
-
.smk-hero__cta {
|
|
240
|
-
display: inline-block;
|
|
241
|
-
padding: 0.75rem 1.5rem;
|
|
242
|
-
margin-top: 2rem;
|
|
243
|
-
background: var(--smk-accent);
|
|
244
|
-
color: var(--smk-accent-fg);
|
|
245
|
-
border-radius: var(--smk-radius-sm);
|
|
246
|
-
text-decoration: none;
|
|
247
|
-
font-weight: 500;
|
|
248
|
-
font-size: 0.95rem;
|
|
249
|
-
transition: opacity 0.15s;
|
|
250
|
-
}
|
|
251
|
-
.smk-hero__cta:hover {
|
|
252
|
-
opacity: 0.85;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/* ============================================================
|
|
256
|
-
Nav — recent-posts / taxonomy-list
|
|
257
|
-
SDK emits: \`<section class="smk-block smk-block--nav-{type} smk-nav {sub-class}" data-layout="list|grid|carousel">\`
|
|
258
|
-
============================================================ */
|
|
259
|
-
.smk-nav {
|
|
260
|
-
padding: 2.5rem 0;
|
|
261
|
-
border-top: 1px solid var(--smk-border);
|
|
262
|
-
}
|
|
263
|
-
.smk-nav__heading {
|
|
264
|
-
font-size: 0.75rem;
|
|
265
|
-
font-weight: 700;
|
|
266
|
-
text-transform: uppercase;
|
|
267
|
-
letter-spacing: 0.12em;
|
|
268
|
-
color: var(--smk-fg-muted);
|
|
269
|
-
margin: 0 0 1.5rem;
|
|
270
|
-
}
|
|
271
|
-
.smk-nav__list {
|
|
272
|
-
list-style: none;
|
|
273
|
-
padding: 0;
|
|
274
|
-
margin: 0;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/* List layout (data-layout="list") */
|
|
278
|
-
.smk-nav[data-layout="list"] .smk-nav__list {
|
|
279
|
-
display: flex;
|
|
280
|
-
flex-direction: column;
|
|
281
|
-
}
|
|
282
|
-
.smk-nav[data-layout="list"] .smk-nav__item + .smk-nav__item {
|
|
283
|
-
border-top: 1px solid var(--smk-border);
|
|
284
|
-
}
|
|
285
|
-
.smk-nav[data-layout="list"] .smk-nav__link {
|
|
286
|
-
display: flex;
|
|
287
|
-
gap: 1.25rem;
|
|
288
|
-
align-items: center;
|
|
289
|
-
padding: 1.25rem 0;
|
|
290
|
-
color: inherit;
|
|
291
|
-
text-decoration: none;
|
|
292
|
-
transition: opacity 0.15s;
|
|
293
|
-
}
|
|
294
|
-
.smk-nav[data-layout="list"] .smk-nav__thumb {
|
|
295
|
-
width: 6rem;
|
|
296
|
-
height: 6rem;
|
|
297
|
-
object-fit: cover;
|
|
298
|
-
border-radius: var(--smk-radius-sm);
|
|
299
|
-
flex-shrink: 0;
|
|
300
|
-
background: var(--smk-bg-soft);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
/* Grid layout (data-layout="grid") */
|
|
304
|
-
.smk-nav[data-layout="grid"] .smk-nav__list {
|
|
305
|
-
display: grid;
|
|
306
|
-
grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
|
|
307
|
-
gap: 1.25rem;
|
|
308
|
-
}
|
|
309
|
-
.smk-nav[data-layout="grid"] .smk-nav__link {
|
|
310
|
-
display: flex;
|
|
311
|
-
flex-direction: column;
|
|
312
|
-
color: inherit;
|
|
313
|
-
text-decoration: none;
|
|
314
|
-
background: var(--smk-bg);
|
|
315
|
-
border: 1px solid var(--smk-border);
|
|
316
|
-
border-radius: var(--smk-radius-md);
|
|
317
|
-
overflow: hidden;
|
|
318
|
-
transition: transform 0.15s, box-shadow 0.15s, border-color 0.15s;
|
|
319
|
-
}
|
|
320
|
-
.smk-nav[data-layout="grid"] .smk-nav__link:hover {
|
|
321
|
-
transform: translateY(-2px);
|
|
322
|
-
border-color: var(--smk-border-strong);
|
|
323
|
-
box-shadow: 0 8px 24px -8px rgb(0 0 0 / 0.12);
|
|
324
|
-
}
|
|
325
|
-
.smk-nav[data-layout="grid"] .smk-nav__thumb {
|
|
326
|
-
width: 100%;
|
|
327
|
-
aspect-ratio: 16 / 10;
|
|
328
|
-
object-fit: cover;
|
|
329
|
-
background: var(--smk-bg-soft);
|
|
330
|
-
}
|
|
331
|
-
.smk-nav[data-layout="grid"] .smk-nav__meta {
|
|
332
|
-
padding: 1rem;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
/* Carousel layout (data-layout="carousel") — horizontal scroll-snap */
|
|
336
|
-
.smk-nav[data-layout="carousel"] .smk-nav__list {
|
|
337
|
-
display: grid;
|
|
338
|
-
grid-auto-flow: column;
|
|
339
|
-
grid-auto-columns: 70%;
|
|
340
|
-
gap: 1rem;
|
|
341
|
-
overflow-x: auto;
|
|
342
|
-
scroll-snap-type: x mandatory;
|
|
343
|
-
padding-bottom: 0.5rem;
|
|
344
|
-
}
|
|
345
|
-
@media (min-width: 640px) {
|
|
346
|
-
.smk-nav[data-layout="carousel"] .smk-nav__list {
|
|
347
|
-
grid-auto-columns: 40%;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
.smk-nav[data-layout="carousel"] .smk-nav__item {
|
|
351
|
-
scroll-snap-align: start;
|
|
352
|
-
background: var(--smk-bg);
|
|
353
|
-
border: 1px solid var(--smk-border);
|
|
354
|
-
border-radius: var(--smk-radius-md);
|
|
355
|
-
overflow: hidden;
|
|
356
|
-
}
|
|
357
|
-
.smk-nav[data-layout="carousel"] .smk-nav__link {
|
|
358
|
-
display: flex;
|
|
359
|
-
flex-direction: column;
|
|
360
|
-
color: inherit;
|
|
361
|
-
text-decoration: none;
|
|
362
|
-
}
|
|
363
|
-
.smk-nav[data-layout="carousel"] .smk-nav__thumb {
|
|
364
|
-
width: 100%;
|
|
365
|
-
aspect-ratio: 16 / 10;
|
|
366
|
-
object-fit: cover;
|
|
367
|
-
background: var(--smk-bg-soft);
|
|
368
|
-
}
|
|
369
|
-
.smk-nav[data-layout="carousel"] .smk-nav__meta {
|
|
370
|
-
padding: 1rem;
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/* Shared internals */
|
|
374
|
-
.smk-nav__link:hover {
|
|
375
|
-
opacity: 0.85;
|
|
376
|
-
}
|
|
377
|
-
.smk-nav[data-layout="grid"] .smk-nav__link:hover,
|
|
378
|
-
.smk-nav[data-layout="carousel"] .smk-nav__link:hover {
|
|
379
|
-
opacity: 1;
|
|
380
|
-
}
|
|
381
|
-
.smk-nav__meta {
|
|
382
|
-
flex: 1;
|
|
383
|
-
min-width: 0;
|
|
384
|
-
}
|
|
385
|
-
.smk-nav__title {
|
|
386
|
-
font-family: var(--smk-font-serif);
|
|
387
|
-
font-size: 1.15rem;
|
|
388
|
-
font-weight: 700;
|
|
389
|
-
line-height: 1.3;
|
|
390
|
-
margin: 0 0 0.5rem;
|
|
391
|
-
letter-spacing: -0.01em;
|
|
392
|
-
}
|
|
393
|
-
.smk-nav__excerpt {
|
|
394
|
-
font-size: 0.9rem;
|
|
395
|
-
color: var(--smk-fg-muted);
|
|
396
|
-
margin: 0;
|
|
397
|
-
line-height: 1.55;
|
|
398
|
-
display: -webkit-box;
|
|
399
|
-
-webkit-line-clamp: 2;
|
|
400
|
-
-webkit-box-orient: vertical;
|
|
401
|
-
overflow: hidden;
|
|
402
|
-
}
|
|
403
|
-
.smk-nav__date {
|
|
404
|
-
display: block;
|
|
405
|
-
font-size: 0.75rem;
|
|
406
|
-
color: var(--smk-fg-subtle);
|
|
407
|
-
margin-top: 0.6rem;
|
|
408
|
-
letter-spacing: 0.04em;
|
|
409
|
-
text-transform: uppercase;
|
|
410
|
-
}
|
|
411
|
-
.smk-nav__empty {
|
|
412
|
-
color: var(--smk-fg-subtle);
|
|
413
|
-
font-style: italic;
|
|
414
|
-
padding: 1rem 0;
|
|
415
|
-
}
|
|
416
|
-
.smk-nav__view-all {
|
|
417
|
-
display: inline-flex;
|
|
418
|
-
align-items: center;
|
|
419
|
-
gap: 0.4rem;
|
|
420
|
-
margin-top: 1.5rem;
|
|
421
|
-
color: var(--smk-accent);
|
|
422
|
-
font-size: 0.875rem;
|
|
423
|
-
font-weight: 600;
|
|
424
|
-
text-decoration: none;
|
|
425
|
-
}
|
|
426
|
-
.smk-nav__view-all::after {
|
|
427
|
-
content: "→";
|
|
428
|
-
transition: transform 0.15s;
|
|
429
|
-
}
|
|
430
|
-
.smk-nav__view-all:hover::after {
|
|
431
|
-
transform: translateX(3px);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
/* ============================================================
|
|
435
|
-
Nav-search — inline filter input
|
|
436
|
-
SDK emits: \`<section class="smk-block smk-block--nav-search smk-nav-search">\`
|
|
437
|
-
with \`data-smk-search-input\` / \`data-smk-search-results\` /
|
|
438
|
-
\`data-smk-search-empty\` attributes the IIFE wires up.
|
|
439
|
-
============================================================ */
|
|
440
|
-
.smk-nav-search {
|
|
441
|
-
padding: 2rem 0;
|
|
442
|
-
border-top: 1px solid var(--smk-border);
|
|
443
|
-
}
|
|
444
|
-
.smk-nav-search__shell {
|
|
445
|
-
position: relative;
|
|
446
|
-
}
|
|
447
|
-
.smk-nav-search__input {
|
|
448
|
-
width: 100%;
|
|
449
|
-
padding: 0.875rem 1.125rem;
|
|
450
|
-
border: 1px solid var(--smk-border);
|
|
451
|
-
border-radius: var(--smk-radius-md);
|
|
452
|
-
background: var(--smk-bg);
|
|
453
|
-
color: var(--smk-fg);
|
|
454
|
-
font-size: 1rem;
|
|
455
|
-
font-family: inherit;
|
|
456
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
457
|
-
}
|
|
458
|
-
.smk-nav-search__input::placeholder {
|
|
459
|
-
color: var(--smk-fg-subtle);
|
|
460
|
-
}
|
|
461
|
-
.smk-nav-search__input:focus {
|
|
462
|
-
outline: none;
|
|
463
|
-
border-color: var(--smk-fg);
|
|
464
|
-
box-shadow: 0 0 0 4px color-mix(in srgb, var(--smk-fg) 8%, transparent);
|
|
465
|
-
}
|
|
466
|
-
.smk-nav-search__list {
|
|
467
|
-
list-style: none;
|
|
468
|
-
padding: 0;
|
|
469
|
-
margin: 0.75rem 0 0;
|
|
470
|
-
border: 1px solid var(--smk-border);
|
|
471
|
-
border-radius: var(--smk-radius-md);
|
|
472
|
-
background: var(--smk-bg);
|
|
473
|
-
overflow: hidden;
|
|
474
|
-
box-shadow: 0 4px 12px -4px rgb(0 0 0 / 0.1);
|
|
475
|
-
}
|
|
476
|
-
.smk-nav-search__result + .smk-nav-search__result {
|
|
477
|
-
border-top: 1px solid var(--smk-border);
|
|
478
|
-
}
|
|
479
|
-
.smk-nav-search__result a {
|
|
480
|
-
display: block;
|
|
481
|
-
padding: 0.875rem 1.125rem;
|
|
482
|
-
color: var(--smk-fg);
|
|
483
|
-
text-decoration: none;
|
|
484
|
-
transition: background 0.1s;
|
|
485
|
-
}
|
|
486
|
-
.smk-nav-search__result a:hover {
|
|
487
|
-
background: var(--smk-bg-soft);
|
|
488
|
-
}
|
|
489
|
-
.smk-nav-search__empty {
|
|
490
|
-
color: var(--smk-fg-subtle);
|
|
491
|
-
padding: 1rem 0 0;
|
|
492
|
-
font-size: 0.9rem;
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
/* ============================================================
|
|
496
|
-
Carousel — Apple-newsroom style horizontal carousel with
|
|
497
|
-
\`data-aspect\` token-driven aspect-ratio, container-query-anchored
|
|
498
|
-
nav buttons, and dots indicator.
|
|
499
|
-
SDK emits:
|
|
500
|
-
<smking-carousel class="smk-block smk-block--carousel smk-carousel"
|
|
501
|
-
data-aspect="16:9|4:3|1:1|3:4|9:16">
|
|
502
|
-
<div class="smk-carousel__viewport">
|
|
503
|
-
<smking-slide>
|
|
504
|
-
<div class="smk-carousel__image-frame">
|
|
505
|
-
<img class="smk-carousel__image" /> or .__image--placeholder
|
|
506
|
-
</div>
|
|
507
|
-
<div class="smk-carousel__caption-row">
|
|
508
|
-
<div class="smk-carousel__caption">…</div>
|
|
509
|
-
<a class="smk-carousel__download" href />
|
|
510
|
-
</div>
|
|
511
|
-
</smking-slide>
|
|
512
|
-
</div>
|
|
513
|
-
<button class="smk-carousel__nav-prev|next" />
|
|
514
|
-
<div class="smk-carousel__dots">
|
|
515
|
-
<button class="smk-carousel__dot" aria-current="true|false" />
|
|
516
|
-
</div>
|
|
517
|
-
</smking-carousel>
|
|
518
|
-
|
|
519
|
-
Web Component IIFE (SMKING_ELEMENTS_IIFE) upgrades the markup with
|
|
520
|
-
swipe / scroll-snap / dot-binding behaviour client-side. CSS below
|
|
521
|
-
drives all visual state — image cover-fill, nav-button vertical
|
|
522
|
-
centering anchored to image height (cqw container-queries), dot
|
|
523
|
-
active state.
|
|
524
|
-
============================================================ */
|
|
525
|
-
smking-carousel,
|
|
526
|
-
.smk-carousel {
|
|
527
|
-
display: block;
|
|
528
|
-
position: relative;
|
|
529
|
-
margin: 2.5rem 0;
|
|
530
|
-
padding-top: 2.5rem; /* room for editor settings overlay; harmless on customer side */
|
|
531
|
-
padding-bottom: 1.5rem;
|
|
532
|
-
container-type: inline-size; /* enables \`cqw\` for nav-button vertical anchor */
|
|
533
|
-
--smk-carousel-aspect: 16 / 9;
|
|
534
|
-
--smk-carousel-nav-top: 28.125cqw; /* 50cqw × 9/16 = half of image height */
|
|
535
|
-
}
|
|
536
|
-
.smk-carousel[data-aspect="4:3"],
|
|
537
|
-
smking-carousel[data-aspect="4:3"] {
|
|
538
|
-
--smk-carousel-aspect: 4 / 3;
|
|
539
|
-
--smk-carousel-nav-top: 37.5cqw;
|
|
540
|
-
}
|
|
541
|
-
.smk-carousel[data-aspect="1:1"],
|
|
542
|
-
smking-carousel[data-aspect="1:1"] {
|
|
543
|
-
--smk-carousel-aspect: 1 / 1;
|
|
544
|
-
--smk-carousel-nav-top: 50cqw;
|
|
545
|
-
}
|
|
546
|
-
.smk-carousel[data-aspect="3:4"],
|
|
547
|
-
smking-carousel[data-aspect="3:4"] {
|
|
548
|
-
--smk-carousel-aspect: 3 / 4;
|
|
549
|
-
--smk-carousel-nav-top: 66.667cqw;
|
|
550
|
-
}
|
|
551
|
-
.smk-carousel[data-aspect="9:16"],
|
|
552
|
-
smking-carousel[data-aspect="9:16"] {
|
|
553
|
-
--smk-carousel-aspect: 9 / 16;
|
|
554
|
-
--smk-carousel-nav-top: 88.889cqw;
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
.smk-carousel__viewport {
|
|
558
|
-
display: flex;
|
|
559
|
-
overflow-x: auto;
|
|
560
|
-
scroll-snap-type: x mandatory;
|
|
561
|
-
scroll-behavior: smooth;
|
|
562
|
-
scrollbar-width: none;
|
|
563
|
-
-webkit-overflow-scrolling: touch;
|
|
564
|
-
}
|
|
565
|
-
.smk-carousel__viewport::-webkit-scrollbar {
|
|
566
|
-
display: none;
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
smking-slide,
|
|
570
|
-
.smk-carousel__slide {
|
|
571
|
-
display: flex;
|
|
572
|
-
flex-direction: column;
|
|
573
|
-
flex: 0 0 100%;
|
|
574
|
-
scroll-snap-align: start;
|
|
575
|
-
min-width: 0;
|
|
576
|
-
padding-inline: 0.5rem;
|
|
577
|
-
gap: 1rem;
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
/* Image frame — fixed-aspect box. Image cover-fills. */
|
|
581
|
-
.smk-carousel__image-frame {
|
|
582
|
-
position: relative;
|
|
583
|
-
aspect-ratio: var(--smk-carousel-aspect);
|
|
584
|
-
overflow: hidden;
|
|
585
|
-
border-radius: var(--smk-radius-md);
|
|
586
|
-
}
|
|
587
|
-
.smk-carousel__image {
|
|
588
|
-
display: block;
|
|
589
|
-
width: 100%;
|
|
590
|
-
height: 100%;
|
|
591
|
-
object-fit: cover;
|
|
592
|
-
}
|
|
593
|
-
.smk-carousel__image--placeholder {
|
|
594
|
-
width: 100%;
|
|
595
|
-
height: 100%;
|
|
596
|
-
display: flex;
|
|
597
|
-
align-items: center;
|
|
598
|
-
justify-content: center;
|
|
599
|
-
background: var(--smk-bg-soft);
|
|
600
|
-
border: 1px dashed var(--smk-border);
|
|
601
|
-
}
|
|
602
|
-
|
|
603
|
-
/* Caption row — text on the left, download icon on the right. Matches
|
|
604
|
-
Apple newsroom's per-slide attribution + asset-download pattern. */
|
|
605
|
-
.smk-carousel__caption-row {
|
|
606
|
-
display: flex;
|
|
607
|
-
align-items: center;
|
|
608
|
-
justify-content: space-between;
|
|
609
|
-
gap: 1rem;
|
|
610
|
-
padding-inline: 0.25rem;
|
|
611
|
-
}
|
|
612
|
-
.smk-carousel__caption {
|
|
613
|
-
flex: 1;
|
|
614
|
-
text-align: center;
|
|
615
|
-
font-size: 0.875rem;
|
|
616
|
-
color: var(--smk-fg-muted);
|
|
617
|
-
line-height: 1.5;
|
|
618
|
-
}
|
|
619
|
-
.smk-carousel__download {
|
|
620
|
-
display: inline-flex;
|
|
621
|
-
align-items: center;
|
|
622
|
-
justify-content: center;
|
|
623
|
-
width: 1.75rem;
|
|
624
|
-
height: 1.75rem;
|
|
625
|
-
border-radius: 9999px;
|
|
626
|
-
color: var(--smk-fg-muted);
|
|
627
|
-
text-decoration: none;
|
|
628
|
-
transition: background-color 0.15s, color 0.15s;
|
|
629
|
-
}
|
|
630
|
-
.smk-carousel__download:hover {
|
|
631
|
-
background: var(--smk-bg-soft);
|
|
632
|
-
color: var(--smk-fg);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
/* Nav buttons — circular cards overlapping the image edges. Top is
|
|
636
|
-
centered on the IMAGE (not the whole carousel) via container query:
|
|
637
|
-
image height = 100cqw × (ratio_h / ratio_w); half = nav-top var.
|
|
638
|
-
2.5rem offset = container padding-top. */
|
|
639
|
-
.smk-carousel__nav-prev,
|
|
640
|
-
.smk-carousel__nav-next {
|
|
641
|
-
position: absolute;
|
|
642
|
-
top: calc(2.5rem + var(--smk-carousel-nav-top));
|
|
643
|
-
transform: translateY(-50%);
|
|
644
|
-
z-index: 1;
|
|
645
|
-
display: inline-flex;
|
|
646
|
-
align-items: center;
|
|
647
|
-
justify-content: center;
|
|
648
|
-
width: 2.5rem;
|
|
649
|
-
height: 2.5rem;
|
|
650
|
-
padding: 0;
|
|
651
|
-
border-radius: 9999px;
|
|
652
|
-
border: 1px solid var(--smk-border);
|
|
653
|
-
background: var(--smk-bg);
|
|
654
|
-
color: var(--smk-fg);
|
|
655
|
-
cursor: pointer;
|
|
656
|
-
box-shadow: 0 1px 3px rgb(0 0 0 / 0.06);
|
|
657
|
-
transition: background-color 0.15s, transform 0.15s;
|
|
658
|
-
}
|
|
659
|
-
.smk-carousel__nav-prev { left: 0.75rem; }
|
|
660
|
-
.smk-carousel__nav-next { right: 0.75rem; }
|
|
661
|
-
.smk-carousel__nav-prev:hover,
|
|
662
|
-
.smk-carousel__nav-next:hover {
|
|
663
|
-
background: var(--smk-bg-soft);
|
|
664
|
-
}
|
|
665
|
-
.smk-carousel__nav-prev:active,
|
|
666
|
-
.smk-carousel__nav-next:active {
|
|
667
|
-
transform: translateY(-50%) scale(0.95);
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
/* Dots — small circles centered below the caption row. Inactive is
|
|
671
|
-
hairline grey, active fills with foreground colour. */
|
|
672
|
-
.smk-carousel__dots {
|
|
673
|
-
display: flex;
|
|
674
|
-
justify-content: center;
|
|
675
|
-
align-items: center;
|
|
676
|
-
gap: 0.5rem;
|
|
677
|
-
margin-top: 0.75rem;
|
|
678
|
-
}
|
|
679
|
-
.smk-carousel__dot {
|
|
680
|
-
width: 0.5rem;
|
|
681
|
-
height: 0.5rem;
|
|
682
|
-
padding: 0;
|
|
683
|
-
border-radius: 9999px;
|
|
684
|
-
border: none;
|
|
685
|
-
background: color-mix(in srgb, var(--smk-fg) 20%, transparent);
|
|
686
|
-
cursor: pointer;
|
|
687
|
-
transition: background-color 0.15s, transform 0.15s;
|
|
688
|
-
}
|
|
689
|
-
.smk-carousel__dot:hover {
|
|
690
|
-
background: color-mix(in srgb, var(--smk-fg) 45%, transparent);
|
|
691
|
-
}
|
|
692
|
-
.smk-carousel__dot[aria-current="true"] {
|
|
693
|
-
background: var(--smk-fg);
|
|
694
|
-
transform: scale(1.1);
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
/* ============================================================
|
|
698
|
-
Unknown block — forward-compat fallback
|
|
699
|
-
============================================================ */
|
|
700
|
-
.smk-block--unknown {
|
|
701
|
-
display: none;
|
|
702
|
-
}
|
|
703
|
-
}
|
|
704
|
-
`;
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* IIFE source for `<smking-carousel>` + `<smking-slide>` Web Component
|
|
3
|
-
* registration. The Next.js SDK inlines this as a `<script>` tag once
|
|
4
|
-
* per page so customer sites get the carousel interactivity without
|
|
5
|
-
* adding a `<script src>` to their layout.
|
|
6
|
-
*
|
|
7
|
-
* Idempotent: the IIFE guards each `customElements.define` call with a
|
|
8
|
-
* `customElements.get(...)` check, so multiple emits per page are
|
|
9
|
-
* harmless.
|
|
10
|
-
*
|
|
11
|
-
* Keep this string in sync with
|
|
12
|
-
* `apps/web/src/features/cms/sdk/smking-elements.ts` — the SaaS-side
|
|
13
|
-
* Plate editor imports the TS module directly, so both paths register
|
|
14
|
-
* the same runtime.
|
|
15
|
-
*/
|
|
16
|
-
export const SMKING_ELEMENTS_IIFE = `(function(){if(typeof window==="undefined"||typeof customElements==="undefined")return;
|
|
17
|
-
var C="smking-carousel",S="smking-slide";
|
|
18
|
-
if(customElements.get(C))return;
|
|
19
|
-
function nextFrame(cb){if(typeof requestAnimationFrame!=="undefined")return requestAnimationFrame(cb);return setTimeout(cb,16);}
|
|
20
|
-
function cancelFrame(id){if(typeof cancelAnimationFrame!=="undefined")return cancelAnimationFrame(id);return clearTimeout(id);}
|
|
21
|
-
function CarouselEl(){return Reflect.construct(HTMLElement,[],CarouselEl);}
|
|
22
|
-
CarouselEl.prototype=Object.create(HTMLElement.prototype);
|
|
23
|
-
CarouselEl.prototype.constructor=CarouselEl;
|
|
24
|
-
CarouselEl.prototype.connectedCallback=function(){var self=this;
|
|
25
|
-
var viewport=self.querySelector(".smk-carousel__viewport");
|
|
26
|
-
if(!viewport)return;
|
|
27
|
-
var dotsContainer=self.querySelector(".smk-carousel__dots");
|
|
28
|
-
var prevBtn=self.querySelector(".smk-carousel__nav-prev");
|
|
29
|
-
var nextBtn=self.querySelector(".smk-carousel__nav-next");
|
|
30
|
-
var slides=Array.prototype.slice.call(viewport.querySelectorAll(":scope > "+S));
|
|
31
|
-
if(dotsContainer){var dotEls=slides.map(function(_,i){var d=document.createElement("button");d.type="button";d.className="smk-carousel__dot";d.dataset.index=String(i);d.setAttribute("aria-label","Go to slide "+(i+1));if(i===0)d.setAttribute("aria-current","true");d.addEventListener("click",function(e){e.preventDefault();viewport.scrollTo({left:i*viewport.clientWidth,behavior:"smooth"});});return d;});dotsContainer.replaceChildren.apply(dotsContainer,dotEls);}
|
|
32
|
-
function onPrev(e){e.preventDefault();viewport.scrollBy({left:-viewport.clientWidth,behavior:"smooth"});}
|
|
33
|
-
function onNext(e){e.preventDefault();viewport.scrollBy({left:viewport.clientWidth,behavior:"smooth"});}
|
|
34
|
-
if(prevBtn)prevBtn.addEventListener("click",onPrev);
|
|
35
|
-
if(nextBtn)nextBtn.addEventListener("click",onNext);
|
|
36
|
-
var raf=0;
|
|
37
|
-
function onScroll(){if(raf)return;raf=nextFrame(function(){raf=0;var ai=Math.round(viewport.scrollLeft/Math.max(1,viewport.clientWidth));if(!dotsContainer)return;var dots=dotsContainer.querySelectorAll(".smk-carousel__dot");dots.forEach(function(d,i){if(i===ai)d.setAttribute("aria-current","true");else d.removeAttribute("aria-current");});});}
|
|
38
|
-
viewport.addEventListener("scroll",onScroll,{passive:true});
|
|
39
|
-
self._smkCleanup=function(){if(prevBtn)prevBtn.removeEventListener("click",onPrev);if(nextBtn)nextBtn.removeEventListener("click",onNext);viewport.removeEventListener("scroll",onScroll);if(raf)cancelFrame(raf);};};
|
|
40
|
-
CarouselEl.prototype.disconnectedCallback=function(){if(this._smkCleanup)this._smkCleanup();};
|
|
41
|
-
function SlideEl(){return Reflect.construct(HTMLElement,[],SlideEl);}
|
|
42
|
-
SlideEl.prototype=Object.create(HTMLElement.prototype);
|
|
43
|
-
SlideEl.prototype.constructor=SlideEl;
|
|
44
|
-
customElements.define(C,CarouselEl);
|
|
45
|
-
customElements.define(S,SlideEl);
|
|
46
|
-
})();`;
|