@noirmd/previewer 2.1.3 → 2.1.5

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/NReditor.js CHANGED
@@ -531,6 +531,22 @@ function parseHtmlAttrs(attrsString) {
531
531
  }
532
532
 
533
533
  // core/parser.ts
534
+ var VOID_ELEMENTS = /* @__PURE__ */ new Set([
535
+ "area",
536
+ "base",
537
+ "br",
538
+ "col",
539
+ "embed",
540
+ "hr",
541
+ "img",
542
+ "input",
543
+ "link",
544
+ "meta",
545
+ "param",
546
+ "source",
547
+ "track",
548
+ "wbr"
549
+ ]);
534
550
  function parseMarkdown(markdown2) {
535
551
  if (!markdown2) return [];
536
552
  const lines = markdown2.replace(/\r\n/g, "\n").replace(/\r/g, "").split("\n");
@@ -544,8 +560,19 @@ function parseMarkdown(markdown2) {
544
560
  if (match = trimmed.match(/^(#{1,6})\s+(.+)$/)) {
545
561
  const level = match[1].length;
546
562
  const rawText = match[2];
547
- const { text: text2, classes: classes2, id: customId } = extractAttributes(rawText);
548
- const baseId = customId || generateId(text2.replace(/->|<-/g, ""));
563
+ const { text: rawParsedText, classes: classes2, id: customId } = extractAttributes(rawText);
564
+ let text2 = rawParsedText;
565
+ let align;
566
+ const alignCenter = text2.match(/^->\s*(.+?)\s*<-\s*$/);
567
+ const alignRight = text2.match(/^->\s*(.+?)\s*->\s*$/);
568
+ if (alignCenter) {
569
+ text2 = alignCenter[1];
570
+ align = "center";
571
+ } else if (alignRight) {
572
+ text2 = alignRight[1];
573
+ align = "right";
574
+ }
575
+ const baseId = customId || generateId(text2);
549
576
  let id2 = baseId;
550
577
  let n = 1;
551
578
  while (usedIds.has(id2)) {
@@ -553,7 +580,7 @@ function parseMarkdown(markdown2) {
553
580
  id2 = `${baseId}-${n}`;
554
581
  }
555
582
  usedIds.add(id2);
556
- result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0 });
583
+ result.push({ type: "header", level, text: text2, id: id2, classes: classes2 || void 0, align });
557
584
  i++;
558
585
  continue;
559
586
  }
@@ -729,29 +756,13 @@ function parseMarkdown(markdown2) {
729
756
  let tagStartMatch = trimmed.match(/^<([a-zA-Z][\w-]*)/);
730
757
  if (tagStartMatch) {
731
758
  const tagName = tagStartMatch[1].toLowerCase();
732
- const voidElements = /* @__PURE__ */ new Set([
733
- "area",
734
- "base",
735
- "br",
736
- "col",
737
- "embed",
738
- "hr",
739
- "img",
740
- "input",
741
- "link",
742
- "meta",
743
- "param",
744
- "source",
745
- "track",
746
- "wbr"
747
- ]);
748
759
  const remainingText = lines.slice(i).join("\n");
749
760
  const openTagRegex = new RegExp(`^\\s*<${tagName}\\b([^>]*?)>`, "i");
750
761
  const openTagMatch = remainingText.match(openTagRegex);
751
762
  if (openTagMatch) {
752
763
  const fullOpenTag = openTagMatch[0];
753
764
  const attrs = openTagMatch[1].replace(/\s+/g, " ").trim();
754
- const isSelfClosing = fullOpenTag.endsWith("/>") || voidElements.has(tagName);
765
+ const isSelfClosing = fullOpenTag.endsWith("/>") || VOID_ELEMENTS.has(tagName);
755
766
  if (isSelfClosing) {
756
767
  const blockText = remainingText.substring(0, openTagMatch.index + fullOpenTag.length);
757
768
  const consumedLines = blockText.split("\n").length;
@@ -1274,11 +1285,77 @@ function parseInlinePart(part) {
1274
1285
  return document.createTextNode(part);
1275
1286
  }
1276
1287
 
1288
+ // vanilla/utils.ts
1289
+ var THEME_TOKENS = /* @__PURE__ */ new Set([
1290
+ "primary",
1291
+ "secondary",
1292
+ "accent",
1293
+ "neutral",
1294
+ "info",
1295
+ "success",
1296
+ "warning",
1297
+ "error"
1298
+ ]);
1299
+ function isThemeToken(color) {
1300
+ return !!color && THEME_TOKENS.has(color);
1301
+ }
1302
+ function isArbitraryColor(value) {
1303
+ if (THEME_TOKENS.has(value)) return false;
1304
+ if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
1305
+ if (/^[a-zA-Z]+$/.test(value)) return true;
1306
+ return false;
1307
+ }
1308
+ function applyBaseProps(el, props) {
1309
+ if (props.class) {
1310
+ el.classList.add(...props.class.split(/\s+/).filter(Boolean));
1311
+ }
1312
+ if (props.style) {
1313
+ const styles = parseCssString(props.style);
1314
+ for (const [key, value] of Object.entries(styles)) {
1315
+ el.style.setProperty(key, String(value));
1316
+ }
1317
+ }
1318
+ }
1319
+ function applyFloatStyle(el, float, width) {
1320
+ if (!float) return;
1321
+ if (float === "left" || float === "right") {
1322
+ el.style.float = float;
1323
+ if (!width) el.style.maxWidth = "50%";
1324
+ el.style.marginInlineStart = float === "right" ? "1rem" : "";
1325
+ el.style.marginInlineEnd = float === "left" ? "1rem" : "";
1326
+ } else if (float === "center") {
1327
+ el.style.marginInline = "auto";
1328
+ }
1329
+ }
1330
+ function applyColor(el, color, classSuffix) {
1331
+ if (!color) return "";
1332
+ if (isThemeToken(color)) {
1333
+ return ` ${classSuffix}--${color}`;
1334
+ }
1335
+ if (isArbitraryColor(color)) {
1336
+ el.style.background = color;
1337
+ el.style.color = "white";
1338
+ }
1339
+ return "";
1340
+ }
1341
+ function openModal(dialog) {
1342
+ if (!dialog.open) {
1343
+ document.body.appendChild(dialog);
1344
+ dialog.showModal();
1345
+ dialog.addEventListener("close", () => dialog.remove(), { once: true });
1346
+ }
1347
+ }
1348
+ var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
1349
+ function parseIntProp(value, defaultValue) {
1350
+ if (!value) return defaultValue;
1351
+ const n = parseInt(value, 10);
1352
+ return Number.isNaN(n) ? defaultValue : n;
1353
+ }
1354
+
1277
1355
  // vanilla/directives/admonition.ts
1278
1356
  var admonitionDirective = ({ directiveType, props, renderSlot }) => {
1279
1357
  const el = createAdmonition(directiveType, props.title, props.icon);
1280
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
1281
- if (props.style) el.setAttribute("style", props.style);
1358
+ applyBaseProps(el, props);
1282
1359
  const body = el.querySelector(".nr-admonition__body");
1283
1360
  if (body) {
1284
1361
  body.appendChild(renderSlot("default"));
@@ -1294,8 +1371,7 @@ var detailsDirective = ({ props, renderSlot }) => {
1294
1371
  props.icon,
1295
1372
  props.defaultOpen === "true"
1296
1373
  );
1297
- if (props.class) el.classList.add(...props.class.split(/\s+/).filter(Boolean));
1298
- if (props.style) el.setAttribute("style", props.style);
1374
+ applyBaseProps(el, props);
1299
1375
  const body = el.querySelector(".nr-details__body");
1300
1376
  if (body) {
1301
1377
  body.appendChild(renderSlot("default"));
@@ -1308,12 +1384,12 @@ var details_default = detailsDirective;
1308
1384
  var modalDirective = ({ props, renderSlot }) => {
1309
1385
  const label = props.label || props.title || "Open";
1310
1386
  const modalTitle = props.title || "Modal";
1311
- const customClass = props.class || "";
1387
+ const align = props.align || "left";
1312
1388
  const wrapper = document.createElement("div");
1313
- wrapper.className = "nr-modal-trigger";
1389
+ wrapper.className = `nr-modal-trigger${align === "center" ? " nr-modal-trigger--center" : align === "right" ? " nr-modal-trigger--right" : ""}`;
1314
1390
  const btn = document.createElement("button");
1315
1391
  btn.className = `nr-button nr-button--default`;
1316
- if (customClass) btn.classList.add(...customClass.split(/\s+/).filter(Boolean));
1392
+ applyBaseProps(btn, props);
1317
1393
  const icon = props.icon || "open_in_new";
1318
1394
  if (icon) btn.appendChild(createIcon(icon));
1319
1395
  btn.appendChild(document.createTextNode(label));
@@ -1325,15 +1401,7 @@ var modalDirective = ({ props, renderSlot }) => {
1325
1401
  prose.appendChild(renderSlot("default"));
1326
1402
  body.appendChild(prose);
1327
1403
  }
1328
- btn.addEventListener("click", () => {
1329
- if (!dialog.open) {
1330
- document.body.appendChild(dialog);
1331
- dialog.showModal();
1332
- dialog.addEventListener("close", () => {
1333
- dialog.remove();
1334
- }, { once: true });
1335
- }
1336
- });
1404
+ btn.addEventListener("click", () => openModal(dialog));
1337
1405
  wrapper.appendChild(btn);
1338
1406
  wrapper.appendChild(dialog);
1339
1407
  return wrapper;
@@ -1343,19 +1411,20 @@ var modal_default = modalDirective;
1343
1411
  // vanilla/directives/button.ts
1344
1412
  var buttonDirective = ({ props, renderSlot }) => {
1345
1413
  const url = props.url || props.href || "#";
1346
- const label = props.label;
1414
+ const label = props.label || props.title;
1347
1415
  const icon = props.icon || "near_me";
1348
1416
  const target = props.target || "_blank";
1349
1417
  const customClass = props.class || "";
1418
+ const align = props.align || "left";
1350
1419
  const wrapper = document.createElement("div");
1351
- wrapper.className = "nr-button-wrap";
1420
+ wrapper.className = `nr-button-wrap${align === "center" ? " nr-button-wrap--center" : align === "right" ? " nr-button-wrap--right" : ""}`;
1352
1421
  if (label) {
1353
1422
  const a = document.createElement("a");
1354
1423
  a.href = url;
1355
1424
  a.target = target;
1356
1425
  a.rel = "noopener noreferrer";
1357
1426
  a.className = "nr-button nr-button--default";
1358
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
1427
+ applyBaseProps(a, props);
1359
1428
  a.appendChild(createIcon(icon));
1360
1429
  a.appendChild(document.createTextNode(label));
1361
1430
  wrapper.appendChild(a);
@@ -1366,7 +1435,7 @@ var buttonDirective = ({ props, renderSlot }) => {
1366
1435
  if (links.length > 0) {
1367
1436
  links.forEach((link) => {
1368
1437
  link.classList.add("nr-button", "nr-button--default");
1369
- if (customClass) link.classList.add(...customClass.split(/\s+/).filter(Boolean));
1438
+ applyBaseProps(link, props);
1370
1439
  });
1371
1440
  wrapper.appendChild(slotContent);
1372
1441
  } else {
@@ -1375,7 +1444,7 @@ var buttonDirective = ({ props, renderSlot }) => {
1375
1444
  a.target = target;
1376
1445
  a.rel = "noopener noreferrer";
1377
1446
  a.className = "nr-button nr-button--default";
1378
- if (customClass) a.classList.add(...customClass.split(/\s+/).filter(Boolean));
1447
+ applyBaseProps(a, props);
1379
1448
  a.appendChild(createIcon(icon));
1380
1449
  a.appendChild(slotContent);
1381
1450
  wrapper.appendChild(a);
@@ -1398,12 +1467,9 @@ var cardDirective = ({
1398
1467
  const { isSingleCard } = options || {};
1399
1468
  const isModal = directiveType === "card-m";
1400
1469
  const isLink = directiveType === "card-b";
1401
- const inlineStyles = props.style ? parseCssString(props.style) : {};
1402
1470
  const card = document.createElement("div");
1403
1471
  card.className = `nr-card${isModal || isLink ? " nr-card--interactive" : ""} ${customClass}`.trim();
1404
- for (const [key, value] of Object.entries(inlineStyles)) {
1405
- card.style.setProperty(key, String(value));
1406
- }
1472
+ applyBaseProps(card, props);
1407
1473
  if (image) {
1408
1474
  const imgWrap = document.createElement("div");
1409
1475
  imgWrap.className = `nr-card__image${isSingleCard ? " nr-card__image--tall" : ""}`;
@@ -1482,13 +1548,7 @@ var cardDirective = ({
1482
1548
  prose.appendChild(renderSlot("content") || renderSlot("default"));
1483
1549
  modalBody.appendChild(prose);
1484
1550
  }
1485
- card.addEventListener("click", () => {
1486
- if (!dialog.open) {
1487
- document.body.appendChild(dialog);
1488
- dialog.showModal();
1489
- dialog.addEventListener("close", () => dialog.remove(), { once: true });
1490
- }
1491
- });
1551
+ card.addEventListener("click", () => openModal(dialog));
1492
1552
  const frag = document.createDocumentFragment();
1493
1553
  frag.appendChild(card);
1494
1554
  frag.appendChild(dialog);
@@ -1536,8 +1596,8 @@ var slideDirective = ({
1536
1596
  if (lines.length === 0) {
1537
1597
  return document.createDocumentFragment();
1538
1598
  }
1539
- const interval = parseInt(props.interval || "3000", 10);
1540
- const speed = parseInt(props.speed || "500", 10);
1599
+ const interval = parseIntProp(props.interval, 3e3);
1600
+ const speed = parseIntProp(props.speed, 500);
1541
1601
  const rawClass = props.class || "";
1542
1602
  const inlineStyle = props.style ? parseCssString(props.style) : {};
1543
1603
  const scopeClass = `sld-${++slideCounter}`;
@@ -1588,10 +1648,11 @@ var slideDirective = ({
1588
1648
  }
1589
1649
  });
1590
1650
  if (lines.length > 1) {
1591
- setInterval(() => {
1651
+ const id = setInterval(() => {
1592
1652
  current = (current + 1) % lines.length;
1593
1653
  track.style.transform = `translateY(${-current * maxH}px)`;
1594
1654
  }, interval);
1655
+ container.dataset.nrIntervalId = String(id);
1595
1656
  }
1596
1657
  return container;
1597
1658
  };
@@ -1601,8 +1662,7 @@ var slide_default = slideDirective;
1601
1662
  var keysDirective = ({ props, slots }) => {
1602
1663
  const wrap = document.createElement("div");
1603
1664
  wrap.className = "nr-keys";
1604
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
1605
- if (props.style) wrap.setAttribute("style", props.style);
1665
+ applyBaseProps(wrap, props);
1606
1666
  const sizeClass = props.size ? ` nr-kbd--${props.size}` : "";
1607
1667
  const parts = (slots.default || "").split("+").map((p) => p.trim()).filter(Boolean);
1608
1668
  parts.forEach((part, i) => {
@@ -1626,8 +1686,7 @@ var accordionCounter = 0;
1626
1686
  var accordionItemDirective = ({ props, renderSlot }) => {
1627
1687
  const item = document.createElement("div");
1628
1688
  item.className = "nr-accordion__item";
1629
- if (props.class) item.classList.add(...props.class.split(/\s+/).filter(Boolean));
1630
- if (props.style) item.setAttribute("style", props.style);
1689
+ applyBaseProps(item, props);
1631
1690
  const input = document.createElement("input");
1632
1691
  input.type = "radio";
1633
1692
  input.className = "nr-accordion__input";
@@ -1646,8 +1705,7 @@ var accordionItemDirective = ({ props, renderSlot }) => {
1646
1705
  var accordionDirective = ({ props, renderSlot }) => {
1647
1706
  const wrap = document.createElement("div");
1648
1707
  wrap.className = "nr-accordion";
1649
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
1650
- if (props.style) wrap.setAttribute("style", props.style);
1708
+ applyBaseProps(wrap, props);
1651
1709
  wrap.appendChild(renderSlot("default"));
1652
1710
  const mode = props.mode === "checkbox" ? "checkbox" : "radio";
1653
1711
  const group = `nr-acc-${++accordionCounter}`;
@@ -1660,7 +1718,6 @@ var accordionDirective = ({ props, renderSlot }) => {
1660
1718
  var accordion_default = accordionDirective;
1661
1719
 
1662
1720
  // vanilla/directives/carousel.ts
1663
- var IMG_RE = /!\[([^\]]*)\]\(([^)]+)\)/g;
1664
1721
  var carouselDirective = ({ props, slots }) => {
1665
1722
  const images = [];
1666
1723
  const raw = slots.default || "";
@@ -1676,19 +1733,9 @@ var carouselDirective = ({ props, slots }) => {
1676
1733
  wrap.className = "nr-carousel";
1677
1734
  wrap.tabIndex = 0;
1678
1735
  wrap.setAttribute("aria-label", "Image carousel");
1679
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
1680
- if (props.style) wrap.setAttribute("style", props.style);
1736
+ applyBaseProps(wrap, props);
1681
1737
  if (props.width) wrap.style.width = props.width;
1682
- if (props.float) {
1683
- if (props.float === "left" || props.float === "right") {
1684
- wrap.style.float = props.float;
1685
- if (!props.width) wrap.style.maxWidth = "50%";
1686
- wrap.style.marginInlineStart = props.float === "right" ? "1rem" : "";
1687
- wrap.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
1688
- } else if (props.float === "center") {
1689
- wrap.style.marginInline = "auto";
1690
- }
1691
- }
1738
+ applyFloatStyle(wrap, props.float, props.width);
1692
1739
  const viewport = document.createElement("div");
1693
1740
  viewport.className = "nr-carousel__viewport";
1694
1741
  if (props.height) viewport.style.height = props.height;
@@ -1758,11 +1805,10 @@ var DEFAULT_LABELS = ["days", "hours", "min", "sec"];
1758
1805
  var countdownDirective = ({ props }) => {
1759
1806
  const wrap = document.createElement("div");
1760
1807
  wrap.className = "nr-countdown";
1761
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
1762
- if (props.style) wrap.setAttribute("style", props.style);
1808
+ applyBaseProps(wrap, props);
1763
1809
  const labelParts = (props.labels || "").split("|").map((s) => s.trim());
1764
1810
  const labels = DEFAULT_LABELS.map((label, i) => labelParts[i] || label);
1765
- const digits = parseInt(props.digits || "2", 10);
1811
+ const digits = parseIntProp(props.digits, 2);
1766
1812
  const targetTime = props.target ? new Date(props.target).getTime() : NaN;
1767
1813
  const hasTarget = !Number.isNaN(targetTime);
1768
1814
  const blocks = [];
@@ -1809,13 +1855,15 @@ var countdownDirective = ({ props }) => {
1809
1855
  blocks.push({ value });
1810
1856
  });
1811
1857
  render();
1812
- if (hasTarget) setInterval(render, 1e3);
1858
+ if (hasTarget) {
1859
+ const id = setInterval(render, 1e3);
1860
+ wrap.dataset.nrIntervalId = String(id);
1861
+ }
1813
1862
  return wrap;
1814
1863
  };
1815
1864
  var countdown_default = countdownDirective;
1816
1865
 
1817
1866
  // vanilla/directives/diff.ts
1818
- var IMG_RE2 = /!\[([^\]]*)\]\(([^)]+)\)/g;
1819
1867
  var diffDirective = ({ props, slots }) => {
1820
1868
  let before = (props.before || "").split("#")[0].trim();
1821
1869
  let after = (props.after || "").split("#")[0].trim();
@@ -1823,8 +1871,8 @@ var diffDirective = ({ props, slots }) => {
1823
1871
  const urls = [];
1824
1872
  const raw = slots.default || "";
1825
1873
  let m;
1826
- IMG_RE2.lastIndex = 0;
1827
- while ((m = IMG_RE2.exec(raw)) !== null) {
1874
+ IMG_RE.lastIndex = 0;
1875
+ while ((m = IMG_RE.exec(raw)) !== null) {
1828
1876
  urls.push(m[2].split("#")[0].trim());
1829
1877
  }
1830
1878
  if (!before && urls.length > 0) before = urls[0];
@@ -1837,21 +1885,11 @@ var diffDirective = ({ props, slots }) => {
1837
1885
  figure.className = "nr-diff";
1838
1886
  figure.tabIndex = 0;
1839
1887
  figure.setAttribute("aria-label", "Image comparison slider");
1840
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
1841
- if (props.style) figure.setAttribute("style", props.style);
1888
+ applyBaseProps(figure, props);
1842
1889
  if (props.aspect) figure.style.aspectRatio = props.aspect;
1843
1890
  if (props.height) figure.style.height = props.height;
1844
1891
  if (props.width) figure.style.width = props.width;
1845
- if (props.float) {
1846
- if (props.float === "left" || props.float === "right") {
1847
- figure.style.float = props.float;
1848
- if (!props.width) figure.style.maxWidth = "50%";
1849
- figure.style.marginInlineStart = props.float === "right" ? "1rem" : "";
1850
- figure.style.marginInlineEnd = props.float === "left" ? "1rem" : "";
1851
- } else if (props.float === "center") {
1852
- figure.style.marginInline = "auto";
1853
- }
1854
- }
1892
+ applyFloatStyle(figure, props.float, props.width);
1855
1893
  const beforeItem = document.createElement("div");
1856
1894
  beforeItem.className = "nr-diff__item nr-diff__item--before";
1857
1895
  beforeItem.setAttribute("role", "img");
@@ -1914,8 +1952,7 @@ var diff_default = diffDirective;
1914
1952
  var hover3dDirective = ({ props, renderSlot }) => {
1915
1953
  const container = document.createElement("div");
1916
1954
  container.className = "nr-hover-3d";
1917
- if (props.class) container.classList.add(...props.class.split(/\s+/).filter(Boolean));
1918
- if (props.style) container.setAttribute("style", props.style);
1955
+ applyBaseProps(container, props);
1919
1956
  const stage = document.createElement("div");
1920
1957
  stage.className = "nr-hover-3d__stage";
1921
1958
  stage.appendChild(renderSlot("default"));
@@ -1928,14 +1965,13 @@ var hover3dDirective = ({ props, renderSlot }) => {
1928
1965
  var hover3d_default = hover3dDirective;
1929
1966
 
1930
1967
  // vanilla/directives/hovergallery.ts
1931
- var IMG_RE3 = /!\[([^\]]*)\]\(([^)]+)\)/g;
1932
1968
  var MAX_IMAGES = 10;
1933
1969
  var hovergalleryDirective = ({ props, slots }) => {
1934
1970
  const images = [];
1935
1971
  const raw = slots.default || "";
1936
1972
  let m;
1937
- IMG_RE3.lastIndex = 0;
1938
- while ((m = IMG_RE3.exec(raw)) !== null) {
1973
+ IMG_RE.lastIndex = 0;
1974
+ while ((m = IMG_RE.exec(raw)) !== null) {
1939
1975
  images.push({ src: m[2].split("#")[0].trim(), alt: m[1].trim() || "gallery image" });
1940
1976
  }
1941
1977
  if (images.length === 0) {
@@ -1944,9 +1980,8 @@ var hovergalleryDirective = ({ props, slots }) => {
1944
1980
  const count = Math.min(images.length, MAX_IMAGES);
1945
1981
  const figure = document.createElement("figure");
1946
1982
  figure.className = "nr-hover-gallery";
1983
+ applyBaseProps(figure, props);
1947
1984
  if (props.aspect) figure.style.aspectRatio = props.aspect;
1948
- if (props.class) figure.classList.add(...props.class.split(/\s+/).filter(Boolean));
1949
- if (props.style) figure.setAttribute("style", (figure.getAttribute("style") || "") + ";" + props.style);
1950
1985
  const imgEls = [];
1951
1986
  for (let i = 0; i < count; i++) {
1952
1987
  const el = document.createElement("img");
@@ -1996,28 +2031,11 @@ var hovergalleryDirective = ({ props, slots }) => {
1996
2031
  var hovergallery_default = hovergalleryDirective;
1997
2032
 
1998
2033
  // vanilla/directives/chat.ts
1999
- var CHAT_THEME_TOKENS = /* @__PURE__ */ new Set([
2000
- "primary",
2001
- "secondary",
2002
- "accent",
2003
- "neutral",
2004
- "info",
2005
- "success",
2006
- "warning",
2007
- "error"
2008
- ]);
2009
- function isArbitraryColor(value) {
2010
- if (CHAT_THEME_TOKENS.has(value)) return false;
2011
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
2012
- if (/^[a-zA-Z]+$/.test(value)) return true;
2013
- return false;
2014
- }
2015
2034
  var chatItemDirective = ({ props, renderSlot }) => {
2016
2035
  const side = props.side === "end" ? "end" : "start";
2017
2036
  const wrap = document.createElement("div");
2018
2037
  wrap.className = `nr-chat nr-chat--${side}`;
2019
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
2020
- if (props.style) wrap.setAttribute("style", props.style);
2038
+ applyBaseProps(wrap, props);
2021
2039
  const header = document.createElement("div");
2022
2040
  header.className = "nr-chat__header";
2023
2041
  if (props.name) {
@@ -2042,14 +2060,8 @@ var chatItemDirective = ({ props, renderSlot }) => {
2042
2060
  avatar.appendChild(img);
2043
2061
  wrap.appendChild(avatar);
2044
2062
  }
2045
- const isThemeToken = CHAT_THEME_TOKENS.has(props.color || "");
2046
- const colorClass = isThemeToken ? ` nr-chat__bubble--${props.color}` : "";
2047
2063
  const bubble = document.createElement("div");
2048
- bubble.className = `nr-chat__bubble${colorClass}`;
2049
- if (props.color && isArbitraryColor(props.color) && !isThemeToken) {
2050
- bubble.style.background = props.color;
2051
- bubble.style.color = "white";
2052
- }
2064
+ bubble.className = `nr-chat__bubble${applyColor(bubble, props.color, "nr-chat__bubble")}`;
2053
2065
  bubble.appendChild(renderSlot("default"));
2054
2066
  wrap.appendChild(bubble);
2055
2067
  if (props.footer) {
@@ -2063,8 +2075,7 @@ var chatItemDirective = ({ props, renderSlot }) => {
2063
2075
  var chatDirective = ({ props, renderSlot }) => {
2064
2076
  const wrap = document.createElement("div");
2065
2077
  wrap.className = "nr-chat";
2066
- if (props.class) wrap.classList.add(...props.class.split(/\s+/).filter(Boolean));
2067
- if (props.style) wrap.setAttribute("style", props.style);
2078
+ applyBaseProps(wrap, props);
2068
2079
  wrap.appendChild(renderSlot("default"));
2069
2080
  return wrap;
2070
2081
  };
@@ -2100,8 +2111,7 @@ function bindEventProp(el, eventProp) {
2100
2111
  var richlistItemDirective = ({ props, renderSlot }) => {
2101
2112
  const li = document.createElement("li");
2102
2113
  li.className = "nr-richlist__item";
2103
- if (props.class) li.classList.add(...props.class.split(/\s+/).filter(Boolean));
2104
- if (props.style) li.setAttribute("style", props.style);
2114
+ applyBaseProps(li, props);
2105
2115
  if (props.image) {
2106
2116
  const thumb = document.createElement("div");
2107
2117
  thumb.className = "nr-richlist__thumb";
@@ -2163,36 +2173,20 @@ var richlistItemDirective = ({ props, renderSlot }) => {
2163
2173
  var richlistDirective = ({ props, renderSlot }) => {
2164
2174
  const ul = document.createElement("ul");
2165
2175
  ul.className = "nr-richlist";
2166
- if (props.class) ul.classList.add(...props.class.split(/\s+/).filter(Boolean));
2167
- if (props.style) ul.setAttribute("style", props.style);
2176
+ applyBaseProps(ul, props);
2168
2177
  ul.appendChild(renderSlot("default"));
2169
2178
  return ul;
2170
2179
  };
2171
2180
  var richlist_default = richlistDirective;
2172
2181
 
2173
2182
  // vanilla/directives/stat.ts
2174
- var STAT_THEME_TOKENS = /* @__PURE__ */ new Set([
2175
- "primary",
2176
- "secondary",
2177
- "info",
2178
- "success",
2179
- "warning",
2180
- "error"
2181
- ]);
2182
- function isArbitraryColor2(value) {
2183
- if (STAT_THEME_TOKENS.has(value)) return false;
2184
- if (/^(#|rgb|hsl|oklch|oklab|lab|lch|color\()/i.test(value)) return true;
2185
- if (/^[a-zA-Z]+$/.test(value)) return true;
2186
- return false;
2187
- }
2188
2183
  var statDirective = ({ props }) => {
2189
- const isThemeToken = STAT_THEME_TOKENS.has(props.color || "");
2190
- const colorClass = isThemeToken ? ` nr-stat--${props.color}` : "";
2184
+ const statIsThemeToken = isThemeToken(props.color);
2185
+ const colorClass = statIsThemeToken ? ` nr-stat--${props.color}` : "";
2191
2186
  const stat = document.createElement("div");
2192
2187
  stat.className = `nr-stat${colorClass}`;
2193
- if (props.class) stat.classList.add(...props.class.split(/\s+/).filter(Boolean));
2194
- if (props.style) stat.setAttribute("style", props.style);
2195
- const useInlineColor = props.color && isArbitraryColor2(props.color) && !isThemeToken;
2188
+ applyBaseProps(stat, props);
2189
+ const useInlineColor = props.color && isArbitraryColor(props.color) && !statIsThemeToken;
2196
2190
  if (props.icon) {
2197
2191
  const figure = document.createElement("div");
2198
2192
  figure.className = "nr-stat__figure";
@@ -2285,9 +2279,12 @@ function renderHtmlString(html) {
2285
2279
  processedContent = processedContent.replace(
2286
2280
  /<style(?:\s+[^>]*)?>([\s\S]*?)<\/style>/gi,
2287
2281
  (_match, cssContent) => {
2282
+ const trimmed = cssContent.trim();
2283
+ const existing = document.head.querySelector("style[data-nr-global]");
2284
+ if (existing && existing.textContent === trimmed) return "";
2288
2285
  const styleEl = document.createElement("style");
2289
2286
  styleEl.setAttribute("data-nr-global", "");
2290
- styleEl.textContent = cssContent;
2287
+ styleEl.textContent = trimmed;
2291
2288
  document.head.appendChild(styleEl);
2292
2289
  return "";
2293
2290
  }
@@ -2357,19 +2354,14 @@ function renderElement(element, ctx, allElements) {
2357
2354
  switch (element.type) {
2358
2355
  case "header": {
2359
2356
  const tag = `h${element.level}`;
2360
- let text = element.text;
2361
- const alignCenter = text.match(/^->\s*(.+?)\s*<-\s*$/);
2362
- const alignRight = text.match(/^->\s*(.+?)\s*->\s*$/);
2363
- if (alignCenter) text = alignCenter[1];
2364
- else if (alignRight) text = alignRight[1];
2365
2357
  const h = document.createElement(tag);
2366
2358
  h.id = element.id;
2367
2359
  let cls = `md-h${element.level}`;
2368
- if (alignCenter) cls += " text-center";
2369
- if (alignRight) cls += " text-right";
2360
+ if (element.align === "center") cls += " text-center";
2361
+ else if (element.align === "right") cls += " text-right";
2370
2362
  if (element.classes) cls += ` ${element.classes}`;
2371
2363
  h.className = cls;
2372
- h.appendChild(renderInline(text));
2364
+ h.appendChild(renderInline(element.text));
2373
2365
  return h;
2374
2366
  }
2375
2367
  case "paragraph": {
@@ -2625,7 +2617,7 @@ var guideData = [
2625
2617
  "title": "Card",
2626
2618
  "icon": "dashboard",
2627
2619
  "order": 1,
2628
- "md": '# Card\n\nLa directiva `:::card` crea una tarjeta con icono, t\xEDtulo y contenido markdown.\n\n## Sintaxis b\xE1sica\n\nSolo t\xEDtulo e icono, sin descripci\xF3n:\n\n```md\n:::card {title="Tarjeta simple" icon="star"}\n:::\n```\n\n:::card {title="Tarjeta simple" icon="star"}\n:::\n\n## Con slot `#description`\n\nAgrega un texto descriptivo debajo del t\xEDtulo usando el slot `#description`:\n\n```md\n:::card {title="Mi proyecto" icon="rocket"}\n\n#description\nResumen corto del proyecto.\n\n:::\n```\n\n:::card {title="Mi proyecto" icon="rocket"}\n\n#description\nResumen corto del proyecto.\n\n:::\n\n## Con contenido markdown\n\nEl contenido principal va en el slot `#default` (despu\xE9s de `#description`):\n\n```md\n:::card {title="Documentaci\xF3n t\xE9cnica" icon="code"}\n\n#description\nGu\xEDa completa del motor de renderizado.\n\n- Renderizado por el mismo motor\n- Soporta `inline`, tablas y directivas\n- Sin t\xEDtulo: usa `:::card` a secas\n:::\n```\n\n:::card {title="Documentaci\xF3n t\xE9cnica" icon="code"}\n\n#description\nGu\xEDa completa del motor de renderizado.\n\n- Renderizado por el mismo motor\n- Soporta `inline`, tablas y directivas\n- Sin t\xEDtulo: usa `:::card` a secas\n:::\n\n## Grid autom\xE1tico\n\nLas tarjetas **consecutivas** se agrupan en una cuadr\xEDcula responsive. A\xF1ade `batch="off"` para evitarlo:\n\n```md\n:::card {title="HTML" icon="html"}\n\n#description\nEstructura del documento.\n:::\n:::card {title="CSS" icon="palette"}\n\n#description\nEstilos y variables.\n:::\n:::card {title="JS" icon="javascript"}\n\n#description\nInteracci\xF3n y eventos.\n:::\n```\n\n:::card {title="HTML" icon="html"}\n\n#description\nEstructura del documento.\n:::\n:::card {title="CSS" icon="palette"}\n\n#description\nEstilos y variables.\n:::\n:::card {title="JS" icon="javascript"}\n\n#description\nInteracci\xF3n y eventos.\n:::\n\n## Alineaci\xF3n del grid\n\nUsa `align` para controlar la alineaci\xF3n de las tarjetas en el grid:\n\n```md\n:::card {title="Centrada A" icon="star" align="center"}\n\n#description\nContenido.\n:::\n:::card {title="Centrada B" icon="favorite" align="center"}\n\n#description\nContenido.\n:::\n```\n\n:::card {title="Centrada A" icon="star" align="center"}\n\n#description\nContenido.\n:::\n:::card {title="Centrada B" icon="favorite" align="center"}\n\n#description\nContenido.\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | T\xEDtulo de la tarjeta |\n| `icon` | nombre Material | Icono del t\xEDtulo |\n| `image` | URL | Imagen de banner superior |\n| `align` | `left` \\| `center` \\| `right` | Alineaci\xF3n de las tarjetas en el grid (default `left`) |\n| `batch` | `off` | Desactiva el agrupado en grid con las tarjetas vecinas |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Slots\n\n| Slot | Descripci\xF3n |\n| --- | --- |\n| `#description` | Texto descriptivo secundario (aparece debajo del t\xEDtulo) |\n| `default` | Contenido principal de la tarjeta |\n\n## Anidando directivas\n\n```md\n:::card {title="Ejemplo anidado" icon="layers"}\n\n#description\nUna admonici\xF3n dentro de la tarjeta.\n\n:::note\nLas tarjetas aceptan cualquier directiva dentro.\n:::\n:::\n```\n\n:::card {title="Ejemplo anidado" icon="layers"}\n\n#description\nUna admonici\xF3n dentro de la tarjeta.\n\n:::note\nLas tarjetas aceptan cualquier directiva dentro.\n:::\n:::\n\n## Variantes\n\nExisten dos variantes de la tarjeta con comportamiento interactivo:\n\n| Directiva | Comportamiento al hacer click |\n| --- | --- |\n| `:::card` | Sin acci\xF3n (tarjeta est\xE1tica) |\n| `:::card-m` | Abre un modal con el contenido del slot `#content` |\n| `:::card-b` | Navega a la URL indicada en la prop `url` |\n\nLas tres variantes comparten las mismas props base (`title`, `icon`, `image`) y se agrupan autom\xE1ticamente en grid. Consulta las p\xE1ginas de **Card Modal** y **Card Link** para m\xE1s detalles.'
2620
+ "md": '# Card\n\nLa directiva `:::card` crea una tarjeta con icono, t\xEDtulo y contenido markdown.\n\n## Sintaxis b\xE1sica\n\nEl slot `#description` es **obligatorio** para mostrar texto en la card:\n\n```md\n:::card {title="Mi proyecto" icon="rocket"}\n\n#description\nResumen corto del proyecto.\n\n:::\n```\n\n:::card {title="Mi proyecto" icon="rocket"}\n\n#description\nResumen corto del proyecto.\n\n:::\n\n## Con contenido markdown\n\nEl slot `#description` admite markdown completo:\n\n```md\n:::card {title="Documentaci\xF3n t\xE9cnica" icon="code"}\n\n#description\nGu\xEDa completa del motor de renderizado.\n\n- Renderizado por el mismo motor\n- Soporta `inline`, tablas y directivas\n:::\n```\n\n:::card {title="Documentaci\xF3n t\xE9cnica" icon="code"}\n\n#description\nGu\xEDa completa del motor de renderizado.\n\n- Renderizado por el mismo motor\n- Soporta `inline`, tablas y directivas\n:::\n\n## Grid autom\xE1tico\n\nLas tarjetas **consecutivas** se agrupan en una cuadr\xEDcula responsive. A\xF1ade `batch="off"` para evitarlo:\n\n```md\n:::card {title="HTML" icon="html"}\n\n#description\nEstructura del documento.\n:::\n:::card {title="CSS" icon="palette"}\n\n#description\nEstilos y variables.\n:::\n:::card {title="JS" icon="javascript"}\n\n#description\nInteracci\xF3n y eventos.\n:::\n```\n\n:::card {title="HTML" icon="html"}\n\n#description\nEstructura del documento.\n:::\n:::card {title="CSS" icon="palette"}\n\n#description\nEstilos y variables.\n:::\n:::card {title="JS" icon="javascript"}\n\n#description\nInteracci\xF3n y eventos.\n:::\n\n## Alineaci\xF3n del grid\n\nUsa `align` para controlar la alineaci\xF3n de las tarjetas en el grid:\n\n```md\n:::card {title="Centrada A" icon="star" align="center"}\n\n#description\nContenido.\n:::\n:::card {title="Centrada B" icon="favorite"}\n\n#description\nContenido.\n:::\n```\n\n> `align` solo se define en la primera card del grupo; las dem\xE1s lo ignoran.\n\n:::card {title="Centrada A" icon="star" align="center"}\n\n#description\nContenido.\n:::\n:::card {title="Centrada B" icon="favorite"}\n\n#description\nContenido.\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | T\xEDtulo de la tarjeta |\n| `icon` | nombre Material | Icono del t\xEDtulo |\n| `image` | URL | Imagen de banner superior |\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del grid. Solo se lee de la primera card del grupo (default `left`) |\n| `batch` | `off` | Desactiva el agrupado en grid con las tarjetas vecinas |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Slots\n\n| Slot | Descripci\xF3n |\n| --- | --- |\n| `#description` | Texto de la card. **Obligatorio** para mostrar contenido debajo del t\xEDtulo |\n\n## Anidando directivas\n\n```md\n:::card {title="Ejemplo anidado" icon="layers"}\n\n#description\nUna admonici\xF3n dentro de la tarjeta.\n\n:::note\nLas tarjetas aceptan cualquier directiva dentro.\n:::\n:::\n```\n\n:::card {title="Ejemplo anidado" icon="layers"}\n\n#description\nUna admonici\xF3n dentro de la tarjeta.\n\n:::note\nLas tarjetas aceptan cualquier directiva dentro.\n:::\n:::\n\n## Variantes\n\nExisten dos variantes de la tarjeta con comportamiento interactivo:\n\n| Directiva | Comportamiento al hacer click |\n| --- | --- |\n| `:::card` | Sin acci\xF3n (tarjeta est\xE1tica) |\n| `:::card-m` | Abre un modal con el contenido del slot `#content` |\n| `:::card-b` | Navega a la URL indicada en la prop `url` |\n\nLas tres variantes comparten las mismas props base (`title`, `icon`, `image`) y se agrupan autom\xE1ticamente en grid. Consulta las p\xE1ginas de **Card Modal** y **Card Link** para m\xE1s detalles.'
2629
2621
  },
2630
2622
  {
2631
2623
  "id": "card-m",
@@ -2633,7 +2625,7 @@ var guideData = [
2633
2625
  "title": "Card Modal",
2634
2626
  "icon": "open_in_new",
2635
2627
  "order": 2,
2636
- "md": '# Card Modal\r\n\r\nLa directiva `:::card-m` crea una tarjeta interactiva que al hacer click abre un **modal** con contenido detallado.\r\n\r\n## Sintaxis\r\n\r\n```md\r\n:::card-m {title="Mi proyecto" icon="rocket"}\r\n\r\n#default\r\nDescripci\xF3n breve visible en la tarjeta.\r\n\r\n#content\r\nContenido **detallado** que aparece en el modal.\r\nPuede incluir markdown completo: tablas, c\xF3digo, directivas...\r\n\r\n:::\r\n```\r\n\r\n:::card-m {title="Mi proyecto" icon="rocket"}\r\n\r\n#default\r\nDescripci\xF3n breve visible en la tarjeta.\r\n\r\n#content\r\nContenido **detallado** que aparece en el modal.\r\nPuede incluir markdown completo: tablas, c\xF3digo, directivas...\r\n\r\n:::\r\n\r\n## Con imagen\r\n\r\n```md\r\n:::card-m {title="Paisaje" image="https://img.daisyui.com/images/stock/photo-1506905925346-21bda4d32df4.webp"}\r\n\r\n#default\r\nUna vista impresionante de las monta\xF1as.\r\n\r\n#content\r\n## Detalles del paisaje\r\n\r\n- Ubicaci\xF3n: Alpes suizos\r\n- Altitud: 2.500m\r\n- Mejor \xE9poca: Junio\u2013Septiembre\r\n\r\n:::\r\n```\r\n\r\n:::card-m {title="Paisaje" image="https://img.daisyui.com/images/stock/photo-1506905925346-21bda4d32df4.webp"}\r\n\r\n#default\r\nUna vista impresionante de las monta\xF1as.\r\n\r\n#content\r\n## Detalles del paisaje\r\n\r\n- Ubicaci\xF3n: Alpes suizos\r\n- Altitud: 2.500m\r\n- Mejor \xE9poca: Junio\u2013Septiembre\r\n\r\n:::\r\n\r\n## Con slot `#description`\r\n\r\n```md\r\n:::card-m {title="Estad\xEDsticas" icon="analytics"}\r\n\r\n#default\r\nResumen r\xE1pido del rendimiento.\r\n\r\n#description\r\nDatos del \xFAltimo trimestre.\r\n\r\n#content\r\n| M\xE9trica | Valor |\r\n| --- | --- |\r\n| Usuarios | 12.345 |\r\n| Tasa de conversi\xF3n | 3,2% |\r\n| Tiempo medio | 2m 15s |\r\n\r\n:::\r\n```\r\n\r\n:::card-m {title="Estad\xEDsticas" icon="analytics"}\r\n\r\n#default\r\nResumen r\xE1pido del rendimiento.\r\n\r\n#description\r\nDatos del \xFAltimo trimestre.\r\n\r\n#content\r\n| M\xE9trica | Valor |\r\n| --- | --- |\r\n| Usuarios | 12.345 |\r\n| Tasa de conversi\xF3n | 3,2% |\r\n| Tiempo medio | 2m 15s |\r\n\r\n:::\r\n\r\n## Grid autom\xE1tico\r\n\r\nLas cards `:::card-m` se agrupan en grid con `:::card` y `:::card-b`. Usa `batch="off"` para evitarlo:\r\n\r\n```md\r\n:::card {title="Est\xE1tica" icon="info"}\r\nContenido siempre visible.\r\n:::\r\n:::card-m {title="Modal" icon="open_in_new"}\r\nClick para ver m\xE1s.\r\n:::\r\n:::card-b {title="Link" icon="link" url="https://example.com"}\r\nAbre en nueva pesta\xF1a.\r\n:::\r\n```\r\n\r\n:::card {title="Est\xE1tica" icon="info"}\r\nContenido siempre visible.\r\n:::\r\n:::card-m {title="Modal" icon="open_in_new"}\r\nClick para ver m\xE1s.\r\n:::\r\n:::card-b {title="Link" icon="link" url="https://example.com"}\r\nAbre en nueva pesta\xF1a.\r\n:::\r\n\r\n## Props\r\n\r\n| Prop | Tipo | Descripci\xF3n |\r\n| --- | --- | --- |\r\n| `title` | texto | T\xEDtulo de la tarjeta |\r\n| `icon` | nombre Material | Icono del t\xEDtulo |\r\n| `image` | URL | Imagen de banner superior |\r\n| `url` | URL | URL opcional (no se usa como link, solo metadata) |\r\n| `batch` | `off` | Desactiva el agrupado en grid |\r\n| `class` | texto | Clases CSS adicionales |\r\n| `style` | CSS | Estilos inline |\r\n\r\n## Slots\r\n\r\n| Slot | Descripci\xF3n |\r\n| --- | --- |\r\n| `default` | Contenido breve visible en la tarjeta |\r\n| `#description` | Texto descriptivo secundario |\r\n| `#content` | Contenido detallado que se muestra en el modal |\r\n\r\n## Diferencia con `:::card` y `:::card-b`\r\n\r\n| Directiva | Comportamiento al hacer click |\r\n| --- | --- |\r\n| `:::card` | Sin acci\xF3n (tarjeta est\xE1tica) |\r\n| `:::card-m` | Abre un modal con el contenido de `#content` |\r\n| `:::card-b` | Navega a la URL indicada en `url` |'
2628
+ "md": '# Card Modal\r\n\r\nLa directiva `:::card-m` crea una tarjeta interactiva que al hacer click abre un **modal** con contenido detallado.\r\n\r\n## Sintaxis\r\n\r\n```md\r\n:::card-m {title="Mi proyecto" icon="rocket"}\r\n\r\n#description\r\nDescripci\xF3n breve visible en la tarjeta.\r\n\r\n#content\r\nContenido **detallado** que aparece en el modal.\r\nPuede incluir markdown completo: tablas, c\xF3digo, directivas...\r\n\r\n:::\r\n```\r\n\r\n:::card-m {title="Mi proyecto" icon="rocket"}\r\n\r\n#description\r\nDescripci\xF3n breve visible en la tarjeta.\r\n\r\n#content\r\nContenido **detallado** que aparece en el modal.\r\nPuede incluir markdown completo: tablas, c\xF3digo, directivas...\r\n\r\n:::\r\n\r\n## Con imagen\r\n\r\n```md\r\n:::card-m {title="Paisaje" image="https://img.daisyui.com/images/stock/photo-1506905925346-21bda4d32df4.webp"}\r\n\r\n#description\r\nUna vista impresionante de las monta\xF1as.\r\n\r\n#content\r\n## Detalles del paisaje\r\n\r\n- Ubicaci\xF3n: Alpes suizos\r\n- Altitud: 2.500m\r\n- Mejor \xE9poca: Junio\u2013Septiembre\r\n\r\n:::\r\n```\r\n\r\n:::card-m {title="Paisaje" image="https://img.daisyui.com/images/stock/photo-1506905925346-21bda4d32df4.webp"}\r\n\r\n#description\r\nUna vista impresionante de las monta\xF1as.\r\n\r\n#content\r\n## Detalles del paisaje\r\n\r\n- Ubicaci\xF3n: Alpes suizos\r\n- Altitud: 2.500m\r\n- Mejor \xE9poca: Junio\u2013Septiembre\r\n\r\n:::\r\n\r\n## Con slot `#description`\r\n\r\n```md\r\n:::card-m {title="Estad\xEDsticas" icon="analytics"}\r\n\r\n#description\r\nResumen r\xE1pido del rendimiento.\r\n\r\n#content\r\n| M\xE9trica | Valor |\r\n| --- | --- |\r\n| Usuarios | 12.345 |\r\n| Tasa de conversi\xF3n | 3,2% |\r\n| Tiempo medio | 2m 15s |\r\n\r\n:::\r\n```\r\n\r\n:::card-m {title="Estad\xEDsticas" icon="analytics"}\r\n\r\n#description\r\nResumen r\xE1pido del rendimiento.\r\n\r\n#content\r\n| M\xE9trica | Valor |\r\n| --- | --- |\r\n| Usuarios | 12.345 |\r\n| Tasa de conversi\xF3n | 3,2% |\r\n| Tiempo medio | 2m 15s |\r\n\r\n:::\r\n\r\n## Grid autom\xE1tico\r\n\r\nLas cards `:::card-m` se agrupan en grid con `:::card` y `:::card-b`. Usa `batch="off"` para evitarlo:\r\n\r\n```md\r\n:::card {title="Est\xE1tica" icon="info"}\r\nContenido siempre visible.\r\n:::\r\n:::card-m {title="Modal" icon="open_in_new"}\r\nClick para ver m\xE1s.\r\n:::\r\n:::card-b {title="Link" icon="link" url="https://example.com"}\r\nAbre en nueva pesta\xF1a.\r\n:::\r\n```\r\n\r\n:::card {title="Est\xE1tica" icon="info"}\r\nContenido siempre visible.\r\n:::\r\n:::card-m {title="Modal" icon="open_in_new"}\r\nClick para ver m\xE1s.\r\n:::\r\n:::card-b {title="Link" icon="link" url="https://example.com"}\r\nAbre en nueva pesta\xF1a.\r\n:::\r\n\r\n## Props\r\n\r\n| Prop | Tipo | Descripci\xF3n |\r\n| --- | --- | --- |\r\n| `title` | texto | T\xEDtulo de la tarjeta |\r\n| `icon` | nombre Material | Icono del t\xEDtulo |\r\n| `image` | URL | Imagen de banner superior |\r\n| `url` | URL | URL opcional (no se usa como link, solo metadata) |\r\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del grid. Solo se lee de la primera card del grupo (default `left`) |\r\n| `batch` | `off` | Desactiva el agrupado en grid |\r\n| `class` | texto | Clases CSS adicionales |\r\n| `style` | CSS | Estilos inline |\r\n\r\n## Slots\r\n\r\n| Slot | Descripci\xF3n |\r\n| --- | --- |\r\n| `#description` | Texto de la card. **Obligatorio** para mostrar contenido debajo del t\xEDtulo |\r\n| `#content` | Contenido detallado que se muestra en el modal |\r\n\r\n## Diferencia con `:::card` y `:::card-b`\r\n\r\n| Directiva | Comportamiento al hacer click |\r\n| --- | --- |\r\n| `:::card` | Sin acci\xF3n (tarjeta est\xE1tica) |\r\n| `:::card-m` | Abre un modal con el contenido de `#content` |\r\n| `:::card-b` | Navega a la URL indicada en `url` |'
2637
2629
  },
2638
2630
  {
2639
2631
  "id": "keys",
@@ -2641,7 +2633,7 @@ var guideData = [
2641
2633
  "title": "Keys (teclas)",
2642
2634
  "icon": "keyboard",
2643
2635
  "order": 2,
2644
- "md": '# Keys (teclas)\n\nLa directiva `:::keys` muestra combinaciones de teclado con apariencia de teclas f\xEDsicas.\n\n## Sintaxis\n\n```md\n:::keys\nCTRL + C\n:::\n```\n\n:::keys\nCTRL + C\n:::\n\n## Varias combinaciones\n\n```md\n:::keys\nCTRL + SHIFT + P\n:::\n```\n\n:::keys\nCTRL + SHIFT + P\n:::\n\n:::keys\nALT + F4\n:::\n\n:::keys\nESC\n:::\n\n## Tama\xF1os\n\n| Tama\xF1o | Sintaxis |\n| --- | --- |\n| `xs` | `:::keys {size="xs"}` |\n| `sm` | `:::keys {size="sm"}` |\n| `md` | sin prop (default) |\n| `lg` | `:::keys {size="lg"}` |\n| `xl` | `:::keys {size="xl"}` |\n\n:::keys {size="xs"}\nCTRL + A\n:::\n\n:::keys {size="sm"}\nCTRL + A\n:::\n\n:::keys {size="md"}\nCTRL + A\n:::\n\n:::keys {size="lg"}\nCTRL + A\n:::\n\n:::keys {size="xl"}\nCTRL + A\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `size` | `xs` \\| `sm` \\| `md` \\| `lg` \\| `xl` | Tama\xF1o de las teclas (default `md`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Ejemplo combinado\n\n```md\n:::keys {size="lg"}\nSHIFT + CTRL + G\n:::\n```\n\n:::keys {size="lg"}\nSHIFT + CTRL + G\n:::\n\n> Las teclas se separan autom\xE1ticamente por el signo `+`.'
2636
+ "md": '# Keys (teclas)\n\nLa directiva `:::keys` muestra combinaciones de teclado con apariencia de teclas f\xEDsicas.\n\n## Sintaxis\n\n```md\n:::keys\nCTRL + C\n:::\n```\n\n:::keys\nCTRL + C\n:::\n\n## Varias combinaciones\n\n```md\n:::keys\nCTRL + SHIFT + P\n:::\n```\n\n:::keys\nCTRL + SHIFT + P\n:::\n\n:::keys\nALT + F4\n:::\n\n:::keys\nESC\n:::\n\n## Tama\xF1os\n\n| Tama\xF1o | Sintaxis |\n| --- | --- |\n| `xs` | `:::keys {size="xs"}` |\n| `sm` | `:::keys {size="sm"}` |\n| `md` | sin prop (default) |\n| `lg` | `:::keys {size="lg"}` |\n| `xl` | `:::keys {size="xl"}` |\n\n:::keys {size="xs"}\nCTRL + A\n:::\n\n:::keys {size="sm"}\nCTRL + A\n:::\n\n:::keys {size="md"}\nCTRL + A\n:::\n\n:::keys {size="lg"}\nCTRL + A\n:::\n\n:::keys {size="xl"}\nCTRL + A\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `size` | `xs` / `sm` / `md` / `lg` / `xl` | Tama\xF1o de las teclas (default `md`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Ejemplo combinado\n\n```md\n:::keys {size="lg"}\nSHIFT + CTRL + G\n:::\n```\n\n:::keys {size="lg"}\nSHIFT + CTRL + G\n:::\n\n> Las teclas se separan autom\xE1ticamente por el signo `+`.'
2645
2637
  },
2646
2638
  {
2647
2639
  "id": "accordion",
@@ -2657,7 +2649,7 @@ var guideData = [
2657
2649
  "title": "Card Link",
2658
2650
  "icon": "link",
2659
2651
  "order": 3,
2660
- "md": '# Card Link\r\n\r\nLa directiva `:::card-b` crea una tarjeta interactiva que al hacer click **navega a una URL** en una nueva pesta\xF1a.\r\n\r\n## Sintaxis\r\n\r\n```md\r\n:::card-b {title="Documentaci\xF3n" icon="menu_book" url="https://example.com"}\r\n\r\n#default\r\nAccede a la documentaci\xF3n completa del proyecto.\r\n\r\n:::\r\n```\r\n\r\n:::card-b {title="Documentaci\xF3n" icon="menu_book" url="https://example.com"}\r\n\r\n#default\r\nAccede a la documentaci\xF3n completa del proyecto.\r\n\r\n:::\r\n\r\n## Con imagen\r\n\r\n```md\r\n:::card-b {title="GitHub" image="https://img.daisyui.com/images/stock/photo-1470071459604-3b5ec3a7fe05.webp" url="https://github.com"}\r\n\r\n#default\r\nExplora el repositorio en GitHub.\r\n\r\n:::\r\n```\r\n\r\n:::card-b {title="GitHub" image="https://img.daisyui.com/images/stock/photo-1470071459604-3b5ec3a7fe05.webp" url="https://github.com"}\r\n\r\n#default\r\nExplora el repositorio en GitHub.\r\n\r\n:::\r\n\r\n## Con slot `#description`\r\n\r\n```md\r\n:::card-b {title="NPM" icon="inventory_2" url="https://npmjs.com"}\r\n\r\n#default\r\nPublicado recientemente con las \xFAltimas mejoras.\r\n\r\n#description\r\nPaquete disponible en npm.\r\n\r\n:::\r\n```\r\n\r\n:::card-b {title="NPM" icon="inventory_2" url="https://npmjs.com"}\r\n\r\n#default\r\nPublicado recientemente con las \xFAltimas mejoras.\r\n\r\n#description\r\nPaquete disponible en npm.\r\n\r\n:::\r\n\r\n## Grid autom\xE1tico\r\n\r\nLas cards `:::card-b` se agrupan en grid con `:::card` y `:::card-m`. Usa `batch="off"` para evitarlo:\r\n\r\n```md\r\n:::card-b {title="Docs" icon="menu_book" url="https://docs.example.com"}\r\nDocumentaci\xF3n oficial.\r\n:::\r\n:::card-b {title="GitHub" icon="code" url="https://github.com"}\r\nC\xF3digo fuente.\r\n:::\r\n:::card-b {title="NPM" icon="inventory_2" url="https://npmjs.com"}\r\nPaquete npm.\r\n:::\r\n```\r\n\r\n## Props\r\n\r\n| Prop | Tipo | Descripci\xF3n |\r\n| --- | --- | --- |\r\n| `title` | texto | T\xEDtulo de la tarjeta |\r\n| `icon` | nombre Material | Icono del t\xEDtulo |\r\n| `image` | URL | Imagen de banner superior |\r\n| `url` | URL | **Requerido.** URL de destino al hacer click |\r\n| `target` | texto | Target del enlace (por defecto `_blank`) |\r\n| `batch` | `off` | Desactiva el agrupado en grid |\r\n| `class` | texto | Clases CSS adicionales |\r\n| `style` | CSS | Estilos inline |\r\n\r\n## Slots\r\n\r\n| Slot | Descripci\xF3n |\r\n| --- | --- |\r\n| `default` | Contenido visible en la tarjeta |\r\n| `#description` | Texto descriptivo secundario |\r\n\r\n## Diferencia con `:::card` y `:::card-m`\r\n\r\n| Directiva | Comportamiento al hacer click |\r\n| --- | --- |\r\n| `:::card` | Sin acci\xF3n (tarjeta est\xE1tica) |\r\n| `:::card-m` | Abre un modal con el contenido de `#content` |\r\n| `:::card-b` | Navega a la URL indicada en `url` |'
2652
+ "md": '# Card Link\r\n\r\nLa directiva `:::card-b` crea una tarjeta interactiva que al hacer click **navega a una URL** en una nueva pesta\xF1a.\r\n\r\n## Sintaxis\r\n\r\n```md\r\n:::card-b {title="Documentaci\xF3n" icon="menu_book" url="https://example.com"}\r\n\r\n#description\r\nAccede a la documentaci\xF3n completa del proyecto.\r\n\r\n:::\r\n```\r\n\r\n:::card-b {title="Documentaci\xF3n" icon="menu_book" url="https://example.com"}\r\n\r\n#description\r\nAccede a la documentaci\xF3n completa del proyecto.\r\n\r\n:::\r\n\r\n## Con imagen\r\n\r\n```md\r\n:::card-b {title="GitHub" image="https://img.daisyui.com/images/stock/photo-1470071459604-3b5ec3a7fe05.webp" url="https://github.com"}\r\n\r\n#description\r\nExplora el repositorio en GitHub.\r\n\r\n:::\r\n```\r\n\r\n:::card-b {title="GitHub" image="https://img.daisyui.com/images/stock/photo-1470071459604-3b5ec3a7fe05.webp" url="https://github.com"}\r\n\r\n#description\r\nExplora el repositorio en GitHub.\r\n\r\n:::\r\n\r\n## Con imagen y descripci\xF3n\r\n\r\n```md\r\n:::card-b {title="NPM" icon="inventory_2" image="https://img.daisyui.com/images/stock/photo-1470071459604-3b5ec3a7fe05.webp" url="https://npmjs.com"}\r\n\r\n#description\r\nPublicado recientemente con las \xFAltimas mejoras.\r\n\r\n:::\r\n```\r\n\r\n:::card-b {title="NPM" icon="inventory_2" image="https://img.daisyui.com/images/stock/photo-1470071459604-3b5ec3a7fe05.webp" url="https://npmjs.com"}\r\n\r\n#description\r\nPublicado recientemente con las \xFAltimas mejoras.\r\n\r\n:::\r\n\r\n## Grid autom\xE1tico\r\n\r\nLas cards `:::card-b` se agrupan en grid con `:::card` y `:::card-m`. Usa `batch="off"` para evitarlo:\r\n\r\n```md\r\n:::card-b {title="Docs" icon="menu_book" url="https://docs.example.com"}\r\n\r\n#description\r\nDocumentaci\xF3n oficial.\r\n:::\r\n:::card-b {title="GitHub" icon="code" url="https://github.com"}\r\n\r\n#description\r\nC\xF3digo fuente.\r\n:::\r\n:::card-b {title="NPM" icon="inventory_2" url="https://npmjs.com"}\r\n\r\n#description\r\nPaquete npm.\r\n:::\r\n```\r\n\r\n## Props\r\n\r\n| Prop | Tipo | Descripci\xF3n |\r\n| --- | --- | --- |\r\n| `title` | texto | T\xEDtulo de la tarjeta |\r\n| `icon` | nombre Material | Icono del t\xEDtulo |\r\n| `image` | URL | Imagen de banner superior |\r\n| `url` | URL | **Requerido.** URL de destino al hacer click |\r\n| `target` | texto | Target del enlace (por defecto `_blank`) |\r\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del grid. Solo se lee de la primera card del grupo (default `left`) |\r\n| `batch` | `off` | Desactiva el agrupado en grid |\r\n| `class` | texto | Clases CSS adicionales |\r\n| `style` | CSS | Estilos inline |\r\n\r\n## Slots\r\n\r\n| Slot | Descripci\xF3n |\r\n| --- | --- |\r\n| `#description` | Texto de la card. **Obligatorio** para mostrar contenido debajo del t\xEDtulo |\r\n\r\n## Diferencia con `:::card` y `:::card-m`\r\n\r\n| Directiva | Comportamiento al hacer click |\r\n| --- | --- |\r\n| `:::card` | Sin acci\xF3n (tarjeta est\xE1tica) |\r\n| `:::card-m` | Abre un modal con el contenido de `#content` |\r\n| `:::card-b` | Navega a la URL indicada en `url` |'
2661
2653
  },
2662
2654
  {
2663
2655
  "id": "carousel",
@@ -2665,7 +2657,7 @@ var guideData = [
2665
2657
  "title": "Carousel",
2666
2658
  "icon": "view_carousel",
2667
2659
  "order": 4,
2668
- "md": '# Carousel\n\nLa directiva `:::carousel` muestra un carrusel de im\xE1genes con flechas, puntos de navegaci\xF3n y **loop infinito**.\n\n## Sintaxis\n\nLas im\xE1genes se ponen como markdown dentro del bloque:\n\n```md\n:::carousel {height="320px"}\n![Foto 1](https://img.daisyui.com/images/stock/photo-1506905925346-21bda4d32df4.webp)\n![Foto 2](https://img.daisyui.com/images/stock/photo-1470252649378-9c29740c9fa8.webp)\n![Foto 3](https://img.daisyui.com/images/stock/photo-1441974231531-c6227db76b6e.webp)\n![Foto 4](https://img.daisyui.com/images/stock/photo-1500530855697-b586d89ba3ee.webp)\n:::\n```\n\n:::carousel {height="320px"}\n![Foto 1](https://img.daisyui.com/images/stock/photo-1506905925346-21bda4d32df4.webp)\n![Foto 2](https://img.daisyui.com/images/stock/photo-1470252649378-9c29740c9fa8.webp)\n![Foto 3](https://img.daisyui.com/images/stock/photo-1441974231531-c6227db76b6e.webp)\n![Foto 4](https://img.daisyui.com/images/stock/photo-1500530855697-b586d89ba3ee.webp)\n:::\n\n## Tama\xF1o y proporci\xF3n\n\n- Sin props, el viewport usa `16/9` de aspecto.\n- `height` fija la altura del viewport (las im\xE1genes lo rellenan).\n- `aspect` fija la proporci\xF3n (`4/3`, `1/1`, `21/9`...).\n\n```md\n:::carousel {aspect="4/3" width="420px" float="right"}\n![A](https://img.daisyui.com/images/stock/photo-1529626455594-4ff0802cfb7e.webp)\n![B](https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp)\n:::\n```\n\n:::carousel {aspect="4/3" width="420px" float="right"}\n![A](https://img.daisyui.com/images/stock/photo-1529626455594-4ff0802cfb7e.webp)\n![B](https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp)\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `height` | CSS (px) | Altura fija del viewport |\n| `aspect` | ratio | Proporci\xF3n del viewport (default `16/9`) |\n| `width` | CSS (px, %) | Ancho del carrusel |\n| `float` | `left` \\| `right` \\| `center` | Flotaci\xF3n (sin `width`, el flotante usa `max-width: 50%`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Interacci\xF3n\n\n- **Flechas** (izquierda/derecha): navegar.\n- **Puntos** inferiores: ir a una imagen.\n- **Teclado**: `\u2190` y `\u2192` cuando el carrusel est\xE1 enfocado.\n- El carrusel **da la vuelta** al llegar al final (loop infinito).\n\n> Las im\xE1genes se recortan (`object-fit: cover`) para rellenar el viewport sin deformarse.'
2660
+ "md": '# Carousel\n\nLa directiva `:::carousel` muestra un carrusel de im\xE1genes con flechas, puntos de navegaci\xF3n y **loop infinito**.\n\n## Sintaxis\n\nLas im\xE1genes se ponen como markdown dentro del bloque:\n\n```md\n:::carousel {height="320px"}\n![Foto 1](https://img.daisyui.com/images/stock/photo-1506905925346-21bda4d32df4.webp)\n![Foto 2](https://img.daisyui.com/images/stock/photo-1470252649378-9c29740c9fa8.webp)\n![Foto 3](https://img.daisyui.com/images/stock/photo-1441974231531-c6227db76b6e.webp)\n![Foto 4](https://img.daisyui.com/images/stock/photo-1500530855697-b586d89ba3ee.webp)\n:::\n```\n\n:::carousel {height="320px"}\n![Foto 1](https://img.daisyui.com/images/stock/photo-1506905925346-21bda4d32df4.webp)\n![Foto 2](https://img.daisyui.com/images/stock/photo-1470252649378-9c29740c9fa8.webp)\n![Foto 3](https://img.daisyui.com/images/stock/photo-1441974231531-c6227db76b6e.webp)\n![Foto 4](https://img.daisyui.com/images/stock/photo-1500530855697-b586d89ba3ee.webp)\n:::\n\n## Tama\xF1o y proporci\xF3n\n\n- Sin props, el viewport usa `16/9` de aspecto.\n- `height` fija la altura del viewport (las im\xE1genes lo rellenan).\n- `aspect` fija la proporci\xF3n (`4/3`, `1/1`, `21/9`...).\n\n```md\n:::carousel {aspect="4/3" width="420px" float="right"}\n![A](https://img.daisyui.com/images/stock/photo-1529626455594-4ff0802cfb7e.webp)\n![B](https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp)\n:::\n```\n\n:::carousel {aspect="4/3" width="420px" float="right"}\n![A](https://img.daisyui.com/images/stock/photo-1529626455594-4ff0802cfb7e.webp)\n![B](https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp)\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `height` | CSS (px) | Altura fija del viewport |\n| `aspect` | ratio | Proporci\xF3n del viewport (default `16/9`) |\n| `width` | CSS (px, %) | Ancho del carrusel |\n| `float` | `left` / `right` / `center` | Flotaci\xF3n (sin `width`, el flotante usa `max-width: 50%`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Interacci\xF3n\n\n- **Flechas** (izquierda/derecha): navegar.\n- **Puntos** inferiores: ir a una imagen.\n- **Teclado**: `\u2190` y `\u2192` cuando el carrusel est\xE1 enfocado.\n- El carrusel **da la vuelta** al llegar al final (loop infinito).\n\n> Las im\xE1genes se recortan (`object-fit: cover`) para rellenar el viewport sin deformarse.'
2669
2661
  },
2670
2662
  {
2671
2663
  "id": "countdown",
@@ -2681,7 +2673,7 @@ var guideData = [
2681
2673
  "title": "Diff (comparar im\xE1genes)",
2682
2674
  "icon": "compare",
2683
2675
  "order": 6,
2684
- "md": '# Diff (comparar im\xE1genes)\n\nLa directiva `:::diff` muestra **antes y despu\xE9s** con un slider arrastrable.\n\n## Sintaxis\n\nSe indican las dos im\xE1genes con las props `before` y `after`:\n\n```md\n:::diff {before="https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp" after="https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp"}\n:::\n```\n\n:::diff {before="https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp" after="https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp"}\n:::\n\n## Con dos im\xE1genes markdown\n\nAlternativa: dos im\xE1genes en el cuerpo del bloque (la primera es el \xABantes\xBB):\n\n```md\n:::diff {height="320px"}\n![Antes](https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp)\n![Despu\xE9s](https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp)\n:::\n```\n\n:::diff {height="320px"}\n![Antes](https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp)\n![Despu\xE9s](https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp)\n:::\n\n## Tama\xF1o y flotaci\xF3n\n\n```md\n:::diff {width="440px" aspect="4/3" float="left" before="https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp" after="https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp"}\n:::\n```\n\n:::diff {width="440px" aspect="4/3" float="left" before="https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp" after="https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp"}\n:::\n\nTexto que fluye junto al diff flotante: la comparaci\xF3n queda integrada en el p\xE1rrafo como una imagen flotante m\xE1s.\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `before` | URL | Imagen \xABantes\xBB (descartada si hay dos im\xE1genes markdown) |\n| `after` | URL | Imagen \xABdespu\xE9s\xBB |\n| `height` | CSS (px) | Altura del comparador (default `16/9` de aspecto) |\n| `aspect` | ratio | Proporci\xF3n (`4/3`, `1/1`, ...) |\n| `width` | CSS (px, %) | Ancho del comparador |\n| `float` | `left` \\| `right` \\| `center` | Flotaci\xF3n (sin `width`, `max-width: 50%`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Interacci\xF3n\n\n- **Arrastra** el mango vertical para mover la l\xEDnea de corte.\n- **Haz clic** en cualquier punto para saltar el slider all\xED.\n- **Teclado**: `\u2190` y `\u2192` ajustan \xB15% (enfoca el comparador con Tab).'
2676
+ "md": '# Diff (comparar im\xE1genes)\n\nLa directiva `:::diff` muestra **antes y despu\xE9s** con un slider arrastrable.\n\n## Sintaxis\n\nSe indican las dos im\xE1genes con las props `before` y `after`:\n\n```md\n:::diff {before="https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp" after="https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp"}\n:::\n```\n\n:::diff {before="https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp" after="https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp"}\n:::\n\n## Con dos im\xE1genes markdown\n\nAlternativa: dos im\xE1genes en el cuerpo del bloque (la primera es el \xABantes\xBB):\n\n```md\n:::diff {height="320px"}\n![Antes](https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp)\n![Despu\xE9s](https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp)\n:::\n```\n\n:::diff {height="320px"}\n![Antes](https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp)\n![Despu\xE9s](https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp)\n:::\n\n## Tama\xF1o y flotaci\xF3n\n\n```md\n:::diff {width="440px" aspect="4/3" float="left" before="https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp" after="https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp"}\n:::\n```\n\n:::diff {width="440px" aspect="4/3" float="left" before="https://img.daisyui.com/images/stock/photo-1565098772267-60af42b81ef2.webp" after="https://img.daisyui.com/images/stock/photo-1572635196237-14b3f281503f.webp"}\n:::\n\nTexto que fluye junto al diff flotante: la comparaci\xF3n queda integrada en el p\xE1rrafo como una imagen flotante m\xE1s.\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `before` | URL | Imagen \xABantes\xBB (descartada si hay dos im\xE1genes markdown) |\n| `after` | URL | Imagen \xABdespu\xE9s\xBB |\n| `height` | CSS (px) | Altura del comparador (default `16/9` de aspecto) |\n| `aspect` | ratio | Proporci\xF3n (`4/3`, `1/1`, ...) |\n| `width` | CSS (px, %) | Ancho del comparador |\n| `float` | `left` / `right` / `center` | Flotaci\xF3n (sin `width`, `max-width: 50%`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Interacci\xF3n\n\n- **Arrastra** el mango vertical para mover la l\xEDnea de corte.\n- **Haz clic** en cualquier punto para saltar el slider all\xED.\n- **Teclado**: `\u2190` y `\u2192` ajustan \xB15% (enfoca el comparador con Tab).'
2685
2677
  },
2686
2678
  {
2687
2679
  "id": "hover-3d",
@@ -2705,7 +2697,7 @@ var guideData = [
2705
2697
  "title": "Chat",
2706
2698
  "icon": "chat_bubble",
2707
2699
  "order": 9,
2708
- "md": '# Chat\n\nLa directiva `:::chat` muestra **burbujas de conversaci\xF3n** (`:::chat-item`) estilo app de mensajer\xEDa.\n\n## Sintaxis\n\n```md\n:::chat\n:::chat-item {side="start" name="Ana" time="10:04"}\nHola, \xBFterminaste la documentaci\xF3n?\n:::\n:::chat-item {side="end" name="T\xFA" time="10:05"}\n\xA1S\xED! La gu\xEDa renderiza hasta directivas dentro del chat.\n:::\n:::chat-item {side="start" name="Ana" time="10:06"}\nIncre\xEDble. El motor escribe solo.\n:::\n:::\n```\n\n:::chat\n:::chat-item {side="start" name="Ana" time="10:04"}\nHola, \xBFterminaste la documentaci\xF3n?\n:::\n:::chat-item {side="end" name="T\xFA" time="10:05"}\n\xA1S\xED! La gu\xEDa renderiza hasta directivas dentro del chat.\n:::\n:::chat-item {side="start" name="Ana" time="10:06"}\nIncre\xEDble. El motor escribe solo.\n:::\n:::\n\n## Con avatar y color\n\n```md\n:::chat\n:::chat-item {side="start" name="Soporte" time="11:00" avatar="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" color="info" footer="Atendido"}\n\xBFEn qu\xE9 podemos ayudarte?\n:::\n:::chat-item {side="end" name="T\xFA" time="11:02" color="secondary" footer="Enviado"}\n\xBFC\xF3mo a\xF1ado un avatar personalizado?\n:::\n:::\n```\n\n:::chat\n:::chat-item {side="start" name="Soporte" time="11:00" avatar="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" color="info" footer="Atendido"}\n\xBFEn qu\xE9 podemos ayudarte?\n:::\n:::chat-item {side="end" name="T\xFA" time="11:02" color="secondary" footer="Enviado"}\n\xBFC\xF3mo a\xF1ado un avatar personalizado?\n:::\n:::\n\n## Props\n\n### `:::chat-item`\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `side` | `start` \\| `end` | Burbuja a la izquierda o derecha (default `start`) |\n| `name` | texto | Nombre del autor |\n| `time` | texto | Hora mostrada bajo el nombre |\n| `avatar` | URL | Imagen del avatar |\n| `color` | `neutral` \\| `primary` \\| `secondary` \\| `accent` \\| `info` \\| `success` \\| `warning` \\| `error` \\| CSS color | Color de la burbuja. Acepta tokens del tema o cualquier color CSS v\xE1lido (ej: `blue`, `#ff6600`, `rgb(255,0,0)`) |\n| `footer` | texto | Pie del mensaje |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Notas\n\n- El contenido de cada `chat-item` admite **markdown completo** (c\xF3digo, tablas, enlaces...).\n- Puedes poner varios `:::chat` en el documento; cada uno es un grupo independiente.'
2700
+ "md": '# Chat\n\nLa directiva `:::chat` muestra **burbujas de conversaci\xF3n** (`:::chat-item`) estilo app de mensajer\xEDa.\n\n## Sintaxis\n\n```md\n:::chat\n:::chat-item {side="start" name="Ana" time="10:04"}\nHola, \xBFterminaste la documentaci\xF3n?\n:::\n:::chat-item {side="end" name="T\xFA" time="10:05"}\n\xA1S\xED! La gu\xEDa renderiza hasta directivas dentro del chat.\n:::\n:::chat-item {side="start" name="Ana" time="10:06"}\nIncre\xEDble. El motor escribe solo.\n:::\n:::\n```\n\n:::chat\n:::chat-item {side="start" name="Ana" time="10:04"}\nHola, \xBFterminaste la documentaci\xF3n?\n:::\n:::chat-item {side="end" name="T\xFA" time="10:05"}\n\xA1S\xED! La gu\xEDa renderiza hasta directivas dentro del chat.\n:::\n:::chat-item {side="start" name="Ana" time="10:06"}\nIncre\xEDble. El motor escribe solo.\n:::\n:::\n\n## Con avatar y color\n\n```md\n:::chat\n:::chat-item {side="start" name="Soporte" time="11:00" avatar="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" color="info" footer="Atendido"}\n\xBFEn qu\xE9 podemos ayudarte?\n:::\n:::chat-item {side="end" name="T\xFA" time="11:02" color="secondary" footer="Enviado"}\n\xBFC\xF3mo a\xF1ado un avatar personalizado?\n:::\n:::\n```\n\n:::chat\n:::chat-item {side="start" name="Soporte" time="11:00" avatar="https://img.daisyui.com/images/stock/photo-1534528741775-53994a69daeb.webp" color="info" footer="Atendido"}\n\xBFEn qu\xE9 podemos ayudarte?\n:::\n:::chat-item {side="end" name="T\xFA" time="11:02" color="secondary" footer="Enviado"}\n\xBFC\xF3mo a\xF1ado un avatar personalizado?\n:::\n:::\n\n## Props\n### `:::chat`\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n### `:::chat-item`\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `side` | `start` \\| `end` | Burbuja a la izquierda o derecha (default `start`) |\n| `name` | texto | Nombre del autor |\n| `time` | texto | Hora mostrada bajo el nombre |\n| `avatar` | URL | Imagen del avatar |\n| `color` | `neutral` / `primary` / `secondary` / `accent` / `info` / `success` / `warning` / `error` / CSS color | Color de la burbuja. Acepta tokens del tema o cualquier color CSS v\\u00e1lido (ej: `blue`, `#ff6600`, `rgb(255,0,0)`) |\n| `footer` | texto | Pie del mensaje |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Notas\n\n- El contenido de cada `chat-item` admite **markdown completo** (c\xF3digo, tablas, enlaces...).\n- Puedes poner varios `:::chat` en el documento; cada uno es un grupo independiente.'
2709
2701
  },
2710
2702
  {
2711
2703
  "id": "richlist",
@@ -2721,7 +2713,7 @@ var guideData = [
2721
2713
  "title": "Stat",
2722
2714
  "icon": "insights",
2723
2715
  "order": 11,
2724
- "md": '# Stat\n\nLa directiva `:::stat` muestra una **estad\xEDstica** con icono, valor y descripci\xF3n. Las estad\xEDsticas **consecutivas** se agrupan en una fila.\n\n## Sintaxis\n\n```md\n:::stat {title="Descargas" value="31K" icon="download" color="success"}\n:::\n:::stat {title="Nuevos usuarios" value="4,200" icon="group_add" color="primary"}\n:::\n:::stat {title="Retenci\xF3n" value="82%" icon="trending_up" color="info"}\n:::\n```\n\n:::stat {title="Descargas" value="31K" icon="download" color="success"}\n:::\n:::stat {title="Nuevos usuarios" value="4,200" icon="group_add" color="primary"}\n:::\n:::stat {title="Retenci\xF3n" value="82%" icon="trending_up" color="info"}\n:::\n\n## Con descripci\xF3n\n\n```md\n:::stat {title="Ingresos" value="$14,320" desc="+12% este mes" icon="payments" color="secondary"}\n:::\n:::stat {title="Errores" value="3" desc="resueltos hoy" icon="bug_report" color="warning"}\n:::\n```\n\n:::stat {title="Ingresos" value="$14,320" desc="+12% este mes" icon="payments" color="secondary"}\n:::\n:::stat {title="Errores" value="3" desc="resueltos hoy" icon="bug_report" color="warning"}\n:::\n\n## Prop individual (sin agrupar)\n\n```md\n:::stat {title="Tiempo de actividad" value="99.9%" icon="monitor_heart" color="success"}\n:::\n```\n\n:::stat {title="Tiempo de actividad" value="99.9%" icon="monitor_heart" color="success"}\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | Etiqueta superior |\n| `value` | texto | Valor principal (grande) |\n| `desc` | texto | Descripci\xF3n bajo el valor |\n| `icon` | nombre Material | Icono lateral |\n| `color` | `primary` \\| `secondary` \\| `info` \\| `success` \\| `warning` \\| `error` \\| CSS color | Color del icono y valor. Acepta tokens del tema o cualquier color CSS v\xE1lido (ej: `blue`, `#ff0000`, `rgb(255,0,0)`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n> Cada `:::stat` debe cerrarse con su `:::`. Las stats contiguas se agrupan en fila autom\xE1ticamente; para separarlas deja texto entre medias.'
2716
+ "md": '# Stat\n\nLa directiva `:::stat` muestra una **estad\xEDstica** con icono, valor y descripci\xF3n. Las estad\xEDsticas **consecutivas** se agrupan en una fila.\n\n## Sintaxis\n\n```md\n:::stat {title="Descargas" value="31K" icon="download" color="success"}\n:::\n:::stat {title="Nuevos usuarios" value="4,200" icon="group_add" color="primary"}\n:::\n:::stat {title="Retenci\xF3n" value="82%" icon="trending_up" color="info"}\n:::\n```\n\n:::stat {title="Descargas" value="31K" icon="download" color="success"}\n:::\n:::stat {title="Nuevos usuarios" value="4,200" icon="group_add" color="primary"}\n:::\n:::stat {title="Retenci\xF3n" value="82%" icon="trending_up" color="info"}\n:::\n\n## Con descripci\xF3n\n\n```md\n:::stat {title="Ingresos" value="$14,320" desc="+12% este mes" icon="payments" color="secondary"}\n:::\n:::stat {title="Errores" value="3" desc="resueltos hoy" icon="bug_report" color="warning"}\n:::\n```\n\n:::stat {title="Ingresos" value="$14,320" desc="+12% este mes" icon="payments" color="secondary"}\n:::\n:::stat {title="Errores" value="3" desc="resueltos hoy" icon="bug_report" color="warning"}\n:::\n\n## Prop individual (sin agrupar)\n\n```md\n:::stat {title="Tiempo de actividad" value="99.9%" icon="monitor_heart" color="success"}\n:::\n```\n\n:::stat {title="Tiempo de actividad" value="99.9%" icon="monitor_heart" color="success"}\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | Etiqueta superior |\n| `value` | texto | Valor principal (grande) |\n| `desc` | texto | Descripci\xF3n bajo el valor |\n| `icon` | nombre Material | Icono lateral |\n| `color` | `primary` / `secondary` / `info` / `success` / `warning` / `error` / CSS color | Color del icono y valor. Acepta tokens del tema o cualquier color CSS v\\u00e1lido (ej: `blue`, `#ff0000`, `rgb(255,0,0)`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n> Cada `:::stat` debe cerrarse con su `:::`. Las stats contiguas se agrupan en fila autom\xE1ticamente; para separarlas deja texto entre medias.'
2725
2717
  },
2726
2718
  {
2727
2719
  "id": "details",
@@ -2729,7 +2721,7 @@ var guideData = [
2729
2721
  "title": "Details",
2730
2722
  "icon": "expand_more",
2731
2723
  "order": 1,
2732
- "md": '# Details\n\nLa directiva `:::details` crea un bloque **plegable** nativo (`<details>`), \xFAtil para respuestas largas o contenido oculto.\n\n## Sintaxis\n\n```md\n:::details {title="\xBFQu\xE9 es NoirMD?"}\nEditor y motor de markdown con directivas propias.\n:::\n```\n\n:::details {title="\xBFQu\xE9 es NoirMD?"}\nEditor y motor de markdown con directivas propias.\n:::\n\n## Abierto por defecto\n\n```md\n:::details {title="Atajos del editor" defaultOpen="true"}\n| Atajo | Acci\xF3n |\n| --- | --- |\n| `Ctrl+S` | Guardar |\n| `Ctrl+K` | Alternar preview |\n| `Ctrl+F` | Buscar |\n:::\n```\n\n:::details {title="Atajos del editor" defaultOpen="true"}\n| Atajo | Acci\xF3n |\n| --- | --- |\n| `Ctrl+S` | Guardar |\n| `Ctrl+K` | Alternar preview |\n| `Ctrl+F` | Buscar |\n:::\n\n## Con icono\n\n```md\n:::details {title="Soluci\xF3n del ejercicio" icon="lightbulb"}\nEl c\xF3digo resultante:\n\n```js\nconsole.log(\'\xA1Resuelto!\');\n```\n:::\n```\n\n:::details {title="Soluci\xF3n del ejercicio" icon="lightbulb"}\nEl c\xF3digo resultante:\n\n```js\nconsole.log(\'\xA1Resuelto!\');\n```\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | T\xEDtulo del desplegable |\n| `icon` | nombre Material | Icono junto al t\xEDtulo |\n| `defaultOpen` | `true` | Abierto al cargar |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n> El contenido admite markdown completo y directivas anidadas.'
2724
+ "md": '# Details\n\nLa directiva `:::details` crea un bloque **plegable** nativo (`<details>`), \xFAtil para respuestas largas o contenido oculto.\n\n## Sintaxis\n\n```md\n:::details {title="\xBFQu\xE9 es NoirMD?"}\nEditor y motor de markdown con directivas propias.\n:::\n```\n\n:::details {title="\xBFQu\xE9 es NoirMD?"}\nEditor y motor de markdown con directivas propias.\n:::\n\n## Abierto por defecto\n\n```md\n:::details {title="Atajos del editor" defaultOpen="true"}\n| Atajo | Acci\xF3n |\n| --- | --- |\n| `Ctrl+S` | Guardar |\n| `Ctrl+K` | Alternar preview |\n| `Ctrl+F` | Buscar |\n:::\n```\n\n:::details {title="Atajos del editor" defaultOpen="true"}\n| Atajo | Acci\xF3n |\n| --- | --- |\n| `Ctrl+S` | Guardar |\n| `Ctrl+K` | Alternar preview |\n| `Ctrl+F` | Buscar |\n:::\n\n## Con icono\n\n```md\n:::details {title="Soluci\xF3n del ejercicio" icon="lightbulb"}\nEl c\xF3digo resultante:\n\n```js\nconsole.log(\'\xA1Resuelto!\');\n```\n:::\n```\n\n:::details {title="Soluci\xF3n del ejercicio" icon="lightbulb"}\nEl c\xF3digo resultante:\n\n```js\nconsole.log(\'\xA1Resuelto!\');\n```\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | T\\u00edtulo del desplegable (default `Details`) |\n| `icon` | nombre Material | Icono junto al t\\u00edtulo (default `play_arrow`) |\n| `defaultOpen` | `true` | Abierto al cargar |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n> El contenido admite markdown completo y directivas anidadas.'
2733
2725
  },
2734
2726
  {
2735
2727
  "id": "modal",
@@ -2737,7 +2729,7 @@ var guideData = [
2737
2729
  "title": "Modal",
2738
2730
  "icon": "open_in_full",
2739
2731
  "order": 2,
2740
- "md": '# Modal\n\nLa directiva `:::modal` crea un **di\xE1logo modal** con su bot\xF3n de apertura.\n\n## Sintaxis\n\n```md\n:::modal {title="Confirmar borrado" label="Abrir modal" icon="delete"}\n\xBFSeguro que quieres borrar este documento? Esta acci\xF3n no se puede deshacer.\n\n| Acci\xF3n | Efecto |\n| --- | --- |\n| Aceptar | Borra el documento |\n| Cancelar | No hace nada |\n:::\n```\n\n:::modal {title="Confirmar borrado" label="Abrir modal" icon="delete"}\n\xBFSeguro que quieres borrar este documento? Esta acci\xF3n no se puede deshacer.\n\n| Acci\xF3n | Efecto |\n| --- | --- |\n| Aceptar | Borra el documento |\n| Cancelar | No hace nada |\n:::\n\n## Contenido enriquecido\n\n```md\n:::modal {title="Notas de la versi\xF3n" label="Ver novedades" icon="new_releases"}\n**v2.0** \u2014 cambios principales:\n\n- Nuevo componente `:::diff`\n- Gu\xEDa integrada en el editor\n- Especificidad CSS corregida en im\xE1genes\n:::\n```\n\n:::modal {title="Notas de la versi\xF3n" label="Ver novedades" icon="new_releases"}\n**v2.0** \u2014 cambios principales:\n\n- Nuevo componente `:::diff`\n- Gu\xEDa integrada en el editor\n- Especificidad CSS corregida en im\xE1genes\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | T\xEDtulo del modal |\n| `label` | texto | Texto del bot\xF3n de apertura (default: `title` o \xABOpen\xBB) |\n| `icon` | nombre Material | Icono del bot\xF3n (default `open_in_new`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Interacci\xF3n\n\n- **Bot\xF3n**: abre el modal (focus se mueve dentro).\n- **Overlay** o bot\xF3n **\xD7**: cierra.\n- **Esc**: cierra (en desktop).\n- `dialog` nativo \u2192 accesible por defecto, focus trapped y `inert` al fondo.'
2732
+ "md": '# Modal\n\nLa directiva `:::modal` crea un **di\xE1logo modal** con su bot\xF3n de apertura.\n\n## Sintaxis\n\n```md\n:::modal {title="Confirmar borrado" label="Abrir modal" icon="delete"}\n\xBFSeguro que quieres borrar este documento? Esta acci\xF3n no se puede deshacer.\n\n| Acci\xF3n | Efecto |\n| --- | --- |\n| Aceptar | Borra el documento |\n| Cancelar | No hace nada |\n:::\n```\n\n:::modal {title="Confirmar borrado" label="Abrir modal" icon="delete"}\n\xBFSeguro que quieres borrar este documento? Esta acci\xF3n no se puede deshacer.\n\n| Acci\xF3n | Efecto |\n| --- | --- |\n| Aceptar | Borra el documento |\n| Cancelar | No hace nada |\n:::\n\n## Contenido enriquecido\n\n```md\n:::modal {title="Notas de la versi\xF3n" label="Ver novedades" icon="new_releases"}\n**v2.0** \u2014 cambios principales:\n\n- Nuevo componente `:::diff`\n- Gu\xEDa integrada en el editor\n- Especificidad CSS corregida en im\xE1genes\n:::\n```\n\n:::modal {title="Notas de la versi\xF3n" label="Ver novedades" icon="new_releases"}\n**v2.0** \u2014 cambios principales:\n\n- Nuevo componente `:::diff`\n- Gu\xEDa integrada en el editor\n- Especificidad CSS corregida en im\xE1genes\n:::\n\n## Alineaci\xF3n del bot\xF3n\n\nEl bot\xF3n de apertura se alinea a la izquierda por defecto. Usa el prop `align` para cambiar la alineaci\xF3n:\n\n### Centrado\n\n```md\n:::modal {title="Centrado" label="Abrir" icon="open_in_full" align="center"}\nContenido del modal centrado.\n:::\n```\n\n:::modal {title="Centrado" label="Abrir" icon="open_in_full" align="center"}\nContenido del modal centrado.\n:::\n\n### Alineado a la derecha\n\n```md\n:::modal {title="Derecha" label="Abrir" icon="open_in_full" align="right"}\nContenido del modal alineado a la derecha.\n:::\n```\n\n:::modal {title="Derecha" label="Abrir" icon="open_in_full" align="right"}\nContenido del modal alineado a la derecha.\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `title` | texto | T\xEDtulo del modal |\n| `label` (o `title`) | texto | Texto del bot\xF3n de apertura (`title` funciona como alias, default: \xABOpen\xBB) |\n| `icon` | nombre Material | Icono del bot\xF3n (default `open_in_new`) |\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del bot\xF3n de apertura (default `left`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Interacci\xF3n\n\n- **Bot\xF3n**: abre el modal (focus se mueve dentro).\n- **Overlay** o bot\xF3n **\xD7**: cierra.\n- **Esc**: cierra (en desktop).\n- `dialog` nativo \u2192 accesible por defecto, focus trapped y `inert` al fondo.'
2741
2733
  },
2742
2734
  {
2743
2735
  "id": "button",
@@ -2745,7 +2737,7 @@ var guideData = [
2745
2737
  "title": "Button",
2746
2738
  "icon": "touch_app",
2747
2739
  "order": 3,
2748
- "md": '# Button\n\nLa directiva `:::button` crea un **bot\xF3n con enlace** (se abre en pesta\xF1a nueva por defecto).\n\n## Sintaxis\n\n```md\n:::button {label="Documentaci\xF3n" url="https://example.com" icon="menu_book"}\n:::\n```\n\n:::button {label="Documentaci\xF3n" url="https://example.com" icon="menu_book"}\n:::\n\n## Variante con enlace interno\n\n```md\n:::button {label="Ir a la p\xE1gina de notas" url="#admonici\xF3n-nota" icon="sticky_note_2" target="_self"}\n:::\n```\n\n:::button {label="Ir a la p\xE1gina de notas" url="#admonici\xF3n-nota" icon="sticky_note_2" target="_self"}\n:::\n\n## Con contenido markdown\n\nSi el bloque contiene texto/enlaces, se renderizan dentro del bot\xF3n:\n\n```md\n:::button {label="Descargar" url="https://example.com/download" icon="download"}\nDescarga el **manual** en PDF\n:::\n```\n\n:::button {label="Descargar" url="https://example.com/download" icon="download"}\nDescarga el **manual** en PDF\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `label` | texto | Texto del bot\xF3n |\n| `url` (o `href`) | URL | Destino del enlace (default `#`) |\n| `icon` | nombre Material | Icono (default `near_me`) |\n| `target` | `_blank` \\| `_self` \\| ... | Destino del enlace (default `_blank`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |'
2740
+ "md": '# Button\n\nLa directiva `:::button` crea un **bot\xF3n con enlace** (se abre en pesta\xF1a nueva por defecto).\n\n## Sintaxis\n\n```md\n:::button {label="Documentaci\xF3n" url="https://example.com" icon="menu_book"}\n:::\n```\n\n:::button {label="Documentaci\xF3n" url="https://example.com" icon="menu_book"}\n:::\n\n## Variante con enlace interno\n\n```md\n:::button {label="Ir a la p\xE1gina de notas" url="#admonici\xF3n-nota" icon="sticky_note_2" target="_self"}\n:::\n```\n\n:::button {label="Ir a la p\xE1gina de notas" url="#admonici\xF3n-nota" icon="sticky_note_2" target="_self"}\n:::\n\n## Con contenido markdown\n\nSi el bloque contiene texto/enlaces, se renderizan dentro del bot\xF3n:\n\n```md\n:::button {label="Descargar" url="https://example.com/download" icon="download"}\nDescarga el **manual** en PDF\n:::\n```\n\n:::button {label="Descargar" url="https://example.com/download" icon="download"}\nDescarga el **manual** en PDF\n:::\n\n## Alineaci\xF3n\n\nLos botones se alinean a la izquierda por defecto. Usa el prop `align` para cambiar la alineaci\xF3n:\n\n### Centrado\n\n```md\n:::button {label="Centrado" url="https://example.com" icon="center_focus_strong" align="center"}\n:::\n```\n\n:::button {label="Centrado" url="https://example.com" icon="center_focus_strong" align="center"}\n:::\n\n### Alineado a la derecha\n\n```md\n:::button {label="Derecha" url="https://example.com" icon="arrow_forward" align="right"}\n:::\n```\n\n:::button {label="Derecha" url="https://example.com" icon="arrow_forward" align="right"}\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `label` (o `title`) | texto | Texto del bot\xF3n (`title` funciona como alias por compatibilidad) |\n| `url` (o `href`) | URL | Destino del enlace (default `#`) |\n| `icon` | nombre Material | Icono (default `near_me`) |\n| `target` | `_blank` / `_self` / ... | Destino del enlace (default `_blank`) |\n| `align` | `left` / `center` / `right` | Alineaci\xF3n del bot\xF3n (default `left`) |\n| `class` | texto | Clases CSS adicionales |'
2749
2741
  },
2750
2742
  {
2751
2743
  "id": "slide",
@@ -2753,7 +2745,7 @@ var guideData = [
2753
2745
  "title": "Slide",
2754
2746
  "icon": "slideshow",
2755
2747
  "order": 4,
2756
- "md": '# Slide\n\nLa directiva `:::slide` convierte su contenido en un **slider autom\xE1tico** (diapositivas con fade).\n\n## Sintaxis\n\nLas secciones se separan con `---`:\n\n```md\n:::slide {interval="2500"}\n## Diapositiva 1\n\nBienvenido a la **gu\xEDa interactiva**.\n\n---\n\n## Diapositiva 2\n\nCada `---` separa una diapositiva nueva.\n\n---\n\n## Diapositiva 3\n\nY el motor se encarga del resto.\n:::\n```\n\n:::slide {interval="2500"}\n## Diapositiva 1\n\nBienvenido a la **gu\xEDa interactiva**.\n\n---\n\n## Diapositiva 2\n\nCada `---` separa una diapositiva nueva.\n\n---\n\n## Diapositiva 3\n\nY el motor se encarga del resto.\n:::\n\n## Con contenido variado\n\n```md\n:::slide {interval="3500" speed="800"}\n:::card {title="Card" icon="dashboard"}\nLas directivas se anidan dentro.\n:::\n---\n> **Admonici\xF3n** como diapositiva\n---\n| P\xE1gina | Tema |\n| --- | --- |\n| 1 | Slide |\n| 2 | Loop |\n:::\n```\n\n:::slide {interval="3500" speed="800"}\n:::card {title="Card" icon="dashboard"}\nLas directivas se anidan dentro.\n:::\n---\n> **Admonici\xF3n** como diapositiva\n---\n| P\xE1gina | Tema |\n| --- | --- |\n| 1 | Slide |\n| 2 | Loop |\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `interval` | ms | Tiempo por diapositiva (default `3000`) |\n| `speed` | ms | Duraci\xF3n de la transici\xF3n (default `500`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Notas\n\n- Los puntos inferiores permiten saltar a una diapositiva.\n- Al llegar a la \xFAltima, vuelve a la primera autom\xE1ticamente (loop).\n- La barra de progreso superior muestra el avance del ciclo.'
2748
+ "md": '# Slide\n\nLa directiva `:::slide` convierte su contenido en un **slider autom\xE1tico** (diapositivas con fade).\n\n## Sintaxis\n\nLas secciones se separan con `---`:\n\n```md\n:::slide {interval="2500"}\n## Diapositiva 1\n\nBienvenido a la **gu\xEDa interactiva**.\n\n---\n\n## Diapositiva 2\n\nCada `---` separa una diapositiva nueva.\n\n---\n\n## Diapositiva 3\n\nY el motor se encarga del resto.\n:::\n```\n\n:::slide {interval="2500"}\n## Diapositiva 1\n\nBienvenido a la **gu\xEDa interactiva**.\n\n---\n\n## Diapositiva 2\n\nCada `---` separa una diapositiva nueva.\n\n---\n\n## Diapositiva 3\n\nY el motor se encarga del resto.\n:::\n\n## Con contenido variado\n\n```md\n:::slide {interval="3500" speed="800"}\n:::card {title="Card" icon="dashboard"}\nLas directivas se anidan dentro.\n:::\n---\n> **Admonici\xF3n** como diapositiva\n---\n| P\xE1gina | Tema |\n| --- | --- |\n| 1 | Slide |\n| 2 | Loop |\n:::\n```\n\n:::slide {interval="3500" speed="800"}\n:::card {title="Card" icon="dashboard"}\nLas directivas se anidan dentro.\n:::\n---\n> **Admonici\xF3n** como diapositiva\n---\n| P\xE1gina | Tema |\n| --- | --- |\n| 1 | Slide |\n| 2 | Loop |\n:::\n\n## Props\n\n| Prop | Tipo | Descripci\xF3n |\n| --- | --- | --- |\n| `interval` | ms | Tiempo por diapositiva (default `3000`) |\n| `speed` | ms | Duraci\xF3n de la transici\xF3n (default `500`) |\n| `class` | texto | Clases CSS adicionales |\n| `style` | CSS | Estilos inline |\n\n## Notas\n\n- Al llegar a la \\u00faltima diapositiva, vuelve a la primera autom\\u00e1ticamente (loop).\n- El contenido de cada diapositiva admite markdown completo y directivas anidadas.\n- La altura del contenedor se adapta autom\\u00e1ticamente al contenido m\\u00e1s alto.'
2757
2749
  },
2758
2750
  {
2759
2751
  "id": "html-blocks",
@@ -2766,7 +2758,7 @@ var guideData = [
2766
2758
  ];
2767
2759
 
2768
2760
  // react/Guide.tsx
2769
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
2761
+ import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
2770
2762
  var Guide = ({
2771
2763
  open,
2772
2764
  onClose,
@@ -2776,6 +2768,7 @@ var Guide = ({
2776
2768
  const [query, setQuery] = useState2("");
2777
2769
  const [selectedId, setSelectedId] = useState2(null);
2778
2770
  const [collapsed, setCollapsed] = useState2({});
2771
+ const [navOpen, setNavOpen] = useState2(false);
2779
2772
  const contentRef = useRef3(null);
2780
2773
  const groups = useMemo(() => {
2781
2774
  const map = /* @__PURE__ */ new Map();
@@ -2803,6 +2796,7 @@ var Guide = ({
2803
2796
  useEffect4(() => {
2804
2797
  if (!open) return;
2805
2798
  setQuery("");
2799
+ setNavOpen(false);
2806
2800
  if (!selectedId) {
2807
2801
  const initial = guideData.find((e) => e.id === initialDirective) ?? guideData.find((e) => e.id === "introduccion") ?? guideData[0];
2808
2802
  setSelectedId(initial?.id ?? null);
@@ -2835,6 +2829,15 @@ var Guide = ({
2835
2829
  /* @__PURE__ */ jsx2("div", { className: "nr-guide__overlay", onClick: onClose }),
2836
2830
  /* @__PURE__ */ jsxs("div", { className: "nr-guide__panel", children: [
2837
2831
  /* @__PURE__ */ jsxs("header", { className: "nr-guide__head", children: [
2832
+ /* @__PURE__ */ jsx2(
2833
+ "button",
2834
+ {
2835
+ className: "nr-guide__nav-toggle",
2836
+ onClick: () => setNavOpen(true),
2837
+ "aria-label": "Abrir navegaci\xF3n",
2838
+ children: /* @__PURE__ */ jsx2("span", { className: "material-icons-round", children: "menu" })
2839
+ }
2840
+ ),
2838
2841
  /* @__PURE__ */ jsx2("span", { className: "material-icons-round", children: "menu_book" }),
2839
2842
  /* @__PURE__ */ jsx2("h2", { children: "Gu\xEDa de sintaxis" }),
2840
2843
  search && /* @__PURE__ */ jsx2(
@@ -2858,6 +2861,43 @@ var Guide = ({
2858
2861
  )
2859
2862
  ] }),
2860
2863
  /* @__PURE__ */ jsxs("div", { className: "nr-guide__body", children: [
2864
+ navOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
2865
+ /* @__PURE__ */ jsx2("div", { className: "nr-guide__nav-overlay", onClick: () => setNavOpen(false) }),
2866
+ /* @__PURE__ */ jsx2("div", { className: "nr-guide__nav-drawer nr-guide__nav-drawer--open", children: /* @__PURE__ */ jsxs("nav", { children: [
2867
+ filtered.map(([cat, entries]) => /* @__PURE__ */ jsxs("div", { className: "nr-guide__cat", children: [
2868
+ /* @__PURE__ */ jsxs(
2869
+ "button",
2870
+ {
2871
+ className: "nr-guide__cat-head",
2872
+ onClick: () => toggleCategory(cat),
2873
+ children: [
2874
+ /* @__PURE__ */ jsx2("span", { className: "material-icons-round nr-guide__cat-chevron", children: collapsed[cat] ? "chevron_right" : "expand_more" }),
2875
+ cat
2876
+ ]
2877
+ }
2878
+ ),
2879
+ !collapsed[cat] && /* @__PURE__ */ jsx2("ul", { className: "nr-guide__items", children: entries.map((e) => /* @__PURE__ */ jsx2("li", { children: /* @__PURE__ */ jsxs(
2880
+ "button",
2881
+ {
2882
+ className: `nr-guide__item${selectedId === e.id ? " nr-guide__item--active" : ""}`,
2883
+ onClick: () => {
2884
+ setSelectedId(e.id);
2885
+ setNavOpen(false);
2886
+ },
2887
+ children: [
2888
+ e.icon && /* @__PURE__ */ jsx2("span", { className: "material-icons-round nr-guide__item-icon", children: e.icon }),
2889
+ e.title
2890
+ ]
2891
+ }
2892
+ ) }, e.id)) })
2893
+ ] }, cat)),
2894
+ filtered.length === 0 && /* @__PURE__ */ jsxs("div", { className: "nr-guide__empty", children: [
2895
+ "Sin resultados para \xAB",
2896
+ query,
2897
+ "\xBB."
2898
+ ] })
2899
+ ] }) })
2900
+ ] }),
2861
2901
  /* @__PURE__ */ jsxs("nav", { className: "nr-guide__nav", children: [
2862
2902
  filtered.map(([cat, entries]) => /* @__PURE__ */ jsxs("div", { className: "nr-guide__cat", children: [
2863
2903
  /* @__PURE__ */ jsxs(