@bytesbrains/weblocks 0.3.0 → 0.5.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/AGENT.md CHANGED
@@ -46,18 +46,25 @@ its exact config schema:
46
46
  - `catalogPrompt()` — a compact text menu for a system prompt.
47
47
  - `CATALOG.md` — the same, human-readable.
48
48
 
49
- The 39 block types, by group:
49
+ The 42 block types, by group:
50
50
 
51
51
  - **Chrome/app-shell:** `nav`, `app-shell`, `sidebar`, `announcement-bar`, `footer`
52
52
  - **Heroes:** `hero`, `hero-app`
53
53
  - **Content:** `features`, `about`, `rich-text`, `split`, `steps`, `stats`,
54
54
  `services-catalogue`, `pricing`, `logos`, `team`
55
- - **Media:** `gallery`, `carousel`, `video`, `map`
55
+ - **Media:** `gallery`, `carousel`, `video`, `video-gallery`, `map`
56
56
  - **Location:** `directions` (deep links to open the visitor’s map app)
57
57
  - **Structured:** `timeline`, `tabs`, `accordion`, `testimonials`, `faq`
58
58
  - **Collections:** `blog-list`, `blog-post`, `feed`
59
59
  - **Dynamic (powered):** `contact-form`, `newsletter`, `search`, `auth`
60
- - **Conversion/rhythm:** `cta`, `social-links`, `contact-details`, `divider`, `spacer`
60
+ - **Conversion/rhythm:** `cta`, `social-links`, `contact-details`, `divider`, `spacer`, `copyright`
61
+ - **Legal:** `legal` (terms/privacy links that open safe-Markdown dialogs; content is Markdown, never raw HTML)
62
+
63
+ Interactivity is safe to enable: `gallery` with `lightbox: true`, `carousel` with
64
+ `autoplay: true`, `tabs`/`accordion`, etc. work out of the box — CSS-only where
65
+ possible, and the host serves small shipped islands (`gallery` lightbox,
66
+ `carousel` controls) for the rest. You just set the typed config; you never write
67
+ the JavaScript.
61
68
 
62
69
  ## Composing a manifest
63
70
 
package/CATALOG.md CHANGED
@@ -1,8 +1,8 @@
1
- # @bytesbrains/weblocks — Block Catalog (v0.3.0)
1
+ # @bytesbrains/weblocks — Block Catalog (v0.5.0)
2
2
 
3
3
  The AI composes a `SiteManifest` (`{ meta, design, blocks[] }`) using **only** the block types below, then the engine validates + renders it to static HTML. This file is generated from the code (`npm run emit:catalog`) — do not edit by hand.
4
4
 
5
- **Block types:** `app-shell` · `nav` · `announcement-bar` · `sidebar` · `hero` · `hero-app` · `features` · `about` · `rich-text` · `split` · `steps` · `stats` · `services-catalogue` · `pricing` · `logos` · `team` · `gallery` · `carousel` · `video` · `map` · `timeline` · `tabs` · `accordion` · `testimonials` · `faq` · `blog-list` · `blog-post` · `feed` · `contact-form` · `newsletter` · `search` · `auth` · `cta` · `social-links` · `contact-details` · `directions` · `divider` · `spacer` · `footer`
5
+ **Block types:** `app-shell` · `nav` · `announcement-bar` · `sidebar` · `hero` · `hero-app` · `features` · `about` · `rich-text` · `split` · `steps` · `stats` · `services-catalogue` · `pricing` · `logos` · `team` · `gallery` · `carousel` · `video` · `video-gallery` · `map` · `timeline` · `tabs` · `accordion` · `testimonials` · `faq` · `blog-list` · `blog-post` · `feed` · `contact-form` · `newsletter` · `search` · `auth` · `cta` · `social-links` · `contact-details` · `directions` · `legal` · `divider` · `spacer` · `copyright` · `footer`
6
6
 
7
7
  ## `app-shell`
8
8
 
@@ -203,6 +203,17 @@ A responsive embedded video from YouTube or Vimeo (by id or URL) or a self-hoste
203
203
  | `poster` | string | | |
204
204
  | `caption` | string | | |
205
205
 
206
+ ## `video-gallery`
207
+
208
+ A grid or carousel of click-to-play video cards (YouTube/Vimeo/file); each loads its player inline on click (a lightweight facade — no heavy iframes up front).
209
+
210
+ | field | type | required | notes |
211
+ |---|---|---|---|
212
+ | `title` | string | | |
213
+ | `layout` | undefined (grid\|carousel) | | |
214
+ | `columns` | integer (2\|3\|4) | | |
215
+ | `items` | array | | |
216
+
206
217
  ## `map`
207
218
 
208
219
  An embedded location map for a place or address query, with configurable zoom and height and a link to the full map.
@@ -352,11 +363,14 @@ A full-width call-to-action band: a headline, optional subheading, and one butto
352
363
 
353
364
  ## `social-links`
354
365
 
355
- A centered row of links to social or external profiles, each an optional icon plus a label.
366
+ A row or grid of links to social/contact profiles; each link picks a built-in brand icon by platform (or a custom emoji), shown labeled or icon-only.
356
367
 
357
368
  | field | type | required | notes |
358
369
  |---|---|---|---|
359
370
  | `title` | string | | |
371
+ | `layout` | undefined (row\|grid) | | |
372
+ | `variant` | undefined (labeled\|icon) | | |
373
+ | `align` | undefined (start\|center\|end) | | |
360
374
  | `links` | array | | |
361
375
 
362
376
  ## `contact-details`
@@ -386,6 +400,17 @@ A location card with deep links that open the visitor’s map app (Google/Apple
386
400
  | `directionsLabel` | string | | |
387
401
  | `appleMaps` | boolean | | |
388
402
 
403
+ ## `legal`
404
+
405
+ Policy links (terms, privacy, cookies, …) that open as scrollable, dismissible dialogs; each body is safe Markdown (never raw HTML). No JavaScript.
406
+
407
+ | field | type | required | notes |
408
+ |---|---|---|---|
409
+ | `title` | string | | |
410
+ | `align` | undefined (start\|center\|end) | | |
411
+ | `separator` | string | | |
412
+ | `documents` | array | | |
413
+
389
414
  ## `divider`
390
415
 
391
416
  A visual section break rendered as a thin line, a dotted rule, or a gradient bar.
@@ -402,6 +427,18 @@ Deliberate vertical whitespace between sections, in one of four sizes (sm, md, l
402
427
  |---|---|---|---|
403
428
  | `size` | undefined (sm\|md\|lg\|xl) | | |
404
429
 
430
+ ## `copyright`
431
+
432
+ A small copyright bar (© year holder + rights text) for the bottom of a page; the year auto-fills to the current year when left blank.
433
+
434
+ | field | type | required | notes |
435
+ |---|---|---|---|
436
+ | `holder` | string | | |
437
+ | `year` | string | | |
438
+ | `text` | string | | |
439
+ | `showSymbol` | boolean | | |
440
+ | `align` | undefined (start\|center\|end) | | |
441
+
405
442
  ## `footer`
406
443
 
407
444
  Page footer: brand, tagline, a row of links, and a copyright line. Place last.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,54 @@ follows [semantic versioning](https://semver.org): the **block catalog** and the
5
5
  **`SiteManifest` shape** are the public contract — additive block/field changes
6
6
  are minor, breaking changes to either are major.
7
7
 
8
+ ## 0.5.0 — 2026-07-16
9
+
10
+ More media & footer blocks, brand icons, and shipped interactivity. Additive and
11
+ **non-breaking** — every `0.4.0` manifest still validates and renders identically.
12
+ Catalog 40 → 42.
13
+
14
+ ### Added
15
+ - **`video-gallery` block** — a grid or carousel of **click-to-play video cards**
16
+ (YouTube / Vimeo / self-hosted). Each card is a lightweight *facade* (thumbnail
17
+ + play button); the new **`video.js` island** loads the real player inline on
18
+ click, so no heavy iframes load up front. YouTube thumbnails auto-derive from
19
+ the id; no-JS clicks open the platform.
20
+ - **`copyright` block** — a small "© year holder · rights" bar for the bottom of
21
+ a page (a standalone alternative to the `footer` copyright line). The year
22
+ auto-fills to the current year when blank; configurable holder, note, symbol,
23
+ and alignment.
24
+ - **`social-links` — built-in brand icons & layouts.** Each link takes a typed
25
+ `platform` (x/twitter, instagram, facebook, linkedin, youtube, github, tiktok,
26
+ whatsapp, telegram, discord, mastodon, rss, email, website, phone) that supplies
27
+ a monochrome brand icon (simple-icons, CC0; themed via `currentColor`) and a
28
+ default label — `custom` still takes your own emoji/glyph. New `layout`
29
+ (row/grid), `variant` (labeled/icon-only), and `align` options. Non-breaking.
30
+ - **Shipped interactive islands** — the engine now ships the client scripts for
31
+ its interactive blocks (previously host-provided), as zero-dependency browser
32
+ modules under the `./islands/*.js` subpath export:
33
+ - `lightbox.js` — `gallery` click-to-zoom: prev/next, keyboard, swipe, Esc,
34
+ caption, scroll-lock, focus restore.
35
+ - `carousel.js` — `carousel` arrows, dot indicators, keyboard nav, and optional
36
+ autoplay (honours `prefers-reduced-motion`, pauses on hover/focus).
37
+ - `renderSite(manifest, { islandBase })` to configure where island scripts are
38
+ served (default `/_island`).
39
+ - Islands are built by a separate `tsconfig.islands.json` (DOM lib); the engine's
40
+ Node code stays DOM-free. `gallery` now marks `data-wl-lightbox` when its
41
+ lightbox is enabled.
42
+
43
+ ## 0.4.0 — 2026-07-16
44
+
45
+ Additive and non-breaking — every `0.3.0` manifest still validates and renders
46
+ identically. Catalog 39 → 40.
47
+
48
+ ### Added
49
+ - **`legal` block** — policy links (Terms, Privacy, Cookies, …) that open as
50
+ **scrollable, dismissible dialogs** (pure CSS `:target`, no JavaScript). Each
51
+ document's body is authored in a **safe Markdown subset** (headings, lists,
52
+ links, emphasis, quotes, rules) — rich formatting, but **never raw HTML** (any
53
+ literal HTML is escaped, not executed). Exports `renderMarkdown`. Catalog now
54
+ 40 blocks.
55
+
8
56
  ## 0.3.0 — 2026-07-16
9
57
 
10
58
  Additive and non-breaking — every `0.2.0` manifest still validates and renders
package/README.md CHANGED
@@ -139,19 +139,20 @@ import catalogJson from '@bytesbrains/weblocks/catalog.json' with { type: 'json'
139
139
 
140
140
  ## Block catalog
141
141
 
142
- **39 typed blocks.** Full field reference in [`CATALOG.md`](./CATALOG.md).
142
+ **42 typed blocks.** Full field reference in [`CATALOG.md`](./CATALOG.md).
143
143
 
144
144
  | Group | Blocks |
145
145
  |---|---|
146
146
  | Chrome / app-shell | `nav` · `app-shell` · `sidebar` · `announcement-bar` · `footer` |
147
147
  | Heroes | `hero` · `hero-app` |
148
148
  | Content | `features` · `about` · `rich-text` · `split` · `steps` · `stats` · `services-catalogue` · `pricing` · `logos` · `team` |
149
- | Media | `gallery` · `carousel` · `video` · `map` |
149
+ | Media | `gallery` · `carousel` · `video` · `video-gallery` · `map` |
150
150
  | Location | `directions` (deep links to the visitor’s map app) |
151
151
  | Structured | `timeline` · `tabs` · `accordion` · `testimonials` · `faq` |
152
152
  | Collections | `blog-list` · `blog-post` · `feed` |
153
153
  | Dynamic (powered) | `contact-form` · `newsletter` · `search` · `auth` |
154
- | Conversion / rhythm | `cta` · `social-links` · `contact-details` · `divider` · `spacer` |
154
+ | Conversion / rhythm | `cta` · `social-links` · `contact-details` · `divider` · `spacer` · `copyright` |
155
+ | Legal | `legal` (terms/privacy links → safe-Markdown dialogs) |
155
156
 
156
157
  `rich-text` and `blog-post` carry **typed** content nodes (headings, paragraphs,
157
158
  quotes, lists) — a safe freeform-content escape hatch that is never raw HTML.
@@ -233,6 +234,30 @@ for (const [file, body] of Object.entries(emitPwa(manifest) ?? {})) writeFileSyn
233
234
  // → manifest.webmanifest + sw.js
234
235
  ```
235
236
 
237
+ ## Interactivity (islands)
238
+
239
+ Pages are static-first: JavaScript ships only for the interactive blocks that need
240
+ it, and only when their behaviour is on. `renderSite` emits a marker for each —
241
+ `<script type="module" src="/_island/<name>.js">` — and the engine **ships the
242
+ island scripts** (zero-dependency, ~6 KB each) under a subpath:
243
+
244
+ | Block | Island | Behaviour |
245
+ |---|---|---|
246
+ | `gallery` (`lightbox: true`) | `lightbox.js` | click-to-zoom, prev/next, keyboard, swipe, Esc |
247
+ | `carousel` | `carousel.js` | arrows, dots, keyboard, optional autoplay |
248
+ | `video-gallery` | `video.js` | click-to-play cards (load the real player inline on press) |
249
+
250
+ Serve them at the island URL. Copy from the package's `./islands/*.js` export, e.g.:
251
+
252
+ ```ts
253
+ import lightbox from '@bytesbrains/weblocks/islands/lightbox.js?url'; // bundler
254
+ // or, for a static host, copy node_modules/@bytesbrains/weblocks/lib/islands/*.js
255
+ // to /_island/ (change the base with renderSite(m, { islandBase: '/assets/js' }))
256
+ ```
257
+
258
+ Blocks whose behaviour is off (e.g. `tabs`, `accordion` — CSS-only) ship no JS at
259
+ all.
260
+
236
261
  ## API reference
237
262
 
238
263
  All exports are named; types are shipped (`lib/index.d.ts`).
package/catalog.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "package": "@bytesbrains/weblocks",
4
- "version": "0.3.0",
4
+ "version": "0.5.0",
5
5
  "description": "Block catalog for @bytesbrains/weblocks. Send this to the model as its API reference; it composes a SiteManifest ({ meta, design, blocks[] }) using ONLY these block types. The engine then validates and renders it.",
6
6
  "blockTypes": [
7
7
  "app-shell",
@@ -23,6 +23,7 @@
23
23
  "gallery",
24
24
  "carousel",
25
25
  "video",
26
+ "video-gallery",
26
27
  "map",
27
28
  "timeline",
28
29
  "tabs",
@@ -40,8 +41,10 @@
40
41
  "social-links",
41
42
  "contact-details",
42
43
  "directions",
44
+ "legal",
43
45
  "divider",
44
46
  "spacer",
47
+ "copyright",
45
48
  "footer"
46
49
  ],
47
50
  "blocks": [
@@ -1029,6 +1032,73 @@
1029
1032
  "additionalProperties": false
1030
1033
  }
1031
1034
  },
1035
+ {
1036
+ "type": "video-gallery",
1037
+ "description": "A grid or carousel of click-to-play video cards (YouTube/Vimeo/file); each loads its player inline on click (a lightweight facade — no heavy iframes up front).",
1038
+ "schema": {
1039
+ "type": "object",
1040
+ "properties": {
1041
+ "title": {
1042
+ "type": "string",
1043
+ "maxLength": 120,
1044
+ "default": ""
1045
+ },
1046
+ "layout": {
1047
+ "enum": [
1048
+ "grid",
1049
+ "carousel"
1050
+ ],
1051
+ "default": "grid"
1052
+ },
1053
+ "columns": {
1054
+ "type": "integer",
1055
+ "enum": [
1056
+ 2,
1057
+ 3,
1058
+ 4
1059
+ ],
1060
+ "default": 3
1061
+ },
1062
+ "items": {
1063
+ "type": "array",
1064
+ "items": {
1065
+ "type": "object",
1066
+ "properties": {
1067
+ "provider": {
1068
+ "enum": [
1069
+ "youtube",
1070
+ "vimeo",
1071
+ "file"
1072
+ ],
1073
+ "default": "youtube"
1074
+ },
1075
+ "src": {
1076
+ "type": "string",
1077
+ "maxLength": 500,
1078
+ "default": ""
1079
+ },
1080
+ "title": {
1081
+ "type": "string",
1082
+ "maxLength": 160,
1083
+ "default": ""
1084
+ },
1085
+ "poster": {
1086
+ "type": "string",
1087
+ "maxLength": 500,
1088
+ "default": ""
1089
+ }
1090
+ },
1091
+ "required": [
1092
+ "src"
1093
+ ],
1094
+ "additionalProperties": false
1095
+ },
1096
+ "maxItems": 24
1097
+ }
1098
+ },
1099
+ "additionalProperties": false
1100
+ }
1101
+ },
1032
1102
  {
1033
1103
  "type": "map",
1034
1104
  "description": "An embedded location map for a place or address query, with configurable zoom and height and a link to the full map.",
@@ -1736,7 +1806,7 @@
1736
1806
  },
1737
1807
  {
1738
1808
  "type": "social-links",
1739
- "description": "A centered row of links to social or external profiles, each an optional icon plus a label.",
1809
+ "description": "A row or grid of links to social/contact profiles; each link picks a built-in brand icon by platform (or a custom emoji), shown labeled or icon-only.",
1740
1810
  "schema": {
1741
1811
  "type": "object",
1742
1812
  "properties": {
@@ -1745,21 +1815,65 @@
1745
1815
  "maxLength": 120,
1746
1816
  "default": ""
1747
1817
  },
1818
+ "layout": {
1819
+ "enum": [
1820
+ "row",
1821
+ "grid"
1822
+ ],
1823
+ "default": "row"
1824
+ },
1825
+ "variant": {
1826
+ "enum": [
1827
+ "labeled",
1828
+ "icon"
1829
+ ],
1830
+ "default": "labeled"
1831
+ },
1832
+ "align": {
1833
+ "enum": [
1834
+ "start",
1835
+ "center",
1836
+ "end"
1837
+ ],
1838
+ "default": "center"
1839
+ },
1748
1840
  "links": {
1749
1841
  "type": "array",
1750
1842
  "items": {
1751
1843
  "type": "object",
1752
1844
  "properties": {
1753
- "label": {
1754
- "type": "string",
1755
- "maxLength": 40,
1756
- "default": ""
1845
+ "platform": {
1846
+ "enum": [
1847
+ "x",
1848
+ "twitter",
1849
+ "instagram",
1850
+ "facebook",
1851
+ "linkedin",
1852
+ "youtube",
1853
+ "github",
1854
+ "tiktok",
1855
+ "whatsapp",
1856
+ "telegram",
1857
+ "discord",
1858
+ "mastodon",
1859
+ "rss",
1860
+ "email",
1861
+ "website",
1862
+ "phone",
1863
+ "custom"
1864
+ ],
1865
+ "default": "custom"
1757
1866
  },
1758
1867
  "href": {
1759
1868
  "type": "string",
1760
1869
  "maxLength": 500,
1761
1870
  "default": ""
1762
1871
  },
1872
+ "label": {
1873
+ "type": "string",
1874
+ "maxLength": 60,
1875
+ "default": ""
1876
+ },
1763
1877
  "icon": {
1764
1878
  "type": "string",
1765
1879
  "maxLength": 8,
@@ -1767,12 +1881,11 @@
1767
1881
  }
1768
1882
  },
1769
1883
  "required": [
1770
- "label",
1771
1884
  "href"
1772
1885
  ],
1773
1886
  "additionalProperties": false
1774
1887
  },
1775
- "maxItems": 12
1888
+ "maxItems": 20
1776
1889
  }
1777
1890
  },
1778
1891
  "additionalProperties": false
@@ -1862,6 +1975,62 @@
1862
1975
  "additionalProperties": false
1863
1976
  }
1864
1977
  },
1978
+ {
1979
+ "type": "legal",
1980
+ "description": "Policy links (terms, privacy, cookies, …) that open as scrollable, dismissible dialogs; each body is safe Markdown (never raw HTML). No JavaScript.",
1981
+ "schema": {
1982
+ "type": "object",
1983
+ "properties": {
1984
+ "title": {
1985
+ "type": "string",
1986
+ "maxLength": 120,
1987
+ "default": ""
1988
+ },
1989
+ "align": {
1990
+ "enum": [
1991
+ "start",
1992
+ "center",
1993
+ "end"
1994
+ ],
1995
+ "default": "center"
1996
+ },
1997
+ "separator": {
1998
+ "type": "string",
1999
+ "maxLength": 4,
2000
+ "default": "·"
2001
+ },
2002
+ "documents": {
2003
+ "type": "array",
2004
+ "items": {
2005
+ "type": "object",
2006
+ "properties": {
2007
+ "label": {
2008
+ "type": "string",
2009
+ "maxLength": 60,
2010
+ "default": ""
2011
+ },
2012
+ "title": {
2013
+ "type": "string",
2014
+ "maxLength": 120,
2015
+ "default": ""
2016
+ },
2017
+ "content": {
2018
+ "type": "string",
2019
+ "maxLength": 40000,
2020
+ "default": ""
2021
+ }
2022
+ },
2023
+ "required": [
2024
+ "label"
2025
+ ],
2026
+ "additionalProperties": false
2027
+ },
2028
+ "maxItems": 8
2029
+ }
2030
+ },
2031
+ "additionalProperties": false
2032
+ }
2033
+ },
1865
2034
  {
1866
2035
  "type": "divider",
1867
2036
  "description": "A visual section break rendered as a thin line, a dotted rule, or a gradient bar.",
@@ -1899,6 +2068,43 @@
1899
2068
  "additionalProperties": false
1900
2069
  }
1901
2070
  },
2071
+ {
2072
+ "type": "copyright",
2073
+ "description": "A small copyright bar (© year holder + rights text) for the bottom of a page; the year auto-fills to the current year when left blank.",
2074
+ "schema": {
2075
+ "type": "object",
2076
+ "properties": {
2077
+ "holder": {
2078
+ "type": "string",
2079
+ "maxLength": 120,
2080
+ "default": ""
2081
+ },
2082
+ "year": {
2083
+ "type": "string",
2084
+ "maxLength": 24,
2085
+ "default": ""
2086
+ },
2087
+ "text": {
2088
+ "type": "string",
2089
+ "maxLength": 160,
2090
+ "default": "All rights reserved."
2091
+ },
2092
+ "showSymbol": {
2093
+ "type": "boolean",
2094
+ "default": true
2095
+ },
2096
+ "align": {
2097
+ "enum": [
2098
+ "start",
2099
+ "center",
2100
+ "end"
2101
+ ],
2102
+ "default": "center"
2103
+ }
2104
+ },
2105
+ "additionalProperties": false
2106
+ }
2107
+ },
1902
2108
  {
1903
2109
  "type": "footer",
1904
2110
  "description": "Page footer: brand, tagline, a row of links, and a copyright line. Place last.",
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Brand icon path data (24×24 viewBox, fill=currentColor). Sourced from
3
+ * simple-icons (CC0 / public domain). Generated from source — do not edit by hand.
4
+ */
5
+ export declare const BRAND_ICON_PATHS: Record<string, string>;
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Brand icon path data (24×24 viewBox, fill=currentColor). Sourced from
3
+ * simple-icons (CC0 / public domain). Generated from source — do not edit by hand.
4
+ */
5
+ export const BRAND_ICON_PATHS = {
6
+ discord: "M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z",
7
+ facebook: "M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z",
8
+ github: "M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12",
9
+ instagram: "M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077",
10
+ linkedin: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z",
11
+ mastodon: "M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z",
12
+ rss: "M19.199 24C19.199 13.467 10.533 4.8 0 4.8V0c13.165 0 24 10.835 24 24h-4.801zM3.291 17.415c1.814 0 3.293 1.479 3.293 3.295 0 1.813-1.485 3.29-3.301 3.29C1.47 24 0 22.526 0 20.71s1.475-3.294 3.291-3.295zM15.909 24h-4.665c0-6.169-5.075-11.245-11.244-11.245V8.09c8.727 0 15.909 7.184 15.909 15.91z",
13
+ telegram: "M11.944 0A12 12 0 0 0 0 12a12 12 0 0 0 12 12 12 12 0 0 0 12-12A12 12 0 0 0 12 0a12 12 0 0 0-.056 0zm4.962 7.224c.1-.002.321.023.465.14a.506.506 0 0 1 .171.325c.016.093.036.306.02.472-.18 1.898-.962 6.502-1.36 8.627-.168.9-.499 1.201-.82 1.23-.696.065-1.225-.46-1.9-.902-1.056-.693-1.653-1.124-2.678-1.8-1.185-.78-.417-1.21.258-1.91.177-.184 3.247-2.977 3.307-3.23.007-.032.014-.15-.056-.212s-.174-.041-.249-.024c-.106.024-1.793 1.14-5.061 3.345-.48.33-.913.49-1.302.48-.428-.008-1.252-.241-1.865-.44-.752-.245-1.349-.374-1.297-.789.027-.216.325-.437.893-.663 3.498-1.524 5.83-2.529 6.998-3.014 3.332-1.386 4.025-1.627 4.476-1.635z",
14
+ tiktok: "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z",
15
+ whatsapp: "M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51-.173-.008-.371-.01-.57-.01-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z",
16
+ x: "M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z",
17
+ youtube: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z",
18
+ };
@@ -0,0 +1,2 @@
1
+ import type { BlockSpec } from '../registry.js';
2
+ export declare const copyright: BlockSpec;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * `copyright` — a small "© year holder · rights" bar for the very bottom of a
3
+ * page. A standalone alternative to the `footer` block's copyright line: pair it
4
+ * with `legal` and `social-links` for a lightweight footer, or use it under the
5
+ * full `footer`. The year auto-fills to the current year when left blank (or give
6
+ * an explicit year / range like "2020–2026"). Static brick.
7
+ */
8
+ import { escapeHtml } from '../schema.js';
9
+ const schema = {
10
+ holder: { kind: 'string', default: '', max: 120 }, // e.g. "Acme Inc."
11
+ year: { kind: 'string', default: '', max: 24 }, // blank → current year
12
+ text: { kind: 'string', default: 'All rights reserved.', max: 160 }, // trailing note
13
+ showSymbol: { kind: 'boolean', default: true }, // the © symbol
14
+ align: { kind: 'enum', values: ['start', 'center', 'end'], default: 'center' },
15
+ };
16
+ const css = `
17
+ .blk-copyright{padding:var(--space) var(--space);background:var(--bg);color:var(--muted);border-top:1px solid color-mix(in srgb,var(--text) 10%,transparent)}
18
+ .blk-copyright .wrap{max-width:1080px;margin:0 auto;font-size:var(--fs-base)}
19
+ .blk-copyright.align-center .wrap{text-align:center}
20
+ .blk-copyright.align-end .wrap{text-align:right}
21
+ .blk-copyright p{margin:0}
22
+ `.trim();
23
+ function render(config) {
24
+ const holder = config.holder;
25
+ const yearIn = String(config.year ?? '').trim();
26
+ const year = yearIn || String(new Date().getFullYear()); // blank → current year
27
+ const text = config.text;
28
+ const symbol = config.showSymbol !== false;
29
+ const align = ['start', 'center', 'end'].includes(config.align) ? config.align : 'center';
30
+ let line = `${symbol ? '© ' : ''}${escapeHtml(year)}`;
31
+ if (holder)
32
+ line += ` ${escapeHtml(holder)}`;
33
+ // Add the note, but don't double punctuation when the holder ends in "." (Inc./Co./Ltd.).
34
+ if (text)
35
+ line += `${/[.!?]$/.test(line) ? ' ' : '. '}${escapeHtml(text)}`;
36
+ return `<footer class="blk-copyright align-${align}" aria-label="Copyright">
37
+ <div class="wrap">
38
+ <p>${line}</p>
39
+ </div>
40
+ </footer>`;
41
+ }
42
+ export const copyright = {
43
+ type: 'copyright',
44
+ description: 'A small copyright bar (© year holder + rights text) for the bottom of a page; the year auto-fills to the current year when left blank.',
45
+ schema,
46
+ css,
47
+ render,
48
+ };
@@ -44,8 +44,9 @@ function figure(it) {
44
44
  function render(config) {
45
45
  const columns = Number.isInteger(config.columns) ? config.columns : 3;
46
46
  const gap = GAP_REM[config.gap] ?? GAP_REM.md;
47
+ const lightbox = config.lightbox === true; // flag for the `lightbox` island
47
48
  const items = (config.items ?? []).map(figure).filter(Boolean);
48
- return `<section class="blk-gallery" aria-label="Gallery">
49
+ return `<section class="blk-gallery" aria-label="Gallery"${lightbox ? ' data-wl-lightbox="true"' : ''}>
49
50
  <div class="wrap">
50
51
  <div class="grid" style="--cols:${columns};--gap:${gap}">
51
52
  ${items.join('\n ')}
@@ -0,0 +1,2 @@
1
+ import type { BlockSpec } from '../registry.js';
2
+ export declare const legal: BlockSpec;